Skip to content

Commit e6009c6

Browse files
committed
feat: bip84 support
1 parent 2cdb4bd commit e6009c6

File tree

9 files changed

+210
-19
lines changed

9 files changed

+210
-19
lines changed

BDKSwiftExampleWallet.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
AE91CEED2C0FDB70000AAD20 /* SentAndReceivedValues+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE91CEEC2C0FDB70000AAD20 /* SentAndReceivedValues+Extensions.swift */; };
8282
AE91CEEF2C0FDBC7000AAD20 /* CanonicalTx+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE91CEEE2C0FDBC7000AAD20 /* CanonicalTx+Extensions.swift */; };
8383
AE96F6622A424C400055623C /* BDKSwiftExampleWalletReceiveViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE96F6612A424C400055623C /* BDKSwiftExampleWalletReceiveViewModelTests.swift */; };
84+
AE97E74D2E315A8F000A407D /* AddressType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE97E74C2E315A8F000A407D /* AddressType+Extensions.swift */; };
8485
AEA0A6272E297203008A525B /* BitcoinDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = AEA0A6262E297203008A525B /* BitcoinDevKit */; };
8586
AEAB03112ABDDB86000C9528 /* FeeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEAB03102ABDDB86000C9528 /* FeeViewModel.swift */; };
8687
AEAB03132ABDDBF4000C9528 /* AmountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEAB03122ABDDBF4000C9528 /* AmountViewModel.swift */; };
@@ -187,6 +188,7 @@
187188
AE91CEEC2C0FDB70000AAD20 /* SentAndReceivedValues+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentAndReceivedValues+Extensions.swift"; sourceTree = "<group>"; };
188189
AE91CEEE2C0FDBC7000AAD20 /* CanonicalTx+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CanonicalTx+Extensions.swift"; sourceTree = "<group>"; };
189190
AE96F6612A424C400055623C /* BDKSwiftExampleWalletReceiveViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BDKSwiftExampleWalletReceiveViewModelTests.swift; sourceTree = "<group>"; };
191+
AE97E74C2E315A8F000A407D /* AddressType+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddressType+Extensions.swift"; sourceTree = "<group>"; };
190192
AEAB03102ABDDB86000C9528 /* FeeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeViewModel.swift; sourceTree = "<group>"; };
191193
AEAB03122ABDDBF4000C9528 /* AmountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountViewModel.swift; sourceTree = "<group>"; };
192194
AEB130C82A44E4850087785B /* TransactionDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailView.swift; sourceTree = "<group>"; };
@@ -567,6 +569,7 @@
567569
isa = PBXGroup;
568570
children = (
569571
77EDA65A2E2A5B3800A5E3AD /* URL+Extensions.swift */,
572+
AE97E74C2E315A8F000A407D /* AddressType+Extensions.swift */,
570573
77F0FDC82DA9A93700B30E4F /* Persister+Extensions.swift */,
571574
AEE6C74B2ABCB3E200442ADD /* Transaction+Extensions.swift */,
572575
AE83EFDA2C9D07B200B41244 /* ChainPosition+Extensions.swift */,
@@ -762,6 +765,7 @@
762765
AE783A012AB4E5E1005F0CBA /* BuildTransactionView.swift in Sources */,
763766
AE6F34DA2AA6C1E00087E700 /* Balance+Extensions.swift in Sources */,
764767
AED4CC0C2A1D3A9400CE1831 /* OnboardingView.swift in Sources */,
768+
AE97E74D2E315A8F000A407D /* AddressType+Extensions.swift in Sources */,
765769
77F0FDC92DA9A93D00B30E4F /* Persister+Extensions.swift in Sources */,
766770
AE6716012A9AC089005C193F /* KeyServiceError.swift in Sources */,
767771
77AD9F062DBB031D00182E65 /* ActivityHomeHeaderView.swift in Sources */,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// AddressType+Extensions.swift
3+
// BDKSwiftExampleWallet
4+
//
5+
// Created by Matthew Ramsden on 7/23/25.
6+
//
7+
8+
import Foundation
9+
10+
enum AddressType: String, CaseIterable {
11+
case bip86 = "bip86"
12+
case bip84 = "bip84"
13+
14+
var description: String {
15+
switch self {
16+
case .bip86: return "bip86"
17+
case .bip84: return "bip84"
18+
}
19+
}
20+
21+
var displayName: String {
22+
switch self {
23+
case .bip86: return "BIP86 (Taproot)"
24+
case .bip84: return "BIP84 (SegWit)"
25+
}
26+
}
27+
28+
init?(stringValue: String) {
29+
switch stringValue {
30+
case "bip86": self = .bip86
31+
case "bip84": self = .bip84
32+
default: return nil
33+
}
34+
}
35+
}

