1+ //! Simple one-liner Electrum sync helper
2+ //!
3+ //! This provides a clean API for synchronizing wallets with Electrum servers
4+ //! while preserving cache and respecting BDK's architectural boundaries.
5+
6+ use bdk_chain:: { keychain_txout:: KeychainTxOutIndex , tx_graph:: TxGraph } ;
7+ use bdk_core:: {
8+ collections:: BTreeMap ,
9+ spk_client:: { FullScanRequest , SyncRequest } ,
10+ CheckPoint ,
11+ } ;
12+ use bdk_electrum:: BdkElectrumClient ;
13+ use electrum_client:: Client ;
14+
15+ /// Result type for Electrum synchronization
16+ pub type ElectrumSyncResult < K > = ( Option < CheckPoint > , TxGraph , Option < BTreeMap < K , u32 > > ) ;
17+
18+ /// Simple configuration for Electrum synchronization
19+ #[ derive( Debug , Clone , Copy ) ]
20+ pub struct SyncOptions {
21+ pub fast : bool ,
22+ pub stop_gap : usize ,
23+ pub batch_size : usize ,
24+ pub fetch_prev : bool ,
25+ }
26+
27+ impl Default for SyncOptions {
28+ fn default ( ) -> Self {
29+ Self {
30+ fast : false ,
31+ stop_gap : 25 ,
32+ batch_size : 30 ,
33+ fetch_prev : false ,
34+ }
35+ }
36+ }
37+
38+ impl SyncOptions {
39+ /// Create options for fast sync
40+ pub fn fast_sync ( ) -> Self {
41+ Self {
42+ fast : true ,
43+ ..Default :: default ( )
44+ }
45+ }
46+
47+ /// Create options for full scan
48+ pub fn full_scan ( ) -> Self {
49+ Self :: default ( )
50+ }
51+
52+ pub fn with_stop_gap ( mut self , stop_gap : usize ) -> Self {
53+ self . stop_gap = stop_gap;
54+ self
55+ }
56+
57+ pub fn with_batch_size ( mut self , batch_size : usize ) -> Self {
58+ self . batch_size = batch_size;
59+ self
60+ }
61+
62+ pub fn with_fetch_prev ( mut self , fetch_prev : bool ) -> Self {
63+ self . fetch_prev = fetch_prev;
64+ self
65+ }
66+ }
67+
68+ /// Long-lived Electrum sync manager that preserves cache across operations
69+ ///
70+ /// This struct holds a persistent connection to an Electrum server, maintaining
71+ /// transaction and header caches between sync operations for better performance.
72+ pub struct ElectrumSyncManager {
73+ client : BdkElectrumClient < Client > ,
74+ }
75+
76+ impl ElectrumSyncManager {
77+ /// Create a new sync manager with the given Electrum server URL
78+ pub fn new ( url : & str ) -> Result < Self , electrum_client:: Error > {
79+ let client = Client :: new ( url) ?;
80+ Ok ( Self {
81+ client : BdkElectrumClient :: new ( client) ,
82+ } )
83+ }
84+
85+ /// One-liner synchronization - the main convenience method
86+ ///
87+ /// # Example
88+ /// ```text
89+ /// let manager = ElectrumSyncManager::new("tcp://electrum.example.com:50001")?;
90+ /// let result = manager.sync(&wallet, SyncOptions::full_scan())?;
91+ /// ```
92+ pub fn sync < K > (
93+ & self ,
94+ wallet : & KeychainTxOutIndex < K > ,
95+ options : SyncOptions ,
96+ ) -> Result < ElectrumSyncResult < K > , electrum_client:: Error >
97+ where
98+ K : Ord + Clone + Send + Sync + std:: fmt:: Debug + ' static ,
99+ {
100+ if options. fast {
101+ self . fast_sync ( wallet, options. batch_size , options. fetch_prev )
102+ } else {
103+ self . full_scan ( wallet, options. stop_gap , options. batch_size , options. fetch_prev )
104+ }
105+ }
106+
107+ /// One-liner fast sync (only checks revealed addresses)
108+ pub fn fast_sync < K > (
109+ & self ,
110+ wallet : & KeychainTxOutIndex < K > ,
111+ batch_size : usize ,
112+ fetch_prev : bool ,
113+ ) -> Result < ElectrumSyncResult < K > , electrum_client:: Error >
114+ where
115+ K : Ord + Clone + Send + Sync + std:: fmt:: Debug + ' static ,
116+ {
117+ let request = Self :: build_sync_request ( wallet) ;
118+ let response = self . client . sync ( request, batch_size, fetch_prev) ?;
119+
120+ Ok ( (
121+ response. chain_update ,
122+ response. tx_update . into ( ) ,
123+ None ,
124+ ) )
125+ }
126+
127+ /// One-liner full scan (scans until stop gap is reached)
128+ pub fn full_scan < K > (
129+ & self ,
130+ wallet : & KeychainTxOutIndex < K > ,
131+ stop_gap : usize ,
132+ batch_size : usize ,
133+ fetch_prev : bool ,
134+ ) -> Result < ElectrumSyncResult < K > , electrum_client:: Error >
135+ where
136+ K : Ord + Clone + Send + Sync + std:: fmt:: Debug + ' static ,
137+ {
138+ let request = Self :: build_full_scan_request ( wallet) ;
139+ let response = self . client . full_scan (
140+ request,
141+ stop_gap,
142+ batch_size,
143+ fetch_prev,
144+ ) ?;
145+
146+ Ok ( (
147+ response. chain_update ,
148+ response. tx_update . into ( ) ,
149+ Some ( response. last_active_indices ) ,
150+ ) )
151+ }
152+
153+ /// Get a reference to the underlying client for advanced operations
154+ pub fn client ( & self ) -> & BdkElectrumClient < Client > {
155+ & self . client
156+ }
157+
158+ /// Build a sync request based on revealed addresses.
159+ fn build_sync_request < K > ( wallet : & KeychainTxOutIndex < K > ) -> SyncRequest < ( K , u32 ) >
160+ where
161+ K : Ord + Clone + Send + Sync + std:: fmt:: Debug + ' static ,
162+ {
163+ let mut builder = SyncRequest :: builder ( ) ;
164+
165+ for keychain_id in wallet. keychains ( ) . map ( |( k, _) | k) {
166+ for ( index, spk) in wallet. revealed_keychain_spks ( keychain_id. clone ( ) ) {
167+ builder = builder. spks_with_indexes ( [ ( ( keychain_id. clone ( ) , index) , spk) ] ) ;
168+ }
169+ }
170+
171+ builder. build ( )
172+ }
173+
174+ /// Build a full scan request using unbounded SPK iterators.
175+ fn build_full_scan_request < K > ( wallet : & KeychainTxOutIndex < K > ) -> FullScanRequest < K >
176+ where
177+ K : Ord + Clone + Send + Sync + std:: fmt:: Debug + ' static ,
178+ {
179+ let mut builder = FullScanRequest :: builder ( ) ;
180+
181+ // Add unbounded script iterators for each keychain
182+ for ( keychain_id, spk_iter) in wallet. all_unbounded_spk_iters ( ) {
183+ builder = builder. spks_for_keychain ( keychain_id, spk_iter) ;
184+ }
185+
186+ builder. build ( )
187+ }
188+ }
0 commit comments