iota_sdk/wallet/core/
builder.rs1use std::sync::{
5 atomic::{AtomicU32, AtomicUsize},
6 Arc,
7};
8#[cfg(feature = "storage")]
9use std::{collections::HashSet, sync::atomic::Ordering};
10
11use futures::{future::try_join_all, FutureExt};
12use serde::Serialize;
13use tokio::sync::RwLock;
14
15use super::operations::storage::SaveLoadWallet;
16#[cfg(feature = "events")]
17use crate::wallet::events::EventEmitter;
18#[cfg(all(feature = "storage", not(feature = "rocksdb")))]
19use crate::wallet::storage::adapter::memory::Memory;
20#[cfg(feature = "storage")]
21use crate::wallet::{
22 account::AccountDetails,
23 storage::{StorageManager, StorageOptions},
24};
25use crate::{
26 client::secret::{SecretManage, SecretManager},
27 wallet::{core::WalletInner, Account, ClientOptions, Wallet},
28};
29
30#[derive(Debug, Serialize)]
32#[serde(rename_all = "camelCase")]
33pub struct WalletBuilder<S: SecretManage = SecretManager> {
34 pub(crate) client_options: Option<ClientOptions>,
35 pub(crate) coin_type: Option<u32>,
36 #[cfg(feature = "storage")]
37 pub(crate) storage_options: Option<StorageOptions>,
38 #[serde(skip)]
39 pub(crate) secret_manager: Option<Arc<RwLock<S>>>,
40}
41
42impl<S: SecretManage> Default for WalletBuilder<S> {
43 fn default() -> Self {
44 Self {
45 client_options: Default::default(),
46 coin_type: Default::default(),
47 #[cfg(feature = "storage")]
48 storage_options: Default::default(),
49 secret_manager: Default::default(),
50 }
51 }
52}
53
54impl<S: 'static + SecretManage> WalletBuilder<S>
55where
56 crate::wallet::Error: From<S::Error>,
57{
58 pub fn new() -> Self {
60 Self {
61 secret_manager: None,
62 ..Default::default()
63 }
64 }
65
66 pub fn with_client_options(mut self, client_options: impl Into<Option<ClientOptions>>) -> Self {
68 self.client_options = client_options.into();
69 self
70 }
71
72 pub fn with_coin_type(mut self, coin_type: impl Into<Option<u32>>) -> Self {
74 self.coin_type = coin_type.into();
75 self
76 }
77
78 #[cfg(feature = "storage")]
80 #[cfg_attr(docsrs, doc(cfg(feature = "storage")))]
81 pub fn with_storage_options(mut self, storage_options: impl Into<Option<StorageOptions>>) -> Self {
82 self.storage_options = storage_options.into();
83 self
84 }
85
86 pub fn with_secret_manager(mut self, secret_manager: impl Into<Option<S>>) -> Self {
88 self.secret_manager = secret_manager.into().map(|sm| Arc::new(RwLock::new(sm)));
89 self
90 }
91
92 pub fn with_secret_manager_arc(mut self, secret_manager: impl Into<Option<Arc<RwLock<S>>>>) -> Self {
95 self.secret_manager = secret_manager.into();
96 self
97 }
98
99 #[cfg(feature = "storage")]
101 #[cfg_attr(docsrs, doc(cfg(feature = "storage")))]
102 pub fn with_storage_path(mut self, path: impl Into<std::path::PathBuf>) -> Self {
103 self.storage_options = Some(StorageOptions {
104 path: path.into(),
105 ..Default::default()
106 });
107 self
108 }
109}
110
111impl<S: 'static + SecretManage> WalletBuilder<S>
112where
113 crate::wallet::Error: From<S::Error>,
114 Self: SaveLoadWallet,
115{
116 pub async fn finish(mut self) -> crate::wallet::Result<Wallet<S>> {
118 log::debug!("[WalletBuilder]");
119
120 #[cfg(feature = "storage")]
121 let storage_options = self.storage_options.clone().unwrap_or_default();
122 #[cfg(feature = "storage")]
125 if !storage_options.path.is_dir() {
126 if self.client_options.is_none() {
127 return Err(crate::wallet::Error::MissingParameter("client_options"));
128 }
129 if self.coin_type.is_none() {
130 return Err(crate::wallet::Error::MissingParameter("coin_type"));
131 }
132 if self.secret_manager.is_none() {
133 return Err(crate::wallet::Error::MissingParameter("secret_manager"));
134 }
135 }
136
137 #[cfg(all(feature = "rocksdb", feature = "storage"))]
138 let storage =
139 crate::wallet::storage::adapter::rocksdb::RocksdbStorageAdapter::new(storage_options.path.clone())?;
140 #[cfg(all(not(feature = "rocksdb"), feature = "storage"))]
141 let storage = Memory::default();
142
143 #[cfg(feature = "storage")]
144 let mut storage_manager = StorageManager::new(storage, storage_options.encryption_key.clone()).await?;
145
146 #[cfg(feature = "storage")]
147 let read_manager_builder = Self::load(&storage_manager).await?;
148 #[cfg(not(feature = "storage"))]
149 let read_manager_builder: Option<Self> = None;
150
151 let new_provided_client_options = if self.client_options.is_none() {
153 let loaded_client_options = read_manager_builder
154 .as_ref()
155 .and_then(|data| data.client_options.clone())
156 .ok_or(crate::wallet::Error::MissingParameter("client_options"))?;
157
158 self.client_options.replace(loaded_client_options);
160 false
161 } else {
162 true
163 };
164
165 if self.secret_manager.is_none() {
166 let secret_manager = read_manager_builder
167 .as_ref()
168 .and_then(|data| data.secret_manager.clone())
169 .ok_or(crate::wallet::Error::MissingParameter("secret_manager"))?;
170
171 self.secret_manager.replace(secret_manager);
173 }
174
175 if self.coin_type.is_none() {
176 self.coin_type = read_manager_builder.and_then(|builder| builder.coin_type);
177 }
178 let coin_type = self.coin_type.ok_or(crate::wallet::Error::MissingParameter(
179 "coin_type (IOTA: 4218, Shimmer: 4219)",
180 ))?;
181
182 #[cfg(feature = "storage")]
183 let mut accounts = storage_manager.get_accounts().await?;
184
185 #[cfg(feature = "storage")]
187 if let Some(account) = accounts.first() {
188 if *account.coin_type() != coin_type {
189 return Err(crate::wallet::Error::InvalidCoinType {
190 new_coin_type: coin_type,
191 existing_coin_type: *account.coin_type(),
192 });
193 }
194 }
195
196 #[cfg(feature = "storage")]
198 self.save(&storage_manager).await?;
199
200 #[cfg(feature = "events")]
201 let event_emitter = tokio::sync::RwLock::new(EventEmitter::new());
202
203 #[cfg(feature = "storage")]
206 unlock_unused_inputs(&mut accounts)?;
207 #[cfg(not(feature = "storage"))]
208 let accounts = Vec::new();
209 let wallet_inner = Arc::new(WalletInner {
210 background_syncing_status: AtomicUsize::new(0),
211 client: self
212 .client_options
213 .clone()
214 .ok_or(crate::wallet::Error::MissingParameter("client_options"))?
215 .finish()
216 .await?,
217 coin_type: AtomicU32::new(coin_type),
218 secret_manager: self
219 .secret_manager
220 .ok_or(crate::wallet::Error::MissingParameter("secret_manager"))?,
221 #[cfg(feature = "events")]
222 event_emitter,
223 #[cfg(feature = "storage")]
224 storage_options,
225 #[cfg(feature = "storage")]
226 storage_manager: tokio::sync::RwLock::new(storage_manager),
227 });
228
229 let mut accounts: Vec<Account<S>> = try_join_all(
230 accounts
231 .into_iter()
232 .map(|a| Account::new(a, wallet_inner.clone()).boxed()),
233 )
234 .await?;
235
236 if new_provided_client_options {
239 for account in accounts.iter_mut() {
240 account.update_account_bech32_hrp().await?;
242 }
243 }
244
245 Ok(Wallet {
246 inner: wallet_inner,
247 accounts: Arc::new(RwLock::new(accounts)),
248 })
249 }
250
251 #[cfg(feature = "storage")]
252 pub(crate) async fn from_wallet(wallet: &Wallet<S>) -> Self {
253 Self {
254 client_options: Some(wallet.client_options().await),
255 coin_type: Some(wallet.coin_type.load(Ordering::Relaxed)),
256 storage_options: Some(wallet.storage_options.clone()),
257 secret_manager: Some(wallet.secret_manager.clone()),
258 }
259 }
260}
261
262#[cfg(feature = "storage")]
265fn unlock_unused_inputs(accounts: &mut [AccountDetails]) -> crate::wallet::Result<()> {
266 log::debug!("[unlock_unused_inputs]");
267 for account in accounts.iter_mut() {
268 let mut used_inputs = HashSet::new();
269 for transaction_id in account.pending_transactions() {
270 if let Some(tx) = account.transactions().get(transaction_id) {
271 for input in &tx.inputs {
272 used_inputs.insert(*input.metadata.output_id());
273 }
274 }
275 }
276 account.locked_outputs.retain(|input| {
277 let used = used_inputs.contains(input);
278 if !used {
279 log::debug!("unlocking unused input {input}");
280 }
281 used
282 })
283 }
284 Ok(())
285}
286
287#[cfg(feature = "serde")]
288pub(crate) mod dto {
289 use serde::Deserialize;
290
291 use super::*;
292 #[cfg(feature = "storage")]
293 use crate::{client::secret::SecretManage, wallet::storage::StorageOptions};
294
295 #[derive(Debug, Deserialize)]
296 #[serde(rename_all = "camelCase")]
297 pub struct WalletBuilderDto {
298 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub(crate) client_options: Option<ClientOptions>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub(crate) coin_type: Option<u32>,
302 #[cfg(feature = "storage")]
303 #[serde(default, skip_serializing_if = "Option::is_none")]
304 pub(crate) storage_options: Option<StorageOptions>,
305 }
306
307 impl<S: SecretManage> From<WalletBuilderDto> for WalletBuilder<S> {
308 fn from(value: WalletBuilderDto) -> Self {
309 Self {
310 client_options: value.client_options,
311 coin_type: value.coin_type,
312 #[cfg(feature = "storage")]
313 storage_options: value.storage_options,
314 secret_manager: None,
315 }
316 }
317 }
318
319 impl<'de, S: SecretManage> Deserialize<'de> for WalletBuilder<S> {
320 fn deserialize<D>(d: D) -> Result<Self, D::Error>
321 where
322 D: serde::Deserializer<'de>,
323 {
324 WalletBuilderDto::deserialize(d).map(Into::into)
325 }
326 }
327}