BDKSwiftExampleWallet/Resources/Localizable.xcstrings

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@
347347
}
348348
}
349349
}
350+
},
351+
"Address Type" : {
352+
350353
},
351354
"Amount Error" : {
352355
"localizations" : {
@@ -1003,6 +1006,9 @@
10031006
}
10041007
}
10051008
}
1009+
},
1010+
"Select Address Type" : {
1011+
10061012
},
10071013
"Select Bitcoin Network" : {
10081014
"localizations" : {
@@ -1313,6 +1319,9 @@
13131319
}
13141320
}
13151321
}
1322+
},
1323+
"Unknown" : {
1324+
13161325
},
13171326
"Unspent" : {
13181327
"localizations" : {

BDKSwiftExampleWallet/Service/BDK Service/BDKService.swift

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,82 @@ private class BDKService {
5656
self.esploraClient = EsploraClient(url: self.esploraURL)
5757
}
5858

59+
private func getCurrentAddressType() -> AddressType {
60+
let storedAddressTypeString = try? keyClient.getAddressType() ?? AddressType.bip86.description
61+
return AddressType(stringValue: storedAddressTypeString ?? "") ?? .bip86
62+
}
63+
64+
private func createDescriptors(
65+
for addressType: AddressType,
66+
secretKey: DescriptorSecretKey,
67+
network: Network
68+
) -> (descriptor: Descriptor, changeDescriptor: Descriptor) {
69+
switch addressType {
70+
case .bip86:
71+
let descriptor = Descriptor.newBip86(
72+
secretKey: secretKey,
73+
keychainKind: .external,
74+
network: network
75+
)
76+
let changeDescriptor = Descriptor.newBip86(
77+
secretKey: secretKey,
78+
keychainKind: .internal,
79+
network: network
80+
)
81+
return (descriptor, changeDescriptor)
82+
case .bip84:
83+
let descriptor = Descriptor.newBip84(
84+
secretKey: secretKey,
85+
keychainKind: .external,
86+
network: network
87+
)
88+
let changeDescriptor = Descriptor.newBip84(
89+
secretKey: secretKey,
90+
keychainKind: .internal,
91+
network: network
92+
)
93+
return (descriptor, changeDescriptor)
94+
}
95+
}
96+
97+
private func createPublicDescriptors(
98+
for addressType: AddressType,
99+
publicKey: DescriptorPublicKey,
100+
fingerprint: String,
101+
network: Network
102+
) -> (descriptor: Descriptor, changeDescriptor: Descriptor) {
103+
switch addressType {
104+
case .bip86:
105+
let descriptor = Descriptor.newBip86Public(
106+
publicKey: publicKey,
107+
fingerprint: fingerprint,
108+
keychainKind: .external,
109+
network: network
110+
)
111+
let changeDescriptor = Descriptor.newBip86Public(
112+
publicKey: publicKey,
113+
fingerprint: fingerprint,
114+
keychainKind: .internal,
115+
network: network
116+
)
117+
return (descriptor, changeDescriptor)
118+
case .bip84:
119+
let descriptor = Descriptor.newBip84Public(
120+
publicKey: publicKey,
121+
fingerprint: fingerprint,
122+
keychainKind: .external,
123+
network: network
124+
)
125+
let changeDescriptor = Descriptor.newBip84Public(
126+
publicKey: publicKey,
127+
fingerprint: fingerprint,
128+
keychainKind: .internal,
129+
network: network
130+
)
131+
return (descriptor, changeDescriptor)
132+
}
133+
}
134+
59135
func getAddress() throws -> String {
60136
guard let wallet = self.wallet else {
61137
throw WalletError.walletNotFound
@@ -117,16 +193,14 @@ private class BDKService {
117193
mnemonic: mnemonic,
118194
password: nil
119195
)
120-
let descriptor = Descriptor.newBip86(
196+
let currentAddressType = getCurrentAddressType()
197+
let descriptors = createDescriptors(
198+
for: currentAddressType,
121199
secretKey: secretKey,
122-
keychainKind: .external,
123-
network: network
124-
)
125-
let changeDescriptor = Descriptor.newBip86(
126-
secretKey: secretKey,
127-
keychainKind: .internal,
128200
network: network
129201
)
202+
let descriptor = descriptors.descriptor
203+
let changeDescriptor = descriptors.changeDescriptor
130204
let backupInfo = BackupInfo(
131205
mnemonic: mnemonic.description,
132206
descriptor: descriptor.toStringWithSecret(),
@@ -219,18 +293,15 @@ private class BDKService {
219293

220294
let descriptorPublicKey = try DescriptorPublicKey.fromString(publicKey: xpubString)
221295
let fingerprint = descriptorPublicKey.masterFingerprint()
222-
let descriptor = Descriptor.newBip86Public(
223-
publicKey: descriptorPublicKey,
224-
fingerprint: fingerprint,
225-
keychainKind: .external,
226-
network: network
227-
)
228-
let changeDescriptor = Descriptor.newBip86Public(
296+
let currentAddressType = getCurrentAddressType()
297+
let descriptors = createPublicDescriptors(
298+
for: currentAddressType,
229299
publicKey: descriptorPublicKey,
230300
fingerprint: fingerprint,
231-
keychainKind: .internal,
232301
network: network
233302
)
303+
let descriptor = descriptors.descriptor
304+
let changeDescriptor = descriptors.changeDescriptor
234305

235306
let backupInfo = BackupInfo(
236307
mnemonic: "",
@@ -454,6 +525,14 @@ extension BDKService {
454525
func setNeedsFullScan(_ value: Bool) {
455526
needsFullScan = value
456527
}
528+
529+
func getAddressType() -> AddressType {
530+
return getCurrentAddressType()
531+
}
532+
533+
func updateAddressType(_ newAddressType: AddressType) {
534+
try? keyClient.saveAddressType(newAddressType.description)
535+
}
457536
}
458537

459538
struct BDKClient {
@@ -481,6 +560,8 @@ struct BDKClient {
481560
let getEsploraURL: () -> String
482561
let updateNetwork: (Network) -> Void
483562
let updateEsploraURL: (String) -> Void
563+
let getAddressType: () -> AddressType
564+
let updateAddressType: (AddressType) -> Void
484565
}
485566

486567
extension BDKClient {
@@ -534,6 +615,12 @@ extension BDKClient {
534615
},
535616
updateEsploraURL: { newURL in
536617
BDKService.shared.updateEsploraURL(newURL)
618+
},
619+
getAddressType: {
620+
BDKService.shared.getAddressType()
621+
},
622+
updateAddressType: { newAddressType in
623+
BDKService.shared.updateAddressType(newAddressType)
537624
}
538625
)
539626
}
@@ -591,7 +678,9 @@ extension BDKClient {
591678
getNetwork: { .signet },
592679
getEsploraURL: { Constants.Config.EsploraServerURLNetwork.Signet.mutiny },
593680
updateNetwork: { _ in },
594-
updateEsploraURL: { _ in }
681+
updateEsploraURL: { _ in },
682+
getAddressType: { .bip86 },
683+
updateAddressType: { _ in }
595684
)
596685
}
597686
#endif

BDKSwiftExampleWallet/Service/Key Service/KeyService.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ private struct KeyService {
6262
func saveNetwork(network: String) throws {
6363
keychain[string: "SelectedNetwork"] = network
6464
}
65+
66+
func getAddressType() throws -> String? {
67+
return keychain[string: "SelectedAddressType"]
68+
}
69+
70+
func saveAddressType(addressType: String) throws {
71+
keychain[string: "SelectedAddressType"] = addressType
72+
}
6573
}
6674

6775
struct KeyClient {
@@ -71,9 +79,11 @@ struct KeyClient {
7179
let getBackupInfo: () throws -> BackupInfo
7280
let getEsploraURL: () throws -> String?
7381
let getNetwork: () throws -> String?
82+
let getAddressType: () throws -> String?
7483
let saveEsploraURL: (String) throws -> Void
7584
let saveBackupInfo: (BackupInfo) throws -> Void
7685
let saveNetwork: (String) throws -> Void
86+
let saveAddressType: (String) throws -> Void
7787

7888
private init(
7989
deleteBackupInfo: @escaping () throws -> Void,
@@ -82,19 +92,23 @@ struct KeyClient {
8292
getBackupInfo: @escaping () throws -> BackupInfo,
8393
getEsploraURL: @escaping () throws -> String?,
8494
getNetwork: @escaping () throws -> String?,
95+
getAddressType: @escaping () throws -> String?,
8596
saveBackupInfo: @escaping (BackupInfo) throws -> Void,
8697
saveEsploraURL: @escaping (String) throws -> Void,
87-
saveNetwork: @escaping (String) throws -> Void
98+
saveNetwork: @escaping (String) throws -> Void,
99+
saveAddressType: @escaping (String) throws -> Void
88100
) {
89101
self.deleteBackupInfo = deleteBackupInfo
90102
self.deleteEsplora = deleteEsplora
91103
self.deleteNetwork = deleteNetwork
92104
self.getBackupInfo = getBackupInfo
93105
self.getEsploraURL = getEsploraURL
94106
self.getNetwork = getNetwork
107+
self.getAddressType = getAddressType
95108
self.saveBackupInfo = saveBackupInfo
96109
self.saveEsploraURL = saveEsploraURL
97110
self.saveNetwork = saveNetwork
111+
self.saveAddressType = saveAddressType
98112
}
99113
}
100114

@@ -106,9 +120,11 @@ extension KeyClient {
106120
getBackupInfo: { try KeyService().getBackupInfo() },
107121
getEsploraURL: { try KeyService().getEsploraURL() },
108122
getNetwork: { try KeyService().getNetwork() },
123+
getAddressType: { try KeyService().getAddressType() },
109124
saveBackupInfo: { backupInfo in try KeyService().saveBackupInfo(backupInfo: backupInfo) },
110125
saveEsploraURL: { url in try KeyService().saveEsploraURL(url: url) },
111-
saveNetwork: { network in try KeyService().saveNetwork(network: network) }
126+
saveNetwork: { network in try KeyService().saveNetwork(network: network) },
127+
saveAddressType: { addressType in try KeyService().saveAddressType(addressType: addressType) }
112128
)
113129
}
114130

@@ -146,9 +162,11 @@ extension KeyClient {
146162
},
147163
getEsploraURL: { nil },
148164
getNetwork: { nil },
165+
getAddressType: { nil },
149166
saveBackupInfo: { _ in },
150167
saveEsploraURL: { _ in },
151-
saveNetwork: { _ in }
168+
saveNetwork: { _ in },
169+
saveAddressType: { _ in }
152170
)
153171
}
154172
#endif

BDKSwiftExampleWallet/View Model/OnboardingViewModel.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ class OnboardingViewModel: ObservableObject {
3838
bdkClient.updateEsploraURL(selectedURL)
3939
}
4040
}
41+
@Published var selectedAddressType: AddressType = .bip86 {
42+
didSet {
43+
bdkClient.updateAddressType(selectedAddressType)
44+
}
45+
}
4146
@Published var words: String = ""
4247
var wordArray: [String] {
4348
if words.hasPrefix("xpub") || words.hasPrefix("tpub") || words.hasPrefix("vpub") {
@@ -81,6 +86,7 @@ class OnboardingViewModel: ObservableObject {
8186
self.bdkClient = bdkClient
8287
self.selectedNetwork = bdkClient.getNetwork()
8388
self.selectedURL = bdkClient.getEsploraURL()
89+
self.selectedAddressType = bdkClient.getAddressType()
8490
}
8591

8692
func createWallet() {

BDKSwiftExampleWallet/View Model/Settings/SettingsViewModel.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class SettingsViewModel: ObservableObject {
1717
@Published var esploraURL: String?
1818
@Published var inspectedScripts: UInt64 = 0
1919
@Published var network: String?
20+
@Published var addressType: AddressType?
2021
@Published var settingsError: AppError?
2122
@Published var showingSettingsViewErrorAlert = false
2223
@Published var walletSyncState: WalletSyncState = .notStarted
@@ -37,6 +38,10 @@ class SettingsViewModel: ObservableObject {
3738
self.esploraURL = bdkClient.getEsploraURL()
3839
}
3940

41+
func getAddressType() {
42+
self.addressType = bdkClient.getAddressType()
43+
}
44+
4045
func delete() {
4146
do {
4247
try bdkClient.deleteWallet()

0 commit comments

Comments
 (0)