8
8
import BitcoinDevKit
9
9
import Foundation
10
10
11
+ enum BlockchainClientType : String , CaseIterable {
12
+ case esplora = " esplora "
13
+ case kyoto = " kyoto " // future
14
+ case electrum = " electrum " // future
15
+ }
16
+
17
+ struct BlockchainClient {
18
+ let sync : @Sendable ( SyncRequest, UInt64) throws -> Update
19
+ let fullScan : @Sendable ( FullScanRequest, UInt64, UInt64) throws -> Update
20
+ let broadcast : @Sendable ( Transaction) throws -> Void
21
+ let getUrl : @Sendable ( ) -> String
22
+ let getType : @Sendable ( ) -> BlockchainClientType
23
+ let supportsFullScan : @Sendable ( ) -> Bool = { true }
24
+ }
25
+
26
+ extension BlockchainClient {
27
+ static func esplora( url: String ) -> Self {
28
+ let client = EsploraClient ( url: url)
29
+ return Self (
30
+ sync: { request, parallel in
31
+ try client. sync ( request: request, parallelRequests: parallel)
32
+ } ,
33
+ fullScan: { request, stopGap, parallel in
34
+ try client. fullScan ( request: request, stopGap: stopGap, parallelRequests: parallel)
35
+ } ,
36
+ broadcast: { tx in
37
+ try client. broadcast ( transaction: tx)
38
+ } ,
39
+ getUrl: { url } ,
40
+ getType: { . esplora }
41
+ )
42
+ }
43
+ }
44
+
11
45
private class BDKService {
12
46
static var shared : BDKService = BDKService ( )
13
47
14
48
private var balance : Balance ?
15
49
private var persister : Persister ?
16
- private var esploraClient : EsploraClient
50
+ private var blockchainClient : BlockchainClient
51
+ internal private( set) var clientType : BlockchainClientType
17
52
private let keyClient : KeyClient
18
53
private var needsFullScan : Bool = false
19
54
private( set) var network : Network
20
- private( set ) var esploraURL : String
55
+ private var blockchainURL : String
21
56
private var wallet : Wallet ?
22
57
23
58
init ( keyClient: KeyClient = . live) {
24
59
self . keyClient = keyClient
25
60
let storedNetworkString = try ? keyClient. getNetwork ( ) ?? Network . signet. description
26
61
self . network = Network ( stringValue: storedNetworkString ?? " " ) ?? . signet
27
62
28
- self . esploraURL = ( try ? keyClient. getEsploraURL ( ) ) ?? self . network. url
29
-
30
- self . esploraClient = EsploraClient ( url: self . esploraURL)
63
+ let storedClientType = try ? keyClient. getClientType ( )
64
+ self . clientType = storedClientType ?? . esplora
65
+
66
+ self . blockchainURL = ( try ? keyClient. getEsploraURL ( ) ) ?? self . network. url
67
+ self . blockchainClient = BlockchainClient . esplora ( url: self . blockchainURL)
68
+ updateBlockchainClient ( )
31
69
}
32
70
33
71
func updateNetwork( _ newNetwork: Network ) {
@@ -40,20 +78,33 @@ private class BDKService {
40
78
try ? keyClient. saveNetwork ( newNetwork. description)
41
79
42
80
let newURL = newNetwork. url
43
- updateEsploraURL ( newURL)
81
+ updateBlockchainURL ( newURL)
44
82
}
45
83
}
46
84
47
- func updateEsploraURL ( _ newURL: String ) {
48
- if newURL != self . esploraURL {
49
- self . esploraURL = newURL
50
- try ? keyClient. saveEsploraURL ( newURL)
51
- updateEsploraClient ( )
85
+ func updateBlockchainURL ( _ newURL: String ) {
86
+ if newURL != self . blockchainURL {
87
+ self . blockchainURL = newURL
88
+ try ? keyClient. saveEsploraURL ( newURL) // TODO: Future - saveURL(newURL, for: clientType)
89
+ updateBlockchainClient ( )
52
90
}
53
91
}
54
92
55
- private func updateEsploraClient( ) {
56
- self . esploraClient = EsploraClient ( url: self . esploraURL)
93
+ internal func updateBlockchainClient( ) {
94
+ do {
95
+ switch clientType {
96
+ case . esplora:
97
+ self . blockchainClient = . esplora( url: self . blockchainURL)
98
+ case . kyoto:
99
+ throw WalletError . backendNotImplemented
100
+ case . electrum:
101
+ throw WalletError . backendNotImplemented
102
+ }
103
+ } catch {
104
+ // Fallback to esplora if selected backend not implemented
105
+ self . clientType = . esplora
106
+ self . blockchainClient = . esplora( url: self . blockchainURL)
107
+ }
57
108
}
58
109
59
110
private func getCurrentAddressType( ) -> AddressType {
@@ -211,8 +262,8 @@ private class BDKService {
211
262
try keyClient. saveBackupInfo ( backupInfo)
212
263
try keyClient. saveNetwork ( self . network. description)
213
264
try keyClient. saveEsploraURL ( baseUrl)
214
- self . esploraURL = baseUrl
215
- updateEsploraClient ( )
265
+ self . blockchainURL = baseUrl
266
+ updateBlockchainClient ( )
216
267
217
268
let wallet = try Wallet (
218
269
descriptor: descriptor,
@@ -313,8 +364,8 @@ private class BDKService {
313
364
try keyClient. saveBackupInfo ( backupInfo)
314
365
try keyClient. saveNetwork ( self . network. description)
315
366
try keyClient. saveEsploraURL ( baseUrl)
316
- self . esploraURL = baseUrl
317
- updateEsploraClient ( )
367
+ self . blockchainURL = baseUrl
368
+ updateBlockchainClient ( )
318
369
319
370
let wallet = try Wallet (
320
371
descriptor: descriptor,
@@ -441,22 +492,20 @@ private class BDKService {
441
492
let isSigned = try wallet. sign ( psbt: psbt)
442
493
if isSigned {
443
494
let transaction = try psbt. extractTx ( )
444
- let client = self . esploraClient
445
- try client. broadcast ( transaction: transaction)
495
+ try self . blockchainClient. broadcast ( transaction)
446
496
} else {
447
497
throw WalletError . notSigned
448
498
}
449
499
}
450
500
451
501
func syncWithInspector( inspector: SyncScriptInspector ) async throws {
452
502
guard let wallet = self . wallet else { throw WalletError . walletNotFound }
453
- let esploraClient = self . esploraClient
454
503
let syncRequest = try wallet. startSyncWithRevealedSpks ( )
455
504
. inspectSpks ( inspector: inspector)
456
505
. build ( )
457
- let update = try esploraClient . sync (
458
- request : syncRequest,
459
- parallelRequests : UInt64 ( 5 )
506
+ let update = try self . blockchainClient . sync (
507
+ syncRequest,
508
+ UInt64 ( 5 )
460
509
)
461
510
let _ = try wallet. applyUpdate ( update: update)
462
511
guard let persister = self . persister else {
@@ -467,16 +516,18 @@ private class BDKService {
467
516
468
517
func fullScanWithInspector( inspector: FullScanScriptInspector ) async throws {
469
518
guard let wallet = self . wallet else { throw WalletError . walletNotFound }
470
- let esploraClient = esploraClient
519
+ guard self . blockchainClient. supportsFullScan ( ) else {
520
+ throw WalletError . fullScanUnsupported
521
+ }
471
522
let fullScanRequest = try wallet. startFullScan ( )
472
523
. inspectSpksForAllKeychains ( inspector: inspector)
473
524
. build ( )
474
- let update = try esploraClient . fullScan (
475
- request : fullScanRequest,
525
+ let update = try self . blockchainClient . fullScan (
526
+ fullScanRequest,
476
527
// using https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#address-gap-limit
477
- stopGap : UInt64 ( 20 ) ,
528
+ UInt64 ( 20 ) ,
478
529
// using https://github.com/bitcoindevkit/bdk/blob/master/example-crates/example_wallet_esplora_blocking/src/main.rs
479
- parallelRequests : UInt64 ( 5 )
530
+ UInt64 ( 5 )
480
531
)
481
532
let _ = try wallet. applyUpdate ( update: update)
482
533
guard let persister = self . persister else {
@@ -534,6 +585,20 @@ extension BDKService {
534
585
func updateAddressType( _ newAddressType: AddressType ) {
535
586
try ? keyClient. saveAddressType ( newAddressType. description)
536
587
}
588
+
589
+ func updateClientType( _ newType: BlockchainClientType ) {
590
+ self . clientType = newType
591
+ try ? keyClient. saveClientType ( newType)
592
+ updateBlockchainClient ( )
593
+ }
594
+
595
+ var esploraURL : String {
596
+ return blockchainURL
597
+ }
598
+
599
+ func updateEsploraURL( _ newURL: String ) {
600
+ updateBlockchainURL ( newURL)
601
+ }
537
602
}
538
603
539
604
struct BDKClient {
@@ -563,6 +628,8 @@ struct BDKClient {
563
628
let updateEsploraURL : ( String ) -> Void
564
629
let getAddressType : ( ) -> AddressType
565
630
let updateAddressType : ( AddressType ) -> Void
631
+ let getClientType : ( ) -> BlockchainClientType
632
+ let updateClientType : ( BlockchainClientType ) -> Void
566
633
}
567
634
568
635
extension BDKClient {
@@ -622,6 +689,10 @@ extension BDKClient {
622
689
} ,
623
690
updateAddressType: { newAddressType in
624
691
BDKService . shared. updateAddressType ( newAddressType)
692
+ } ,
693
+ getClientType: { BDKService . shared. clientType } ,
694
+ updateClientType: { newType in
695
+ BDKService . shared. updateClientType ( newType)
625
696
}
626
697
)
627
698
}
@@ -681,7 +752,9 @@ extension BDKClient {
681
752
updateNetwork: { _ in } ,
682
753
updateEsploraURL: { _ in } ,
683
754
getAddressType: { . bip86 } ,
684
- updateAddressType: { _ in }
755
+ updateAddressType: { _ in } ,
756
+ getClientType: { . esplora } ,
757
+ updateClientType: { _ in }
685
758
)
686
759
}
687
760
#endif
0 commit comments