Skip to content

Commit d0fdeb6

Browse files
authored
Merge pull request #431 from synonymdev/feat/multiple-addresses-types
feat: support all address types
2 parents 11ac987 + 56a988d commit d0fdeb6

53 files changed

Lines changed: 2328 additions & 1400 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/integration-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ jobs:
6767
-only-testing:BitkitTests/UtxoSelectionTests \
6868
-only-testing:BitkitTests/BlocktankTests \
6969
-only-testing:BitkitTests/PaymentFlowTests \
70+
-only-testing:BitkitTests/AddressTypeIntegrationTests \
7071
| xcbeautify --report junit
7172
}
7273

.github/workflows/unit-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ jobs:
7171
-skip-testing:BitkitTests/UtxoSelectionTests \
7272
-skip-testing:BitkitTests/BlocktankTests \
7373
-skip-testing:BitkitTests/PaymentFlowTests \
74+
-skip-testing:BitkitTests/AddressTypeIntegrationTests \
7475
| xcbeautify --report junit
7576
echo "✅ Unit tests completed at $(date)"
7677

Bitkit.xcodeproj/project.pbxproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
membershipExceptions = (
8282
Constants/Env.swift,
8383
Extensions/HexBytes.swift,
84+
"Extensions/LDKNode+AddressType.swift",
8485
Extensions/PaymentDetails.swift,
8586
Models/BlocktankNotificationType.swift,
8687
Models/LnPeer.swift,
@@ -109,6 +110,7 @@
109110
membershipExceptions = (
110111
Constants/Env.swift,
111112
Extensions/HexBytes.swift,
113+
"Extensions/LDKNode+AddressType.swift",
112114
Extensions/PaymentDetails.swift,
113115
Models/BlocktankNotificationType.swift,
114116
Models/LnPeer.swift,
@@ -926,7 +928,7 @@
926928
repositoryURL = "https://github.com/synonymdev/ldk-node";
927929
requirement = {
928930
kind = revision;
929-
revision = d2a82a2d111e5eb84a0eec02f4754e39fea4189a;
931+
revision = 6f6bc44526695d4c5c6b2bb578aaba48b66f6c2c;
930932
};
931933
};
932934
96DEA0382DE8BBA1009932BF /* XCRemoteSwiftPackageReference "bitkit-core" */ = {

Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Bitkit/AppScene.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,6 @@ struct AppScene: View {
8787
if UserDefaults.standard.bool(forKey: "pinOnLaunch") && settings.pinEnabled {
8888
isPinVerified = false
8989
}
90-
SweepViewModel.checkAndPromptForSweepableFunds(sheets: sheets)
91-
9290
if migrations.needsPostMigrationSync {
9391
app.toast(
9492
type: .warning,
@@ -441,6 +439,9 @@ struct AppScene: View {
441439
}
442440

443441
private func restoreFromMostRecentBackup() async {
442+
BackupService.shared.setRestoring(true)
443+
defer { BackupService.shared.setRestoring(false) }
444+
444445
guard let mnemonicData = try? Keychain.load(key: .bip39Mnemonic(index: 0)),
445446
let mnemonic = String(data: mnemonicData, encoding: .utf8)
446447
else { return }
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import LDKNode
2+
3+
extension LDKNode.AddressType {
4+
// MARK: - All cases (ordered)
5+
6+
static let allAddressTypes: [LDKNode.AddressType] = [.legacy, .nestedSegwit, .nativeSegwit, .taproot]
7+
8+
/// All address types with `selected` first, remaining in standard order.
9+
static func prioritized(selected: LDKNode.AddressType) -> [LDKNode.AddressType] {
10+
var types = [selected]
11+
for type in allAddressTypes where type != selected {
12+
types.append(type)
13+
}
14+
return types
15+
}
16+
17+
// MARK: - Storage string (UserDefaults / BitkitCore APIs)
18+
19+
/// String value used in UserDefaults and BitkitCore APIs.
20+
var stringValue: String {
21+
switch self {
22+
case .legacy: return "legacy"
23+
case .nestedSegwit: return "nestedSegwit"
24+
case .nativeSegwit: return "nativeSegwit"
25+
case .taproot: return "taproot"
26+
}
27+
}
28+
29+
/// Parses storage string; returns nil for invalid or unknown values.
30+
static func from(string: String) -> LDKNode.AddressType? {
31+
switch string {
32+
case "legacy": return .legacy
33+
case "nestedSegwit": return .nestedSegwit
34+
case "nativeSegwit": return .nativeSegwit
35+
case "taproot": return .taproot
36+
default: return nil
37+
}
38+
}
39+
40+
/// Parses storage string; returns `.nativeSegwit` for nil or invalid (backward compatibility).
41+
static func fromStorage(_ string: String?) -> LDKNode.AddressType {
42+
guard let s = string, let type = from(string: s) else { return .nativeSegwit }
43+
return type
44+
}
45+
46+
/// Parses a comma-separated string of address types; filters invalid values.
47+
static func parseCommaSeparated(_ string: String) -> [LDKNode.AddressType] {
48+
string.split(separator: ",")
49+
.map { String($0).trimmingCharacters(in: .whitespaces) }
50+
.compactMap { from(string: $0) }
51+
}
52+
53+
// MARK: - Derivation path
54+
55+
/// BIP derivation path using current network (Env.network) for coin type.
56+
var derivationPath: String {
57+
let coinType = Env.network == .bitcoin ? "0" : "1"
58+
return derivationPath(coinType: coinType)
59+
}
60+
61+
/// BIP derivation path for the given coin type ("0" mainnet, "1" testnet).
62+
func derivationPath(coinType: String) -> String {
63+
switch self {
64+
case .legacy: return "m/44'/\(coinType)'/0'/0" // BIP 44
65+
case .nestedSegwit: return "m/49'/\(coinType)'/0'/0" // BIP 49
66+
case .nativeSegwit: return "m/84'/\(coinType)'/0'/0" // BIP 84
67+
case .taproot: return "m/86'/\(coinType)'/0'/0" // BIP 86
68+
}
69+
}
70+
71+
// MARK: - Localized display
72+
73+
var localizedTitle: String {
74+
switch self {
75+
case .legacy: return "Legacy"
76+
case .nestedSegwit: return "Nested Segwit"
77+
case .nativeSegwit: return "Native Segwit"
78+
case .taproot: return "Taproot"
79+
}
80+
}
81+
82+
var localizedDescription: String {
83+
switch self {
84+
case .legacy: return "Pay-to-public-key-hash (1x...)"
85+
case .nestedSegwit: return "Pay-to-Script-Hash (3x...)"
86+
case .nativeSegwit: return "Pay-to-witness-public-key-hash (bc1x...)"
87+
case .taproot: return "Pay-to-Taproot (bc1px...)"
88+
}
89+
}
90+
91+
var example: String {
92+
switch self {
93+
case .legacy: return "(1x...)"
94+
case .nestedSegwit: return "(3x...)"
95+
case .nativeSegwit: return "(bc1x...)"
96+
case .taproot: return "(bc1px...)"
97+
}
98+
}
99+
100+
var shortExample: String {
101+
switch self {
102+
case .legacy: return "1x..."
103+
case .nestedSegwit: return "3x..."
104+
case .nativeSegwit: return "bc1q..."
105+
case .taproot: return "bc1p..."
106+
}
107+
}
108+
109+
/// Accessibility / UI test identifier.
110+
var testId: String {
111+
switch self {
112+
case .legacy: return "p2pkh"
113+
case .nestedSegwit: return "p2sh-p2wpkh"
114+
case .nativeSegwit: return "p2wpkh"
115+
case .taproot: return "p2tr"
116+
}
117+
}
118+
119+
// MARK: - Address format validation
120+
121+
/// Returns true if the address has the expected prefix for this address type on the given network.
122+
/// Defensive check only; not a full script/checksum validation.
123+
func matchesAddressFormat(_ address: String, network: LDKNode.Network) -> Bool {
124+
let trimmed = address.trimmingCharacters(in: .whitespaces)
125+
guard !trimmed.isEmpty else { return false }
126+
let isMainnet = network == .bitcoin
127+
switch self {
128+
case .legacy:
129+
return isMainnet ? trimmed.hasPrefix("1") : trimmed.hasPrefix("m") || trimmed.hasPrefix("n")
130+
case .nestedSegwit:
131+
return isMainnet ? trimmed.hasPrefix("3") : trimmed.hasPrefix("2")
132+
case .nativeSegwit:
133+
return isMainnet ? trimmed.hasPrefix("bc1q") : trimmed.hasPrefix("tb1q") || trimmed.hasPrefix("bcrt1q")
134+
case .taproot:
135+
return isMainnet ? trimmed.hasPrefix("bc1p") : trimmed.hasPrefix("tb1p") || trimmed.hasPrefix("bcrt1p")
136+
}
137+
}
138+
}

Bitkit/MainNavView.swift

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ struct MainNavView: View {
1010
@EnvironmentObject private var wallet: WalletViewModel
1111
@Environment(\.scenePhase) var scenePhase
1212

13-
@StateObject private var sweepViewModel = SweepViewModel()
14-
1513
@State private var showClipboardAlert = false
1614
@State private var clipboardUri: String?
1715

@@ -164,14 +162,6 @@ struct MainNavView: View {
164162
) {
165163
config in ForceTransferSheet(config: config)
166164
}
167-
.sheet(
168-
item: $sheets.sweepPromptSheetItem,
169-
onDismiss: {
170-
sheets.hideSheet()
171-
}
172-
) {
173-
config in SweepPromptSheet(config: config)
174-
}
175165
.accentColor(.white)
176166
.overlay {
177167
TabBar()
@@ -403,18 +393,14 @@ struct MainNavView: View {
403393

404394
// Advanced settings
405395
case .coinSelection: CoinSelectionSettingsView()
396+
case .addressTypePreference: AddressTypePreferenceView()
406397
case .connections: LightningConnectionsView()
407398
case let .connectionDetail(channelId): LightningConnectionDetailView(channelId: channelId)
408399
case let .closeConnection(channel: channel): CloseConnectionConfirmation(channel: channel)
409400
case .node: NodeStateView()
410401
case .electrumSettings: ElectrumSettingsScreen()
411402
case .rgsSettings: RgsSettingsScreen()
412403
case .addressViewer: AddressViewer()
413-
case .sweep: SweepSettingsView().environmentObject(sweepViewModel)
414-
case .sweepConfirm: SweepConfirmView().environmentObject(sweepViewModel)
415-
case .sweepFeeRate: SweepFeeRateView().environmentObject(sweepViewModel)
416-
case .sweepFeeCustom: SweepFeeCustomView().environmentObject(sweepViewModel)
417-
case let .sweepSuccess(txid): SweepSuccessView(txid: txid).environmentObject(sweepViewModel)
418404

419405
// Dev settings
420406
case .blocktankRegtest: BlocktankRegtestView()

Bitkit/Models/SettingsBackupConfig.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ enum SettingsBackupConfig {
4141
"defaultTransactionSpeed": .string(optional: true),
4242
"coinSelectionMethod": .string(optional: true),
4343
"coinSelectionAlgorithm": .string(optional: true),
44+
"selectedAddressType": .string(optional: true),
45+
"addressTypesToMonitor": .string(optional: true),
4446
"enableQuickpay": .bool,
4547
"showWidgets": .bool,
4648
"showWidgetTitles": .bool,

Bitkit/Resources/Localization/cs.lproj/Localizable.strings

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,8 +710,6 @@
710710
"settings__adv__section_other" = "Další";
711711
"settings__adv__address_type" = "Typ bitcoinové adresy";
712712
"settings__adv__monitored_address_types" = "Sledované typy adres";
713-
"settings__adv__monitored_address_types_update_title" = "Aktualizace sledovaných typů adres";
714-
"settings__adv__monitored_address_types_update_description" = "Změny se plně projeví po restartu aplikace.";
715713
"settings__adv__gap_limit" = "Limit mezery v adrese";
716714
"settings__adv__coin_selection" = "Výběr mince";
717715
"settings__adv__cs_method" = "Metoda výběru mince";

Bitkit/Resources/Localization/de.lproj/Localizable.strings

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,8 +708,6 @@
708708
"settings__adv__section_other" = "Andere";
709709
"settings__adv__address_type" = "Bitcoin Adressen-Typ";
710710
"settings__adv__monitored_address_types" = "Überwachte Adress-Typen";
711-
"settings__adv__monitored_address_types_update_title" = "Überwachte Adress-Typen aktualisiert";
712-
"settings__adv__monitored_address_types_update_description" = "Änderungen werden nach dem Neustart der App vollständig wirksam.";
713711
"settings__adv__gap_limit" = "Address Gap Limit";
714712
"settings__adv__coin_selection" = "Coin-Auswahl";
715713
"settings__adv__cs_method" = "Coin-Auswahlmethode";

0 commit comments

Comments
 (0)