cdk/wallet/
mod.rs

1#![doc = include_str!("./README.md")]
2
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::str::FromStr;
6use std::sync::atomic::AtomicBool;
7use std::sync::Arc;
8use std::time::Duration;
9
10use cdk_common::amount::FeeAndAmounts;
11use cdk_common::database::{self, WalletDatabase};
12use cdk_common::parking_lot::RwLock;
13use cdk_common::subscription::WalletParams;
14use getrandom::getrandom;
15use subscription::{ActiveSubscription, SubscriptionManager};
16#[cfg(feature = "auth")]
17use tokio::sync::RwLock as TokioRwLock;
18use tracing::instrument;
19use zeroize::Zeroize;
20
21use crate::amount::SplitTarget;
22use crate::dhke::construct_proofs;
23use crate::error::Error;
24use crate::fees::calculate_fee;
25use crate::mint_url::MintUrl;
26use crate::nuts::nut00::token::Token;
27use crate::nuts::nut17::Kind;
28use crate::nuts::{
29    nut10, CurrencyUnit, Id, Keys, MintInfo, MintQuoteState, PreMintSecrets, Proof, Proofs,
30    RestoreRequest, SpendingConditions, State,
31};
32use crate::types::ProofInfo;
33use crate::util::unix_time;
34use crate::wallet::mint_metadata_cache::MintMetadataCache;
35use crate::Amount;
36#[cfg(feature = "auth")]
37use crate::OidcClient;
38
39#[cfg(feature = "auth")]
40mod auth;
41#[cfg(all(feature = "tor", not(target_arch = "wasm32")))]
42pub use mint_connector::TorHttpClient;
43mod balance;
44mod builder;
45mod issue;
46mod keysets;
47mod melt;
48mod mint_connector;
49mod mint_metadata_cache;
50pub mod multi_mint_wallet;
51pub mod payment_request;
52mod proofs;
53mod receive;
54mod reclaim;
55mod send;
56#[cfg(not(target_arch = "wasm32"))]
57mod streams;
58pub mod subscription;
59mod swap;
60mod transactions;
61pub mod util;
62
63#[cfg(feature = "auth")]
64pub use auth::{AuthMintConnector, AuthWallet};
65pub use builder::WalletBuilder;
66pub use cdk_common::wallet as types;
67#[cfg(feature = "auth")]
68pub use mint_connector::http_client::AuthHttpClient as BaseAuthHttpClient;
69pub use mint_connector::http_client::HttpClient as BaseHttpClient;
70pub use mint_connector::transport::Transport as HttpTransport;
71#[cfg(feature = "auth")]
72pub use mint_connector::AuthHttpClient;
73pub use mint_connector::{HttpClient, LnurlPayInvoiceResponse, LnurlPayResponse, MintConnector};
74pub use multi_mint_wallet::{MultiMintReceiveOptions, MultiMintSendOptions, MultiMintWallet};
75pub use receive::ReceiveOptions;
76pub use send::{PreparedSend, SendMemo, SendOptions};
77pub use types::{MeltQuote, MintQuote, SendKind};
78
79use crate::nuts::nut00::ProofsMethods;
80
81/// CDK Wallet
82///
83/// The CDK [`Wallet`] is a high level cashu wallet.
84///
85/// A [`Wallet`] is for a single mint and single unit.
86#[derive(Debug, Clone)]
87pub struct Wallet {
88    /// Mint Url
89    pub mint_url: MintUrl,
90    /// Unit
91    pub unit: CurrencyUnit,
92    /// Storage backend
93    pub localstore: Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
94    /// Mint metadata cache for this mint (lock-free cached access to keys, keysets, and mint info)
95    pub metadata_cache: Arc<MintMetadataCache>,
96    /// The targeted amount of proofs to have at each size
97    pub target_proof_count: usize,
98    metadata_cache_ttl: Arc<RwLock<Option<Duration>>>,
99    #[cfg(feature = "auth")]
100    auth_wallet: Arc<TokioRwLock<Option<AuthWallet>>>,
101    seed: [u8; 64],
102    client: Arc<dyn MintConnector + Send + Sync>,
103    subscription: SubscriptionManager,
104    in_error_swap_reverted_proofs: Arc<AtomicBool>,
105}
106
107const ALPHANUMERIC: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
108
109/// Wallet Subscription filter
110#[derive(Debug, Clone)]
111pub enum WalletSubscription {
112    /// Proof subscription
113    ProofState(Vec<String>),
114    /// Mint quote subscription
115    Bolt11MintQuoteState(Vec<String>),
116    /// Melt quote subscription
117    Bolt11MeltQuoteState(Vec<String>),
118    /// Mint bolt12 quote subscription
119    Bolt12MintQuoteState(Vec<String>),
120}
121
122impl From<WalletSubscription> for WalletParams {
123    fn from(val: WalletSubscription) -> Self {
124        let mut buffer = vec![0u8; 10];
125
126        getrandom(&mut buffer).expect("Failed to generate random bytes");
127
128        let id = Arc::new(
129            buffer
130                .iter()
131                .map(|&byte| {
132                    let index = byte as usize % ALPHANUMERIC.len(); // 62 alphanumeric characters (A-Z, a-z, 0-9)
133                    ALPHANUMERIC[index] as char
134                })
135                .collect::<String>(),
136        );
137
138        match val {
139            WalletSubscription::ProofState(filters) => WalletParams {
140                filters,
141                kind: Kind::ProofState,
142                id,
143            },
144            WalletSubscription::Bolt11MintQuoteState(filters) => WalletParams {
145                filters,
146                kind: Kind::Bolt11MintQuote,
147                id,
148            },
149            WalletSubscription::Bolt11MeltQuoteState(filters) => WalletParams {
150                filters,
151                kind: Kind::Bolt11MeltQuote,
152                id,
153            },
154            WalletSubscription::Bolt12MintQuoteState(filters) => WalletParams {
155                filters,
156                kind: Kind::Bolt12MintQuote,
157                id,
158            },
159        }
160    }
161}
162
163impl Wallet {
164    /// Create new [`Wallet`] using the builder pattern
165    /// # Synopsis
166    /// ```rust
167    /// use bitcoin::bip32::Xpriv;
168    /// use std::sync::Arc;
169    ///
170    /// use cdk::nuts::CurrencyUnit;
171    /// use cdk::wallet::{Wallet, WalletBuilder};
172    /// use cdk_sqlite::wallet::memory;
173    /// use rand::random;
174    ///
175    /// async fn test() -> anyhow::Result<()> {
176    ///     let seed = random::<[u8; 64]>();
177    ///     let mint_url = "https://fake.thesimplekid.dev";
178    ///     let unit = CurrencyUnit::Sat;
179    ///
180    ///     let localstore = memory::empty().await?;
181    ///     let wallet = WalletBuilder::new()
182    ///         .mint_url(mint_url.parse().unwrap())
183    ///         .unit(unit)
184    ///         .localstore(Arc::new(localstore))
185    ///         .seed(seed)
186    ///         .build();
187    ///     Ok(())
188    /// }
189    /// ```
190    pub fn new(
191        mint_url: &str,
192        unit: CurrencyUnit,
193        localstore: Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
194        seed: [u8; 64],
195        target_proof_count: Option<usize>,
196    ) -> Result<Self, Error> {
197        let mint_url = MintUrl::from_str(mint_url)?;
198
199        WalletBuilder::new()
200            .mint_url(mint_url)
201            .unit(unit)
202            .localstore(localstore)
203            .seed(seed)
204            .target_proof_count(target_proof_count.unwrap_or(3))
205            .build()
206    }
207
208    /// Subscribe to events
209    pub async fn subscribe<T: Into<WalletParams>>(&self, query: T) -> ActiveSubscription {
210        self.subscription
211            .subscribe(self.mint_url.clone(), query.into())
212            .expect("FIXME")
213    }
214
215    /// Fee required for proof set
216    #[instrument(skip_all)]
217    pub async fn get_proofs_fee(&self, proofs: &Proofs) -> Result<Amount, Error> {
218        let proofs_per_keyset = proofs.count_by_keyset();
219        self.get_proofs_fee_by_count(proofs_per_keyset).await
220    }
221
222    /// Fee required for proof set by count
223    pub async fn get_proofs_fee_by_count(
224        &self,
225        proofs_per_keyset: HashMap<Id, u64>,
226    ) -> Result<Amount, Error> {
227        let mut fee_per_keyset = HashMap::new();
228        let metadata = self
229            .metadata_cache
230            .load(&self.localstore, &self.client, {
231                let ttl = self.metadata_cache_ttl.read();
232                *ttl
233            })
234            .await?;
235
236        for keyset_id in proofs_per_keyset.keys() {
237            let mint_keyset_info = metadata
238                .keysets
239                .get(keyset_id)
240                .ok_or(Error::UnknownKeySet)?;
241            fee_per_keyset.insert(*keyset_id, mint_keyset_info.input_fee_ppk);
242        }
243
244        let fee = calculate_fee(&proofs_per_keyset, &fee_per_keyset)?;
245
246        Ok(fee)
247    }
248
249    /// Get fee for count of proofs in a keyset
250    #[instrument(skip_all)]
251    pub async fn get_keyset_count_fee(&self, keyset_id: &Id, count: u64) -> Result<Amount, Error> {
252        let input_fee_ppk = self
253            .metadata_cache
254            .load(&self.localstore, &self.client, {
255                let ttl = self.metadata_cache_ttl.read();
256                *ttl
257            })
258            .await?
259            .keysets
260            .get(keyset_id)
261            .ok_or(Error::UnknownKeySet)?
262            .input_fee_ppk;
263
264        let fee = (input_fee_ppk * count).div_ceil(1000);
265
266        Ok(Amount::from(fee))
267    }
268
269    /// Update Mint information and related entries in the event a mint changes
270    /// its URL
271    #[instrument(skip(self))]
272    pub async fn update_mint_url(&mut self, new_mint_url: MintUrl) -> Result<(), Error> {
273        // Update the mint URL in the wallet DB
274        self.localstore
275            .update_mint_url(self.mint_url.clone(), new_mint_url.clone())
276            .await?;
277
278        // Update the mint URL in the wallet struct field
279        self.mint_url = new_mint_url;
280
281        Ok(())
282    }
283
284    /// Query mint for current mint information
285    #[instrument(skip(self))]
286    pub async fn fetch_mint_info(&self) -> Result<Option<MintInfo>, Error> {
287        let mint_info = self
288            .metadata_cache
289            .load_from_mint(&self.localstore, &self.client)
290            .await?
291            .mint_info
292            .clone();
293
294        // If mint provides time make sure it is accurate
295        if let Some(mint_unix_time) = mint_info.time {
296            let current_unix_time = unix_time();
297            if current_unix_time.abs_diff(mint_unix_time) > 30 {
298                tracing::warn!(
299                    "Mint time does match wallet time. Mint: {}, Wallet: {}",
300                    mint_unix_time,
301                    current_unix_time
302                );
303                return Err(Error::MintTimeExceedsTolerance);
304            }
305        }
306
307        // Create or update auth wallet
308        #[cfg(feature = "auth")]
309        {
310            let mut auth_wallet = self.auth_wallet.write().await;
311            match &*auth_wallet {
312                Some(auth_wallet) => {
313                    let mut protected_endpoints = auth_wallet.protected_endpoints.write().await;
314                    *protected_endpoints = mint_info.protected_endpoints();
315
316                    if let Some(oidc_client) = mint_info
317                        .openid_discovery()
318                        .map(|url| OidcClient::new(url, None))
319                    {
320                        auth_wallet.set_oidc_client(Some(oidc_client)).await;
321                    }
322                }
323                None => {
324                    tracing::info!("Mint has auth enabled creating auth wallet");
325
326                    let oidc_client = mint_info
327                        .openid_discovery()
328                        .map(|url| OidcClient::new(url, None));
329                    let new_auth_wallet = AuthWallet::new(
330                        self.mint_url.clone(),
331                        None,
332                        self.localstore.clone(),
333                        self.metadata_cache.clone(),
334                        mint_info.protected_endpoints(),
335                        oidc_client,
336                    );
337                    *auth_wallet = Some(new_auth_wallet.clone());
338
339                    self.client.set_auth_wallet(Some(new_auth_wallet)).await;
340                }
341            }
342        }
343
344        tracing::trace!("Mint info updated for {}", self.mint_url);
345
346        Ok(Some(mint_info))
347    }
348
349    /// Load mint info from cache
350    ///
351    /// This is a helper function that loads the mint info from the metadata cache
352    /// using the configured TTL. Unlike `fetch_mint_info()`, this does not make
353    /// a network call if the cache is fresh.
354    #[instrument(skip(self))]
355    pub async fn load_mint_info(&self) -> Result<MintInfo, Error> {
356        let mint_info = self
357            .metadata_cache
358            .load(&self.localstore, &self.client, {
359                let ttl = self.metadata_cache_ttl.read();
360                *ttl
361            })
362            .await?
363            .mint_info
364            .clone();
365
366        Ok(mint_info)
367    }
368
369    /// Get amounts needed to refill proof state
370    #[instrument(skip(self))]
371    pub async fn amounts_needed_for_state_target(
372        &self,
373        fee_and_amounts: &FeeAndAmounts,
374    ) -> Result<Vec<Amount>, Error> {
375        let unspent_proofs = self.get_unspent_proofs().await?;
376
377        let amounts_count: HashMap<u64, u64> =
378            unspent_proofs
379                .iter()
380                .fold(HashMap::new(), |mut acc, proof| {
381                    let amount = proof.amount;
382                    let counter = acc.entry(u64::from(amount)).or_insert(0);
383                    *counter += 1;
384                    acc
385                });
386
387        let needed_amounts =
388            fee_and_amounts
389                .amounts()
390                .iter()
391                .fold(Vec::new(), |mut acc, amount| {
392                    let count_needed = (self.target_proof_count as u64)
393                        .saturating_sub(*amounts_count.get(amount).unwrap_or(&0));
394
395                    for _i in 0..count_needed {
396                        acc.push(Amount::from(*amount));
397                    }
398
399                    acc
400                });
401        Ok(needed_amounts)
402    }
403
404    /// Determine [`SplitTarget`] for amount based on state
405    #[instrument(skip(self))]
406    async fn determine_split_target_values(
407        &self,
408        change_amount: Amount,
409        fee_and_amounts: &FeeAndAmounts,
410    ) -> Result<SplitTarget, Error> {
411        let mut amounts_needed_refill = self
412            .amounts_needed_for_state_target(fee_and_amounts)
413            .await?;
414
415        amounts_needed_refill.sort();
416
417        let mut values = Vec::new();
418
419        for amount in amounts_needed_refill {
420            let values_sum = Amount::try_sum(values.clone().into_iter())?;
421            if values_sum + amount <= change_amount {
422                values.push(amount);
423            }
424        }
425
426        Ok(SplitTarget::Values(values))
427    }
428
429    /// Restore
430    #[instrument(skip(self))]
431    pub async fn restore(&self) -> Result<Amount, Error> {
432        // Check that mint is in store of mints
433        if self
434            .localstore
435            .get_mint(self.mint_url.clone())
436            .await?
437            .is_none()
438        {
439            self.fetch_mint_info().await?;
440        }
441
442        let keysets = self.load_mint_keysets().await?;
443
444        let mut restored_value = Amount::ZERO;
445
446        for keyset in keysets {
447            let keys = self.load_keyset_keys(keyset.id).await?;
448            let mut empty_batch = 0;
449            let mut start_counter = 0;
450
451            while empty_batch.lt(&3) {
452                let premint_secrets = PreMintSecrets::restore_batch(
453                    keyset.id,
454                    &self.seed,
455                    start_counter,
456                    start_counter + 100,
457                )?;
458
459                tracing::debug!(
460                    "Attempting to restore counter {}-{} for mint {} keyset {}",
461                    start_counter,
462                    start_counter + 100,
463                    self.mint_url,
464                    keyset.id
465                );
466
467                let restore_request = RestoreRequest {
468                    outputs: premint_secrets.blinded_messages(),
469                };
470
471                let response = self.client.post_restore(restore_request).await?;
472
473                if response.signatures.is_empty() {
474                    empty_batch += 1;
475                    start_counter += 100;
476                    continue;
477                }
478
479                let premint_secrets: Vec<_> = premint_secrets
480                    .secrets
481                    .iter()
482                    .filter(|p| response.outputs.contains(&p.blinded_message))
483                    .collect();
484
485                // the response outputs and premint secrets should be the same after filtering
486                // blinded messages the mint did not have signatures for
487                assert_eq!(response.outputs.len(), premint_secrets.len());
488
489                let proofs = construct_proofs(
490                    response.signatures,
491                    premint_secrets.iter().map(|p| p.r.clone()).collect(),
492                    premint_secrets.iter().map(|p| p.secret.clone()).collect(),
493                    &keys,
494                )?;
495
496                tracing::debug!("Restored {} proofs", proofs.len());
497
498                self.localstore
499                    .increment_keyset_counter(&keyset.id, proofs.len() as u32)
500                    .await?;
501
502                let states = self.check_proofs_spent(proofs.clone()).await?;
503
504                let unspent_proofs: Vec<Proof> = proofs
505                    .iter()
506                    .zip(states)
507                    .filter(|(_, state)| !state.state.eq(&State::Spent))
508                    .map(|(p, _)| p)
509                    .cloned()
510                    .collect();
511
512                restored_value += unspent_proofs.total_amount()?;
513
514                let unspent_proofs = unspent_proofs
515                    .into_iter()
516                    .map(|proof| {
517                        ProofInfo::new(
518                            proof,
519                            self.mint_url.clone(),
520                            State::Unspent,
521                            keyset.unit.clone(),
522                        )
523                    })
524                    .collect::<Result<Vec<ProofInfo>, _>>()?;
525
526                self.localstore
527                    .update_proofs(unspent_proofs, vec![])
528                    .await?;
529
530                empty_batch = 0;
531                start_counter += 100;
532            }
533        }
534        Ok(restored_value)
535    }
536
537    /// Verify all proofs in token have meet the required spend
538    /// Can be used to allow a wallet to accept payments offline while reducing
539    /// the risk of claiming back to the limits let by the spending_conditions
540    #[instrument(skip(self, token))]
541    pub async fn verify_token_p2pk(
542        &self,
543        token: &Token,
544        spending_conditions: SpendingConditions,
545    ) -> Result<(), Error> {
546        let (refund_keys, pubkeys, locktime, num_sigs) = match spending_conditions {
547            SpendingConditions::P2PKConditions { data, conditions } => {
548                let mut pubkeys = vec![data];
549
550                match conditions {
551                    Some(conditions) => {
552                        pubkeys.extend(conditions.pubkeys.unwrap_or_default());
553
554                        (
555                            conditions.refund_keys,
556                            Some(pubkeys),
557                            conditions.locktime,
558                            conditions.num_sigs,
559                        )
560                    }
561                    None => (None, Some(pubkeys), None, None),
562                }
563            }
564            SpendingConditions::HTLCConditions {
565                conditions,
566                data: _,
567            } => match conditions {
568                Some(conditions) => (
569                    conditions.refund_keys,
570                    conditions.pubkeys,
571                    conditions.locktime,
572                    conditions.num_sigs,
573                ),
574                None => (None, None, None, None),
575            },
576        };
577
578        if refund_keys.is_some() && locktime.is_none() {
579            tracing::warn!(
580                "Invalid spending conditions set: Locktime must be set if refund keys are allowed"
581            );
582            return Err(Error::InvalidSpendConditions(
583                "Must set locktime".to_string(),
584            ));
585        }
586        if token.mint_url()? != self.mint_url {
587            return Err(Error::IncorrectWallet(format!(
588                "Should be {} not {}",
589                self.mint_url,
590                token.mint_url()?
591            )));
592        }
593        // We need the keysets information to properly convert from token proof to proof
594        let keysets_info = self.load_mint_keysets().await?;
595        let proofs = token.proofs(&keysets_info)?;
596
597        for proof in proofs {
598            let secret: nut10::Secret = (&proof.secret).try_into()?;
599
600            let proof_conditions: SpendingConditions = secret.try_into()?;
601
602            if num_sigs.ne(&proof_conditions.num_sigs()) {
603                tracing::debug!(
604                    "Spending condition requires: {:?} sigs proof secret specifies: {:?}",
605                    num_sigs,
606                    proof_conditions.num_sigs()
607                );
608
609                return Err(Error::P2PKConditionsNotMet(
610                    "Num sigs did not match spending condition".to_string(),
611                ));
612            }
613
614            let spending_condition_pubkeys = pubkeys.clone().unwrap_or_default();
615            let proof_pubkeys = proof_conditions.pubkeys().unwrap_or_default();
616
617            // Check the Proof has the required pubkeys
618            if proof_pubkeys.len().ne(&spending_condition_pubkeys.len())
619                || !proof_pubkeys
620                    .iter()
621                    .all(|pubkey| spending_condition_pubkeys.contains(pubkey))
622            {
623                tracing::debug!("Proof did not included Publickeys meeting condition");
624                tracing::debug!("{:?}", proof_pubkeys);
625                tracing::debug!("{:?}", spending_condition_pubkeys);
626                return Err(Error::P2PKConditionsNotMet(
627                    "Pubkeys in proof not allowed by spending condition".to_string(),
628                ));
629            }
630
631            // If spending condition refund keys is allowed (Some(Empty Vec))
632            // If spending conition refund keys is allowed to restricted set of keys check
633            // it is one of them Check that proof locktime is > condition
634            // locktime
635
636            if let Some(proof_refund_keys) = proof_conditions.refund_keys() {
637                let proof_locktime = proof_conditions
638                    .locktime()
639                    .ok_or(Error::LocktimeNotProvided)?;
640
641                if let (Some(condition_refund_keys), Some(condition_locktime)) =
642                    (&refund_keys, locktime)
643                {
644                    // Proof locktime must be greater then condition locktime to ensure it
645                    // cannot be claimed back
646                    if proof_locktime.lt(&condition_locktime) {
647                        return Err(Error::P2PKConditionsNotMet(
648                            "Proof locktime less then required".to_string(),
649                        ));
650                    }
651
652                    // A non empty condition refund key list is used as a restricted set of keys
653                    // returns are allowed to An empty list means the
654                    // proof can be refunded to anykey set in the secret
655                    if !condition_refund_keys.is_empty()
656                        && !proof_refund_keys
657                            .iter()
658                            .all(|refund_key| condition_refund_keys.contains(refund_key))
659                    {
660                        return Err(Error::P2PKConditionsNotMet(
661                            "Refund Key not allowed".to_string(),
662                        ));
663                    }
664                } else {
665                    // Spending conditions does not allow refund keys
666                    return Err(Error::P2PKConditionsNotMet(
667                        "Spending condition does not allow refund keys".to_string(),
668                    ));
669                }
670            }
671        }
672
673        Ok(())
674    }
675
676    /// Verify all proofs in token have a valid DLEQ proof
677    #[instrument(skip(self, token))]
678    pub async fn verify_token_dleq(&self, token: &Token) -> Result<(), Error> {
679        let mut keys_cache: HashMap<Id, Keys> = HashMap::new();
680
681        // TODO: Get mint url
682        // if mint_url != &self.mint_url {
683        //     return Err(Error::IncorrectWallet(format!(
684        //         "Should be {} not {}",
685        //         self.mint_url, mint_url
686        //     )));
687        // }
688
689        // We need the keysets information to properly convert from token proof to proof
690        let keysets_info = self.load_mint_keysets().await?;
691        let proofs = token.proofs(&keysets_info)?;
692        for proof in proofs {
693            let mint_pubkey = match keys_cache.get(&proof.keyset_id) {
694                Some(keys) => keys.amount_key(proof.amount),
695                None => {
696                    let keys = self.load_keyset_keys(proof.keyset_id).await?;
697
698                    let key = keys.amount_key(proof.amount);
699                    keys_cache.insert(proof.keyset_id, keys);
700
701                    key
702                }
703            }
704            .ok_or(Error::AmountKey)?;
705
706            proof
707                .verify_dleq(mint_pubkey)
708                .map_err(|_| Error::CouldNotVerifyDleq)?;
709        }
710
711        Ok(())
712    }
713
714    /// Set the client (MintConnector) for this wallet
715    ///
716    /// This allows updating the connector without recreating the wallet.
717    pub fn set_client(&mut self, client: Arc<dyn MintConnector + Send + Sync>) {
718        self.client = client;
719    }
720
721    /// Set the target proof count for this wallet
722    ///
723    /// This controls how many proofs of each denomination the wallet tries to maintain.
724    pub fn set_target_proof_count(&mut self, count: usize) {
725        self.target_proof_count = count;
726    }
727}
728
729impl Drop for Wallet {
730    fn drop(&mut self) {
731        self.seed.zeroize();
732    }
733}