1+ use crate :: BdkElectrumClient ;
2+ use bdk_chain:: { keychain_txout:: KeychainTxOutIndex , tx_graph:: TxGraph } ;
3+ use bdk_core:: {
4+ collections:: BTreeMap ,
5+ spk_client:: { FullScanRequest , SyncRequest } ,
6+ CheckPoint ,
7+ } ;
8+ use electrum_client:: Client ;
9+
10+ /// The result of an Electrum sync operation.
11+ ///
12+ /// Contains:
13+ /// - `chain_update`: Optional checkpoint update for the blockchain state
14+ /// - `tx_update`: Transaction graph updates (new transactions, anchors, etc.)
15+ /// - `keychain_update`: Optional updates to keychain indices (only in full scan mode)
16+ pub type ElectrumSyncResult < K > = ( Option < CheckPoint > , TxGraph , Option < BTreeMap < K , u32 > > ) ;
17+
18+ /// A builder for synchronizing a wallet with an Electrum server.
19+ ///
20+ /// This struct provides a fluent interface for configuring and executing
21+ /// wallet synchronization with an Electrum server. It supports both fast
22+ /// sync (checking only revealed addresses) and full scan (scanning until
23+ /// stop gap is reached) modes.
24+ ///
25+ /// # Type Parameters
26+ ///
27+ /// * `K` - The keychain identifier type, must be `Ord + Clone + Send + Sync + Debug`
28+ pub struct ElectrumSync < ' a , K > {
29+ wallet : & ' a mut KeychainTxOutIndex < K > ,
30+ url : Option < String > ,
31+ stop_gap : usize ,
32+ batch_size : usize ,
33+ fast : bool ,
34+ fetch_prev : bool ,
35+ }
36+
37+ impl < ' a , K > ElectrumSync < ' a , K >
38+ where
39+ K : Ord + Clone + Send + Sync + core:: fmt:: Debug + ' static ,
40+ {
41+ /// Create a new `ElectrumSync` builder for the given wallet.
42+ pub fn new ( wallet : & ' a mut KeychainTxOutIndex < K > ) -> Self {
43+ Self {
44+ wallet,
45+ url : None ,
46+ stop_gap : 25 ,
47+ batch_size : 30 ,
48+ fast : false ,
49+ fetch_prev : false ,
50+ }
51+ }
52+
53+ /// Set the Electrum server URL.
54+ ///
55+ /// If not set, defaults to `"tcp://electrum.blockstream.info:50001"`
56+ pub fn url ( mut self , url : & str ) -> Self {
57+ self . url = Some ( url. to_string ( ) ) ;
58+ self
59+ }
60+
61+ /// Set the stop gap for full scans.
62+ pub fn stop_gap ( mut self , sg : usize ) -> Self {
63+ self . stop_gap = sg;
64+ self
65+ }
66+
67+ /// Set the batch size for Electrum requests.
68+ ///
69+ /// This controls how many script pubkeys are requested in a single batch call to the
70+ /// Electrum server. Defaults to 30.
71+ pub fn batch_size ( mut self , bs : usize ) -> Self {
72+ self . batch_size = bs;
73+ self
74+ }
75+
76+ /// Enable fast sync mode.
77+ ///
78+ /// Fast sync only checks already revealed script pubkeys, while full scan (the default)
79+ /// scans all possible addresses until the stop gap is reached.
80+ pub fn fast_sync ( mut self ) -> Self {
81+ self . fast = true ;
82+ self
83+ }
84+
85+ /// Enable fetching previous transaction outputs for fee calculation.
86+ ///
87+ /// calculating fees on transactions where your wallet doesn't own the inputs.
88+ pub fn fetch_prev_txouts ( mut self ) -> Self {
89+ self . fetch_prev = true ;
90+ self
91+ }
92+
93+ /// Execute the sync request and return the updates.
94+ pub fn request ( self ) -> Result < ElectrumSyncResult < K > , electrum_client:: Error > {
95+ let url = self
96+ . url
97+ . as_deref ( )
98+ . unwrap_or ( "tcp://electrum.blockstream.info:50001" ) ;
99+
100+ let electrum = Client :: new ( url) ?;
101+ let bdk_client = BdkElectrumClient :: new ( electrum) ;
102+
103+ if self . fast {
104+ let sync_request = self . build_sync_request ( ) ;
105+ let sync_response = bdk_client. sync ( sync_request, self . batch_size , self . fetch_prev ) ?;
106+
107+ Ok ( (
108+ sync_response. chain_update ,
109+ sync_response. tx_update . into ( ) ,
110+ None ,
111+ ) )
112+ } else {
113+ let full_scan_request = self . build_full_scan_request ( ) ;
114+ let full_scan_response = bdk_client. full_scan (
115+ full_scan_request,
116+ self . stop_gap ,
117+ self . batch_size ,
118+ self . fetch_prev ,
119+ ) ?;
120+
121+ Ok ( (
122+ full_scan_response. chain_update ,
123+ full_scan_response. tx_update . into ( ) ,
124+ Some ( full_scan_response. last_active_indices ) ,
125+ ) )
126+ }
127+ }
128+
129+ fn build_sync_request ( & self ) -> SyncRequest < ( K , u32 ) > {
130+ let mut builder = SyncRequest :: builder ( ) ;
131+
132+ // Add revealed scripts from the wallet
133+ for keychain_id in self . wallet . keychains ( ) . map ( |( k, _) | k) {
134+ for ( index, spk) in self . wallet . revealed_keychain_spks ( keychain_id. clone ( ) ) {
135+ builder = builder. spks_with_indexes ( [ ( ( keychain_id. clone ( ) , index) , spk) ] ) ;
136+ }
137+ }
138+
139+ builder. build ( )
140+ }
141+
142+ fn build_full_scan_request ( & self ) -> FullScanRequest < K > {
143+ let mut builder = FullScanRequest :: builder ( ) ;
144+
145+ // Add unbounded script iterators for each keychain
146+ for ( keychain_id, spk_iter) in self . wallet . all_unbounded_spk_iters ( ) {
147+ builder = builder. spks_for_keychain ( keychain_id, spk_iter) ;
148+ }
149+
150+ builder. build ( )
151+ }
152+ }
0 commit comments