Skip to content

Commit bc0a485

Browse files
authored
feat: bip84 support
1 parent 2cdb4bd commit bc0a485

File tree

9 files changed

+212
-19
lines changed

9 files changed

+212
-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: 106 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,83 @@ private class BDKService {
5656
self.esploraClient = EsploraClient(url: self.esploraURL)
5757
}
5858

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

220295
let descriptorPublicKey = try DescriptorPublicKey.fromString(publicKey: xpubString)
221296
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(
297+
let currentAddressType = getCurrentAddressType()
298+
let descriptors = createPublicDescriptors(
299+
for: currentAddressType,
229300
publicKey: descriptorPublicKey,
230301
fingerprint: fingerprint,
231-
keychainKind: .internal,
232302
network: network
233303
)
304+
let descriptor = descriptors.descriptor
305+
let changeDescriptor = descriptors.changeDescriptor
234306

235307
let backupInfo = BackupInfo(
236308
mnemonic: "",
@@ -454,6 +526,14 @@ extension BDKService {
454526
func setNeedsFullScan(_ value: Bool) {
455527
needsFullScan = value
456528
}
529+
530+
func getAddressType() -> AddressType {
531+
return getCurrentAddressType()
532+
}
533+
534+
func updateAddressType(_ newAddressType: AddressType) {
535+
try? keyClient.saveAddressType(newAddressType.description)
536+
}
457537
}
458538

459539
struct BDKClient {
@@ -481,6 +561,8 @@ struct BDKClient {
481561
let getEsploraURL: () -> String
482562
let updateNetwork: (Network) -> Void
483563
let updateEsploraURL: (String) -> Void
564+
let getAddressType: () -> AddressType
565+
let updateAddressType: (AddressType) -> Void
484566
}
485567

486568
extension BDKClient {
@@ -534,6 +616,12 @@ extension BDKClient {
534616
},
535617
updateEsploraURL: { newURL in
536618
BDKService.shared.updateEsploraURL(newURL)
619+
},
620+
getAddressType: {
621+
BDKService.shared.getAddressType()
622+
},
623+
updateAddressType: { newAddressType in
624+
BDKService.shared.updateAddressType(newAddressType)
537625
}
538626
)
539627
}
@@ -591,7 +679,9 @@ extension BDKClient {
591679
getNetwork: { .signet },
592680
getEsploraURL: { Constants.Config.EsploraServerURLNetwork.Signet.mutiny },
593681
updateNetwork: { _ in },
594-
updateEsploraURL: { _ in }
682+
updateEsploraURL: { _ in },
683+
getAddressType: { .bip86 },
684+
updateAddressType: { _ in }
595685
)
596686
}
597687
#endif

BDKSwiftExampleWallet/Service/Key Service/KeyService.swift

Lines changed: 22 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,12 @@ 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)
128+
}
112129
)
113130
}
114131

@@ -146,9 +163,11 @@ extension KeyClient {
146163
},
147164
getEsploraURL: { nil },
148165
getNetwork: { nil },
166+
getAddressType: { nil },
149167
saveBackupInfo: { _ in },
150168
saveEsploraURL: { _ in },
151-
saveNetwork: { _ in }
169+
saveNetwork: { _ in },
170+
saveAddressType: { _ in }
152171
)
153172
}
154173
#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)