Skip to main content

bsv_wallet_toolbox/wallet/
wallet.rs

1//! Wallet struct implementing the full WalletInterface trait.
2//!
3//! This is the core deliverable of Phase 5: a complete WalletInterface implementation
4//! that wires together the signer, storage, and ProtoWallet subsystems. Every method
5//! follows the validate-delegate-postprocess pattern from the TS reference.
6
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use async_trait::async_trait;
11
12use bsv::primitives::public_key::PublicKey;
13use bsv::services::overlay_tools::LookupResolver;
14use bsv::transaction::beef_party::BeefParty;
15use bsv::wallet::cached_key_deriver::CachedKeyDeriver;
16use bsv::wallet::error::WalletError as SdkWalletError;
17use bsv::wallet::interfaces::{
18    AbortActionArgs, AbortActionResult, AcquireCertificateArgs, AcquisitionProtocol,
19    AuthenticatedResult, Certificate, CreateActionArgs, CreateActionOptions, CreateActionResult,
20    CreateHmacArgs, CreateHmacResult, CreateSignatureArgs, CreateSignatureResult, DecryptArgs,
21    DecryptResult, DiscoverByAttributesArgs, DiscoverByIdentityKeyArgs, DiscoverCertificatesResult,
22    EncryptArgs, EncryptResult, GetHeaderArgs, GetHeaderResult, GetHeightResult, GetNetworkResult,
23    GetPublicKeyArgs, GetPublicKeyResult, GetVersionResult, InternalizeActionArgs,
24    InternalizeActionResult, ListActionsArgs, ListActionsResult, ListCertificatesArgs,
25    ListCertificatesResult, ListOutputsArgs, ListOutputsResult, Network, ProveCertificateArgs,
26    ProveCertificateResult, RelinquishCertificateArgs, RelinquishCertificateResult,
27    RelinquishOutputArgs, RelinquishOutputResult, RevealCounterpartyKeyLinkageArgs,
28    RevealCounterpartyKeyLinkageResult, RevealSpecificKeyLinkageArgs,
29    RevealSpecificKeyLinkageResult, SignActionArgs, SignActionOptions, SignActionResult, TrustSelf,
30    VerifyHmacArgs, VerifyHmacResult, VerifySignatureArgs, VerifySignatureResult, WalletInterface,
31};
32use bsv::wallet::proto_wallet::ProtoWallet;
33
34use crate::wallet::discovery::OverlayCache;
35
36use crate::error::WalletError;
37use crate::services::traits::WalletServices;
38use crate::signer::default_signer::DefaultWalletSigner;
39use crate::signer::traits::WalletSigner;
40use crate::storage::manager::WalletStorageManager;
41use crate::storage::traits::provider::StorageProvider;
42use crate::types::Chain;
43use crate::wallet::privileged::PrivilegedKeyManager;
44use crate::wallet::settings::WalletSettingsManager;
45use crate::wallet::types::{
46    AdminStatsResult, AuthId, KeyPair, PendingSignAction, StorageIdentity, UtxoInfo, WalletArgs,
47    WalletBalance, SPEC_OP_FAILED_ACTIONS, SPEC_OP_INVALID_CHANGE, SPEC_OP_NO_SEND_ACTIONS,
48    SPEC_OP_SET_WALLET_CHANGE_PARAMS, SPEC_OP_WALLET_BALANCE,
49};
50use crate::wallet::validation::validate_originator;
51
52// ---------------------------------------------------------------------------
53// Wallet struct
54// ---------------------------------------------------------------------------
55
56/// The core Wallet implementing all 28 WalletInterface methods.
57///
58/// Delegates crypto operations to ProtoWallet (or PrivilegedKeyManager),
59/// action operations to DefaultWalletSigner, and query operations to
60/// WalletStorageManager.
61///
62/// # Example
63///
64/// ```no_run
65/// use bsv::primitives::private_key::PrivateKey;
66/// use bsv_wallet_toolbox::wallet::setup::WalletBuilder;
67/// use bsv_wallet_toolbox::types::Chain;
68///
69/// # async fn example() -> bsv_wallet_toolbox::WalletResult<()> {
70/// let root_key = PrivateKey::from_hex("aa").unwrap();
71/// let setup = WalletBuilder::new()
72///     .chain(Chain::Test)
73///     .root_key(root_key)
74///     .with_sqlite_memory()
75///     .build()
76///     .await?;
77///
78/// // Use the wallet for operations
79/// let wallet = setup.wallet;
80/// # Ok(())
81/// # }
82/// ```
83pub struct Wallet {
84    /// BSV network chain this wallet operates on.
85    pub chain: Chain,
86    /// Key deriver for BRC-42/BRC-43 child key derivation.
87    pub key_deriver: Arc<CachedKeyDeriver>,
88    /// Storage manager providing persistence operations.
89    pub storage: WalletStorageManager,
90    /// Optional network services (broadcasting, chain lookups, etc.).
91    pub services: Option<Arc<dyn WalletServices>>,
92    /// Optional background monitor for transaction lifecycle.
93    pub monitor: Option<Arc<crate::monitor::Monitor>>,
94    /// Optional privileged key manager for sensitive crypto operations.
95    pub privileged_key_manager: Option<Arc<dyn PrivilegedKeyManager>>,
96    /// Settings manager with cached TTL for wallet configuration.
97    pub settings_manager: WalletSettingsManager,
98    /// Public identity key derived from the root private key.
99    pub identity_key: PublicKey,
100    /// Protocol wallet providing default WalletInterface implementations.
101    pub proto: ProtoWallet,
102
103    // BeefParty state
104    beef: tokio::sync::Mutex<BeefParty>,
105    /// Whether to include all source transactions in BEEF output.
106    pub include_all_source_transactions: bool,
107    /// Whether to automatically add known txids from storage.
108    pub auto_known_txids: bool,
109    /// Whether to return only the txid without full BEEF data.
110    pub return_txid_only: bool,
111    /// Self-trust configuration for signed operations.
112    pub trust_self: Option<TrustSelf>,
113    user_party: String,
114
115    /// In-memory pending sign actions awaiting deferred signing.
116    pub pending_sign_actions: tokio::sync::Mutex<HashMap<String, PendingSignAction>>,
117
118    // Overlay discovery cache
119    overlay_cache: OverlayCache,
120    /// Optional overlay lookup resolver for identity certificate discovery.
121    pub lookup_resolver: Option<Arc<LookupResolver>>,
122
123    /// Test hook: pre-determined random values for deterministic testing.
124    pub random_vals: Option<Vec<f64>>,
125
126    // Internal signer
127    signer: DefaultWalletSigner,
128}
129
130// ---------------------------------------------------------------------------
131// Constructor and helpers
132// ---------------------------------------------------------------------------
133
134impl Wallet {
135    /// Create a new Wallet from WalletArgs.
136    ///
137    /// Validates that the key deriver's identity key matches the storage auth ID.
138    pub fn new(args: WalletArgs) -> Result<Self, WalletError> {
139        // Validate identity key match
140        let identity_key = args.key_deriver.identity_key();
141        let identity_key_hex = identity_key.to_der_hex();
142        let storage_auth_id = args.storage.auth_id().to_string();
143        if identity_key_hex != storage_auth_id {
144            return Err(WalletError::InvalidParameter {
145                parameter: "key_deriver".to_string(),
146                must_be: format!(
147                    "consistent with storage auth_id. key_deriver identity_key={} but storage auth_id={}",
148                    identity_key_hex, storage_auth_id
149                ),
150            });
151        }
152
153        // Create ProtoWallet from key_deriver's root key
154        let root_key = args.key_deriver.root_key().clone();
155        let proto = ProtoWallet::new(root_key);
156
157        // Derive user_party
158        let root_pub_hex = args.key_deriver.root_key().to_public_key().to_der_hex();
159        let user_party = format!("user {}", root_pub_hex);
160
161        // Initialize BeefParty
162        let beef = BeefParty::new([user_party.clone()]);
163
164        // Wire services into signer
165        let services_for_signer = args.services.clone().ok_or_else(|| {
166            WalletError::InvalidOperation(
167                "WalletServices required for Wallet construction".to_string(),
168            )
169        })?;
170
171        // Create signer with a shared storage manager (same providers as args.storage)
172        let signer = DefaultWalletSigner::new(
173            Arc::new(WalletStorageManager::new(
174                identity_key_hex.clone(),
175                args.storage.active().cloned(),
176                args.storage.backups().to_vec(),
177            )),
178            services_for_signer,
179            args.key_deriver.clone(),
180            args.chain.clone(),
181            identity_key.clone(),
182        );
183
184        // Settings manager: use provided or create default
185        let settings_manager = args.settings_manager.unwrap_or_else(|| {
186            // Use the initial active provider for the settings cache.
187            // The wallet always requires at least one provider.
188            let active_provider = args
189                .storage
190                .active()
191                .cloned()
192                .expect("WalletStorageManager must have at least one storage provider");
193            WalletSettingsManager::new(active_provider)
194        });
195
196        Ok(Wallet {
197            chain: args.chain,
198            key_deriver: args.key_deriver,
199            storage: args.storage,
200            services: args.services,
201            monitor: args.monitor,
202            privileged_key_manager: args.privileged_key_manager,
203            settings_manager,
204            identity_key,
205            proto,
206            beef: tokio::sync::Mutex::new(beef),
207            include_all_source_transactions: true,
208            auto_known_txids: false,
209            return_txid_only: false,
210            trust_self: Some(TrustSelf::Known),
211            user_party,
212            pending_sign_actions: tokio::sync::Mutex::new(HashMap::new()),
213            overlay_cache: OverlayCache::new(),
214            lookup_resolver: args.lookup_resolver,
215            random_vals: None,
216            signer,
217        })
218    }
219
220    /// Returns the AuthId for this wallet.
221    fn auth_id(&self) -> AuthId {
222        AuthId {
223            identity_key: self.identity_key.to_der_hex(),
224            user_id: None,
225            is_active: None,
226        }
227    }
228
229    /// Validates the originator parameter.
230    fn validate_originator(&self, originator: Option<&str>) -> Result<(), WalletError> {
231        validate_originator(originator)
232    }
233
234    /// Returns a reference to the wallet services, or an error if not configured.
235    fn get_services(&self) -> Result<&Arc<dyn WalletServices>, WalletError> {
236        self.services.as_ref().ok_or_else(|| {
237            WalletError::InvalidOperation("Wallet services not configured".to_string())
238        })
239    }
240
241    /// Returns the client change key pair (root private + public key).
242    pub fn get_client_change_key_pair(&self) -> KeyPair {
243        let root = self.key_deriver.root_key();
244        KeyPair {
245            private_key: root.to_hex(),
246            public_key: root.to_public_key().to_der_hex(),
247        }
248    }
249
250    /// Returns the storage identity for this wallet.
251    ///
252    /// Returns the active store's storage_identity_key if available (after
253    /// make_available), otherwise falls back to the wallet's identity key.
254    pub async fn get_storage_identity(&self) -> StorageIdentity {
255        let key = self
256            .storage
257            .get_storage_identity_key()
258            .await
259            .unwrap_or_else(|_| self.identity_key.to_der_hex());
260        StorageIdentity {
261            storage_identity_key: key,
262            storage_name: "default".to_string(),
263        }
264    }
265
266    /// Returns the storage party string.
267    pub async fn storage_party(&self) -> String {
268        let si = self.get_storage_identity().await;
269        format!("storage {}", si.storage_identity_key)
270    }
271
272    /// Returns the identity key as a hex string.
273    pub async fn get_identity_key(&self) -> Result<String, SdkWalletError> {
274        let result = self
275            .get_public_key(
276                GetPublicKeyArgs {
277                    identity_key: true,
278                    protocol_id: None,
279                    key_id: None,
280                    counterparty: None,
281                    privileged: false,
282                    privileged_reason: None,
283                    for_self: None,
284                    seek_permission: None,
285                },
286                None,
287            )
288            .await?;
289        Ok(result.public_key.to_der_hex())
290    }
291
292    /// Destroy the wallet: destroys storage and privileged key manager if present.
293    pub async fn destroy(&self) -> Result<(), WalletError> {
294        self.storage.destroy().await?;
295        if let Some(ref pkm) = self.privileged_key_manager {
296            pkm.destroy_key().await.map_err(|e| {
297                WalletError::Internal(format!("Failed to destroy privileged key: {}", e))
298            })?;
299        }
300        Ok(())
301    }
302
303    // -----------------------------------------------------------------------
304    // Convenience methods (Phase 7)
305    // -----------------------------------------------------------------------
306
307    /// Returns the total spendable balance in satoshis.
308    ///
309    /// Routes through the specOp WalletBalance basket to sum all spendable
310    /// outputs. If `args` is provided with a non-specOp basket, the specOp
311    /// constant is pushed to tags to combine basket filtering with balance.
312    pub async fn balance(&self, args: Option<ListOutputsArgs>) -> Result<u64, WalletError> {
313        let args = match args {
314            Some(mut a) => {
315                if a.basket != SPEC_OP_WALLET_BALANCE {
316                    a.tags.push(SPEC_OP_WALLET_BALANCE.to_string());
317                }
318                a
319            }
320            None => ListOutputsArgs {
321                basket: SPEC_OP_WALLET_BALANCE.to_string(),
322                tags: vec![],
323                tag_query_mode: None,
324                include: None,
325                include_custom_instructions: Default::default(),
326                include_tags: Default::default(),
327                include_labels: Default::default(),
328                limit: None,
329                offset: None,
330                seek_permission: Default::default(),
331            },
332        };
333
334        let r = self
335            .list_outputs(args, None)
336            .await
337            .map_err(|e| WalletError::Internal(e.to_string()))?;
338        Ok(r.total_outputs as u64)
339    }
340
341    /// Returns the total spendable balance plus individual UTXO details.
342    ///
343    /// Similar to `balance()` but also collects per-output information.
344    pub async fn balance_and_utxos(
345        &self,
346        args: Option<ListOutputsArgs>,
347    ) -> Result<WalletBalance, WalletError> {
348        let args = match args {
349            Some(mut a) => {
350                if a.basket != SPEC_OP_WALLET_BALANCE {
351                    a.tags.push(SPEC_OP_WALLET_BALANCE.to_string());
352                }
353                a
354            }
355            None => ListOutputsArgs {
356                basket: SPEC_OP_WALLET_BALANCE.to_string(),
357                tags: vec![],
358                tag_query_mode: None,
359                include: None,
360                include_custom_instructions: Default::default(),
361                include_tags: Default::default(),
362                include_labels: Default::default(),
363                limit: None,
364                offset: None,
365                seek_permission: Default::default(),
366            },
367        };
368
369        let r = self
370            .list_outputs(args, None)
371            .await
372            .map_err(|e| WalletError::Internal(e.to_string()))?;
373
374        let utxos: Vec<UtxoInfo> = r
375            .outputs
376            .iter()
377            .map(|o| UtxoInfo {
378                satoshis: o.satoshis,
379                outpoint: o.outpoint.clone(),
380            })
381            .collect();
382
383        Ok(WalletBalance {
384            total: r.total_outputs as u64,
385            utxos,
386        })
387    }
388
389    /// Transfer all wallet funds to another wallet using BRC-29 payment.
390    ///
391    /// Creates a sweep transaction sending MAX_POSSIBLE_SATOSHIS to the
392    /// receiving wallet via createAction + internalizeAction.
393    pub async fn sweep_to(&self, to_wallet: &Wallet) -> Result<(), WalletError> {
394        use crate::storage::methods::generate_change::MAX_POSSIBLE_SATOSHIS;
395        use crate::utility::script_template_brc29::ScriptTemplateBRC29;
396        use bsv::wallet::types::BooleanDefaultFalse;
397
398        // Generate random derivation prefix and suffix
399        let derivation_prefix = random_base64(8);
400        let derivation_suffix = random_base64(8);
401
402        let template =
403            ScriptTemplateBRC29::new(derivation_prefix.clone(), derivation_suffix.clone());
404
405        // Lock with sender's private key to receiver's public key
406        let sender_priv = self.key_deriver.root_key().clone();
407        let receiver_pub = to_wallet.identity_key.clone();
408        let lock_script = template.lock(&sender_priv, &receiver_pub)?;
409
410        let custom_instructions = serde_json::json!({
411            "derivationPrefix": derivation_prefix,
412            "derivationSuffix": derivation_suffix,
413            "type": "BRC29"
414        })
415        .to_string();
416
417        let car = self
418            .create_action(
419                CreateActionArgs {
420                    description: "sweep".to_string(),
421                    input_beef: None,
422                    inputs: vec![],
423                    outputs: vec![bsv::wallet::interfaces::CreateActionOutput {
424                        locking_script: Some(lock_script),
425                        satoshis: MAX_POSSIBLE_SATOSHIS,
426                        output_description: "sweep".to_string(),
427                        basket: None,
428                        custom_instructions: Some(custom_instructions),
429                        tags: vec!["relinquish".to_string()],
430                    }],
431                    lock_time: None,
432                    version: None,
433                    labels: vec!["sweep".to_string()],
434                    options: Some(CreateActionOptions {
435                        randomize_outputs: bsv::wallet::types::BooleanDefaultTrue(Some(false)),
436                        accept_delayed_broadcast: bsv::wallet::types::BooleanDefaultTrue(Some(
437                            false,
438                        )),
439                        ..Default::default()
440                    }),
441                    reference: None,
442                },
443                None,
444            )
445            .await
446            .map_err(|e| WalletError::Internal(e.to_string()))?;
447
448        let tx = car.tx.ok_or_else(|| {
449            WalletError::Internal("sweep createAction returned no tx".to_string())
450        })?;
451
452        // Internalize on receiving wallet
453        to_wallet
454            .internalize_action(
455                InternalizeActionArgs {
456                    tx,
457                    description: "sweep".to_string(),
458                    labels: vec!["sweep".to_string()],
459                    seek_permission: bsv::wallet::types::BooleanDefaultTrue(Some(false)),
460                    outputs: vec![bsv::wallet::interfaces::InternalizeOutput::WalletPayment {
461                        output_index: 0,
462                        payment: bsv::wallet::interfaces::Payment {
463                            derivation_prefix: derivation_prefix.into_bytes(),
464                            derivation_suffix: derivation_suffix.into_bytes(),
465                            sender_identity_key: self.identity_key.clone(),
466                        },
467                    }],
468                },
469                None,
470            )
471            .await
472            .map_err(|e| WalletError::Internal(e.to_string()))?;
473
474        Ok(())
475    }
476
477    /// Check UTXOs against the network and identify invalid/unspendable outputs.
478    ///
479    /// If `release` is true, locked invalid outputs are released.
480    /// If `all` is true, all outputs are checked (not just change).
481    pub async fn review_spendable_outputs(
482        &self,
483        release: bool,
484        all: bool,
485    ) -> Result<ListOutputsResult, WalletError> {
486        let mut tags = Vec::new();
487        if release {
488            tags.push("release".to_string());
489        }
490        if all {
491            tags.push("all".to_string());
492        }
493
494        let args = ListOutputsArgs {
495            basket: SPEC_OP_INVALID_CHANGE.to_string(),
496            tags,
497            tag_query_mode: None,
498            include: None,
499            include_custom_instructions: Default::default(),
500            include_tags: Default::default(),
501            include_labels: Default::default(),
502            limit: None,
503            offset: None,
504            seek_permission: Default::default(),
505        };
506
507        self.list_outputs(args, None)
508            .await
509            .map_err(|e| WalletError::Internal(e.to_string()))
510    }
511
512    /// Configure the number and size of change outputs for the default basket.
513    ///
514    /// Parameters are passed via specOp tags to list_outputs.
515    pub async fn set_wallet_change_params(
516        &self,
517        count: u32,
518        satoshis: u64,
519    ) -> Result<(), WalletError> {
520        let args = ListOutputsArgs {
521            basket: SPEC_OP_SET_WALLET_CHANGE_PARAMS.to_string(),
522            tags: vec![count.to_string(), satoshis.to_string()],
523            tag_query_mode: None,
524            include: None,
525            include_custom_instructions: Default::default(),
526            include_tags: Default::default(),
527            include_labels: Default::default(),
528            limit: None,
529            offset: None,
530            seek_permission: Default::default(),
531        };
532
533        let _ = self
534            .list_outputs(args, None)
535            .await
536            .map_err(|e| WalletError::Internal(e.to_string()))?;
537        Ok(())
538    }
539
540    /// List all no-send (un-broadcast) actions.
541    ///
542    /// If `abort` is true, each matched action is aborted after querying.
543    pub async fn list_no_send_actions(
544        &self,
545        abort: bool,
546    ) -> Result<ListActionsResult, WalletError> {
547        let mut labels = vec![SPEC_OP_NO_SEND_ACTIONS.to_string()];
548        if abort {
549            labels.push("abort".to_string());
550        }
551
552        let args = ListActionsArgs {
553            labels,
554            label_query_mode: None,
555            include_labels: Default::default(),
556            include_inputs: Default::default(),
557            include_input_source_locking_scripts: Default::default(),
558            include_input_unlocking_scripts: Default::default(),
559            include_outputs: Default::default(),
560            include_output_locking_scripts: Default::default(),
561            limit: None,
562            offset: None,
563            seek_permission: Default::default(),
564        };
565
566        self.list_actions(args, None)
567            .await
568            .map_err(|e| WalletError::Internal(e.to_string()))
569    }
570
571    /// List all failed actions.
572    ///
573    /// If `unfail` is true, each matched action is reset to unprocessed for retry.
574    pub async fn list_failed_actions(
575        &self,
576        unfail: bool,
577    ) -> Result<ListActionsResult, WalletError> {
578        let mut labels = vec![SPEC_OP_FAILED_ACTIONS.to_string()];
579        if unfail {
580            labels.push("unfail".to_string());
581        }
582
583        let args = ListActionsArgs {
584            labels,
585            label_query_mode: None,
586            include_labels: Default::default(),
587            include_inputs: Default::default(),
588            include_input_source_locking_scripts: Default::default(),
589            include_input_unlocking_scripts: Default::default(),
590            include_outputs: Default::default(),
591            include_output_locking_scripts: Default::default(),
592            limit: None,
593            offset: None,
594            seek_permission: Default::default(),
595        };
596
597        self.list_actions(args, None)
598            .await
599            .map_err(|e| WalletError::Internal(e.to_string()))
600    }
601
602    /// Returns aggregate deployment statistics from storage.
603    ///
604    /// Delegates to the storage manager's admin_stats method.
605    pub async fn admin_stats(&self) -> Result<AdminStatsResult, WalletError> {
606        let identity_key_hex = self.identity_key.to_der_hex();
607        self.storage.admin_stats(&identity_key_hex).await
608    }
609}
610
611/// Generate a random base64-encoded string from `n` random bytes.
612fn random_base64(n: usize) -> String {
613    use base64::Engine;
614    use rand::RngCore;
615    let mut buf = vec![0u8; n];
616    rand::thread_rng().fill_bytes(&mut buf);
617    base64::engine::general_purpose::STANDARD.encode(&buf)
618}
619
620// ---------------------------------------------------------------------------
621// Error conversion: crate::error::WalletError -> bsv::wallet::error::WalletError
622// ---------------------------------------------------------------------------
623
624fn to_sdk_error(e: WalletError) -> SdkWalletError {
625    match e {
626        WalletError::InvalidParameter { parameter, must_be } => SdkWalletError::InvalidParameter(
627            format!("The {} parameter must be {}", parameter, must_be),
628        ),
629        WalletError::NotImplemented(msg) => SdkWalletError::NotImplemented(msg),
630        WalletError::InvalidOperation(msg) => SdkWalletError::Internal(msg),
631        _ => SdkWalletError::Internal(e.to_string()),
632    }
633}
634
635// ---------------------------------------------------------------------------
636// WalletInterface implementation
637// ---------------------------------------------------------------------------
638
639#[async_trait]
640impl WalletInterface for Wallet {
641    // -----------------------------------------------------------------------
642    // CRYPTO methods (9) -- delegate to ProtoWallet or PrivilegedKeyManager
643    // -----------------------------------------------------------------------
644
645    async fn get_public_key(
646        &self,
647        args: GetPublicKeyArgs,
648        originator: Option<&str>,
649    ) -> Result<GetPublicKeyResult, SdkWalletError> {
650        self.validate_originator(originator).map_err(to_sdk_error)?;
651        bsv::wallet::validation::validate_get_public_key_args(&args)?;
652
653        if args.privileged {
654            if let Some(ref pkm) = self.privileged_key_manager {
655                return pkm.get_public_key(args).await;
656            }
657            return Err(SdkWalletError::Internal(
658                "No privileged key manager configured".to_string(),
659            ));
660        }
661        self.proto.get_public_key(args, originator).await
662    }
663
664    async fn encrypt(
665        &self,
666        args: EncryptArgs,
667        originator: Option<&str>,
668    ) -> Result<EncryptResult, SdkWalletError> {
669        self.validate_originator(originator).map_err(to_sdk_error)?;
670        bsv::wallet::validation::validate_encrypt_args(&args)?;
671
672        if args.privileged {
673            if let Some(ref pkm) = self.privileged_key_manager {
674                return pkm.encrypt(args).await;
675            }
676            return Err(SdkWalletError::Internal(
677                "No privileged key manager configured".to_string(),
678            ));
679        }
680        self.proto.encrypt(args, originator).await
681    }
682
683    async fn decrypt(
684        &self,
685        args: DecryptArgs,
686        originator: Option<&str>,
687    ) -> Result<DecryptResult, SdkWalletError> {
688        self.validate_originator(originator).map_err(to_sdk_error)?;
689        bsv::wallet::validation::validate_decrypt_args(&args)?;
690
691        if args.privileged {
692            if let Some(ref pkm) = self.privileged_key_manager {
693                return pkm.decrypt(args).await;
694            }
695            return Err(SdkWalletError::Internal(
696                "No privileged key manager configured".to_string(),
697            ));
698        }
699        self.proto.decrypt(args, originator).await
700    }
701
702    async fn create_hmac(
703        &self,
704        args: CreateHmacArgs,
705        originator: Option<&str>,
706    ) -> Result<CreateHmacResult, SdkWalletError> {
707        self.validate_originator(originator).map_err(to_sdk_error)?;
708        bsv::wallet::validation::validate_create_hmac_args(&args)?;
709
710        if args.privileged {
711            if let Some(ref pkm) = self.privileged_key_manager {
712                return pkm.create_hmac(args).await;
713            }
714            return Err(SdkWalletError::Internal(
715                "No privileged key manager configured".to_string(),
716            ));
717        }
718        self.proto.create_hmac(args, originator).await
719    }
720
721    async fn verify_hmac(
722        &self,
723        args: VerifyHmacArgs,
724        originator: Option<&str>,
725    ) -> Result<VerifyHmacResult, SdkWalletError> {
726        self.validate_originator(originator).map_err(to_sdk_error)?;
727        bsv::wallet::validation::validate_verify_hmac_args(&args)?;
728
729        if args.privileged {
730            if let Some(ref pkm) = self.privileged_key_manager {
731                return pkm.verify_hmac(args).await;
732            }
733            return Err(SdkWalletError::Internal(
734                "No privileged key manager configured".to_string(),
735            ));
736        }
737        self.proto.verify_hmac(args, originator).await
738    }
739
740    async fn create_signature(
741        &self,
742        args: CreateSignatureArgs,
743        originator: Option<&str>,
744    ) -> Result<CreateSignatureResult, SdkWalletError> {
745        self.validate_originator(originator).map_err(to_sdk_error)?;
746        bsv::wallet::validation::validate_create_signature_args(&args)?;
747
748        if args.privileged {
749            if let Some(ref pkm) = self.privileged_key_manager {
750                return pkm.create_signature(args).await;
751            }
752            return Err(SdkWalletError::Internal(
753                "No privileged key manager configured".to_string(),
754            ));
755        }
756        self.proto.create_signature(args, originator).await
757    }
758
759    async fn verify_signature(
760        &self,
761        args: VerifySignatureArgs,
762        originator: Option<&str>,
763    ) -> Result<VerifySignatureResult, SdkWalletError> {
764        self.validate_originator(originator).map_err(to_sdk_error)?;
765        bsv::wallet::validation::validate_verify_signature_args(&args)?;
766
767        if args.privileged {
768            if let Some(ref pkm) = self.privileged_key_manager {
769                return pkm.verify_signature(args).await;
770            }
771            return Err(SdkWalletError::Internal(
772                "No privileged key manager configured".to_string(),
773            ));
774        }
775        self.proto.verify_signature(args, originator).await
776    }
777
778    async fn reveal_counterparty_key_linkage(
779        &self,
780        args: RevealCounterpartyKeyLinkageArgs,
781        originator: Option<&str>,
782    ) -> Result<RevealCounterpartyKeyLinkageResult, SdkWalletError> {
783        self.validate_originator(originator).map_err(to_sdk_error)?;
784        bsv::wallet::validation::validate_reveal_counterparty_key_linkage_args(&args)?;
785
786        if args.privileged.unwrap_or(false) {
787            if let Some(ref pkm) = self.privileged_key_manager {
788                return pkm.reveal_counterparty_key_linkage(args).await;
789            }
790            return Err(SdkWalletError::Internal(
791                "No privileged key manager configured".to_string(),
792            ));
793        }
794        self.proto
795            .reveal_counterparty_key_linkage(args, originator)
796            .await
797    }
798
799    async fn reveal_specific_key_linkage(
800        &self,
801        args: RevealSpecificKeyLinkageArgs,
802        originator: Option<&str>,
803    ) -> Result<RevealSpecificKeyLinkageResult, SdkWalletError> {
804        self.validate_originator(originator).map_err(to_sdk_error)?;
805        bsv::wallet::validation::validate_reveal_specific_key_linkage_args(&args)?;
806
807        if args.privileged.unwrap_or(false) {
808            if let Some(ref pkm) = self.privileged_key_manager {
809                return pkm.reveal_specific_key_linkage(args).await;
810            }
811            return Err(SdkWalletError::Internal(
812                "No privileged key manager configured".to_string(),
813            ));
814        }
815        self.proto
816            .reveal_specific_key_linkage(args, originator)
817            .await
818    }
819
820    // -----------------------------------------------------------------------
821    // INFO methods (6)
822    // -----------------------------------------------------------------------
823
824    async fn is_authenticated(
825        &self,
826        originator: Option<&str>,
827    ) -> Result<AuthenticatedResult, SdkWalletError> {
828        self.validate_originator(originator).map_err(to_sdk_error)?;
829        Ok(AuthenticatedResult {
830            authenticated: true,
831        })
832    }
833
834    async fn wait_for_authentication(
835        &self,
836        originator: Option<&str>,
837    ) -> Result<AuthenticatedResult, SdkWalletError> {
838        self.validate_originator(originator).map_err(to_sdk_error)?;
839        Ok(AuthenticatedResult {
840            authenticated: true,
841        })
842    }
843
844    async fn get_height(
845        &self,
846        originator: Option<&str>,
847    ) -> Result<GetHeightResult, SdkWalletError> {
848        self.validate_originator(originator).map_err(to_sdk_error)?;
849        let services = self.get_services().map_err(to_sdk_error)?;
850        let height = services
851            .get_height()
852            .await
853            .map_err(|e| SdkWalletError::Internal(e.to_string()))?;
854        Ok(GetHeightResult { height })
855    }
856
857    async fn get_header_for_height(
858        &self,
859        args: GetHeaderArgs,
860        originator: Option<&str>,
861    ) -> Result<GetHeaderResult, SdkWalletError> {
862        self.validate_originator(originator).map_err(to_sdk_error)?;
863        bsv::wallet::validation::validate_get_header_args(&args)?;
864        let services = self.get_services().map_err(to_sdk_error)?;
865        let header = services
866            .get_header_for_height(args.height)
867            .await
868            .map_err(|e| SdkWalletError::Internal(e.to_string()))?;
869        Ok(GetHeaderResult { header })
870    }
871
872    async fn get_network(
873        &self,
874        originator: Option<&str>,
875    ) -> Result<GetNetworkResult, SdkWalletError> {
876        self.validate_originator(originator).map_err(to_sdk_error)?;
877        let network = match self.chain {
878            Chain::Main => Network::Mainnet,
879            Chain::Test => Network::Testnet,
880        };
881        Ok(GetNetworkResult { network })
882    }
883
884    async fn get_version(
885        &self,
886        originator: Option<&str>,
887    ) -> Result<GetVersionResult, SdkWalletError> {
888        self.validate_originator(originator).map_err(to_sdk_error)?;
889        Ok(GetVersionResult {
890            version: "wallet-brc100-1.0.0".to_string(),
891        })
892    }
893
894    // -----------------------------------------------------------------------
895    // ACTION methods (4) -- delegate to signer
896    // -----------------------------------------------------------------------
897
898    async fn create_action(
899        &self,
900        args: CreateActionArgs,
901        originator: Option<&str>,
902    ) -> Result<CreateActionResult, SdkWalletError> {
903        self.validate_originator(originator).map_err(to_sdk_error)?;
904        bsv::wallet::validation::validate_create_action_args(&args)?;
905
906        // Merge wallet defaults into options
907        let mut options = args.options.clone().unwrap_or_default();
908
909        // Merge trust_self from wallet defaults if not set
910        if options.trust_self.is_none() {
911            options.trust_self = self.trust_self.clone();
912        }
913
914        // Merge auto_known_txids: add wallet's BeefParty known txids
915        if self.auto_known_txids {
916            let mut beef_lock = self.beef.lock().await;
917            let known = crate::wallet::beef_helpers::get_known_txids(
918                &mut beef_lock,
919                Some(&options.known_txids),
920            );
921            options.known_txids = known;
922        }
923
924        // Build validated args for signer
925        let sign_and_process = *options.sign_and_process;
926        let no_send = *options.no_send;
927        let accept_delayed = *options.accept_delayed_broadcast;
928
929        let valid_args = crate::signer::types::ValidCreateActionArgs {
930            description: args.description,
931            inputs: args
932                .inputs
933                .iter()
934                .map(|input| {
935                    let parts: Vec<&str> = input.outpoint.rsplitn(2, '.').collect();
936                    let (vout_str, txid_str) = if parts.len() == 2 {
937                        (parts[0], parts[1])
938                    } else {
939                        ("0", input.outpoint.as_str())
940                    };
941                    crate::signer::types::ValidCreateActionInput {
942                        outpoint: crate::signer::types::OutpointInfo {
943                            txid: txid_str.to_string(),
944                            vout: vout_str.parse().unwrap_or(0),
945                        },
946                        input_description: input.input_description.clone(),
947                        unlocking_script: input.unlocking_script.clone(),
948                        unlocking_script_length: input.unlocking_script_length.unwrap_or(0)
949                            as usize,
950                        sequence_number: input.sequence_number.unwrap_or(0xffffffff),
951                    }
952                })
953                .collect(),
954            outputs: args.outputs,
955            lock_time: args.lock_time.unwrap_or(0),
956            version: args.version.unwrap_or(1),
957            labels: args.labels,
958            options: options.clone(),
959            input_beef: args.input_beef,
960            is_new_tx: true,
961            is_sign_action: !sign_and_process,
962            is_no_send: no_send,
963            is_delayed: !accept_delayed,
964            is_send_with: !options.send_with.is_empty(),
965        };
966
967        let signer_result = self
968            .signer
969            .create_action(valid_args)
970            .await
971            .map_err(to_sdk_error)?;
972
973        // Convert signer result to SDK result
974        let mut result = CreateActionResult {
975            txid: signer_result.txid,
976            tx: signer_result.tx,
977            no_send_change: signer_result.no_send_change,
978            send_with_results: signer_result.send_with_results,
979            signable_transaction: signer_result.signable_transaction.map(|st| {
980                bsv::wallet::interfaces::SignableTransaction {
981                    reference: st.reference.into_bytes(),
982                    tx: st.tx,
983                }
984            }),
985        };
986
987        // Merge result beef into BeefParty
988        if let Some(ref tx_bytes) = result.tx {
989            let mut beef_lock = self.beef.lock().await;
990            if let Ok(beef) =
991                bsv::transaction::beef::Beef::from_binary(&mut std::io::Cursor::new(tx_bytes))
992            {
993                let _ = beef_lock.beef.merge_beef(&beef);
994            }
995        }
996
997        // Verify returned txid-only if applicable
998        if let Some(ref mut tx_bytes) = result.tx {
999            let beef_lock = self.beef.lock().await;
1000            let verified = crate::wallet::beef_helpers::verify_returned_txid_only_atomic_beef(
1001                tx_bytes,
1002                &beef_lock,
1003                self.return_txid_only,
1004                None,
1005            )?;
1006            *tx_bytes = verified;
1007        }
1008
1009        // Check for unsuccessful results
1010        crate::wallet::error_helpers::throw_if_any_unsuccessful_create_actions(&result)?;
1011
1012        Ok(result)
1013    }
1014
1015    async fn sign_action(
1016        &self,
1017        args: SignActionArgs,
1018        originator: Option<&str>,
1019    ) -> Result<SignActionResult, SdkWalletError> {
1020        self.validate_originator(originator).map_err(to_sdk_error)?;
1021        bsv::wallet::validation::validate_sign_action_args(&args)?;
1022
1023        let reference = String::from_utf8_lossy(&args.reference).to_string();
1024        let raw_options = &args.options;
1025        let options = raw_options.clone().unwrap_or_default();
1026
1027        // Derive Option<bool> flags from raw options, preserving None when the
1028        // caller didn't specify a value.  This enables mergePriorOptions in
1029        // sign_action.rs: None means "inherit from createAction", Some(v) means
1030        // "caller explicitly set this".
1031        let (is_no_send, is_delayed, is_send_with) = match raw_options {
1032            Some(opts) => (
1033                opts.no_send.0,
1034                opts.accept_delayed_broadcast.0.map(|abd| !abd),
1035                if opts.send_with.is_empty() {
1036                    None
1037                } else {
1038                    Some(true)
1039                },
1040            ),
1041            None => (None, None, None),
1042        };
1043
1044        let valid_args = crate::signer::types::ValidSignActionArgs {
1045            reference: reference.clone(),
1046            spends: args.spends,
1047            options,
1048            is_new_tx: true,
1049            is_no_send,
1050            is_delayed,
1051            is_send_with,
1052        };
1053
1054        let signer_result = self
1055            .signer
1056            .sign_action(valid_args)
1057            .await
1058            .map_err(to_sdk_error)?;
1059
1060        let mut result = SignActionResult {
1061            txid: signer_result.txid,
1062            tx: signer_result.tx,
1063            send_with_results: signer_result.send_with_results,
1064        };
1065
1066        // Verify returned txid-only if applicable
1067        if let Some(ref mut tx_bytes) = result.tx {
1068            let beef_lock = self.beef.lock().await;
1069            let verified = crate::wallet::beef_helpers::verify_returned_txid_only_atomic_beef(
1070                tx_bytes,
1071                &beef_lock,
1072                self.return_txid_only,
1073                None,
1074            )?;
1075            *tx_bytes = verified;
1076        }
1077
1078        // Check for unsuccessful results
1079        crate::wallet::error_helpers::throw_if_any_unsuccessful_sign_actions(&result)?;
1080
1081        Ok(result)
1082    }
1083
1084    async fn internalize_action(
1085        &self,
1086        args: InternalizeActionArgs,
1087        originator: Option<&str>,
1088    ) -> Result<InternalizeActionResult, SdkWalletError> {
1089        self.validate_originator(originator).map_err(to_sdk_error)?;
1090        bsv::wallet::validation::validate_internalize_action_args(&args)?;
1091
1092        // Check specOp throw review actions label
1093        for label in &args.labels {
1094            if crate::wallet::types::is_spec_op_throw_label(label) {
1095                return Err(SdkWalletError::Internal(
1096                    "WERR_REVIEW_ACTIONS: internalizeAction specOp throw review actions"
1097                        .to_string(),
1098                ));
1099            }
1100        }
1101
1102        // Pass SDK InternalizeOutput enum directly through to signer
1103        let valid_args = crate::signer::types::ValidInternalizeActionArgs {
1104            tx: args.tx,
1105            description: args.description,
1106            labels: args.labels,
1107            outputs: args.outputs,
1108        };
1109
1110        let signer_result = self
1111            .signer
1112            .internalize_action(valid_args)
1113            .await
1114            .map_err(to_sdk_error)?;
1115
1116        let result = InternalizeActionResult {
1117            accepted: signer_result.accepted,
1118        };
1119
1120        crate::wallet::error_helpers::throw_if_unsuccessful_internalize_action(&result)?;
1121
1122        Ok(result)
1123    }
1124
1125    async fn abort_action(
1126        &self,
1127        args: AbortActionArgs,
1128        originator: Option<&str>,
1129    ) -> Result<AbortActionResult, SdkWalletError> {
1130        self.validate_originator(originator).map_err(to_sdk_error)?;
1131        bsv::wallet::validation::validate_abort_action_args(&args)?;
1132
1133        let auth = self.auth_id();
1134        self.storage
1135            .abort_action(&auth, &args)
1136            .await
1137            .map_err(to_sdk_error)
1138    }
1139
1140    // -----------------------------------------------------------------------
1141    // QUERY methods (3) -- delegate to storage
1142    // -----------------------------------------------------------------------
1143
1144    async fn list_actions(
1145        &self,
1146        args: ListActionsArgs,
1147        originator: Option<&str>,
1148    ) -> Result<ListActionsResult, SdkWalletError> {
1149        self.validate_originator(originator).map_err(to_sdk_error)?;
1150        bsv::wallet::validation::validate_list_actions_args(&args)?;
1151
1152        let auth = self.auth_id();
1153        let mut result = self
1154            .storage
1155            .list_actions(&auth, &args)
1156            .await
1157            .map_err(to_sdk_error)?;
1158
1159        // Strip customInstructions from outputs (security policy)
1160        for action in &mut result.actions {
1161            for output in &mut action.outputs {
1162                output.custom_instructions = None;
1163            }
1164        }
1165
1166        Ok(result)
1167    }
1168
1169    async fn list_outputs(
1170        &self,
1171        args: ListOutputsArgs,
1172        originator: Option<&str>,
1173    ) -> Result<ListOutputsResult, SdkWalletError> {
1174        use bsv::transaction::beef::{Beef, BEEF_V2};
1175        use bsv::wallet::interfaces::OutputInclude;
1176        use std::collections::HashSet;
1177        use std::io::Cursor;
1178
1179        self.validate_originator(originator).map_err(to_sdk_error)?;
1180        bsv::wallet::validation::validate_list_outputs_args(&args)?;
1181
1182        let auth = self.auth_id();
1183        let mut result = self
1184            .storage
1185            .list_outputs(&auth, &args)
1186            .await
1187            .map_err(to_sdk_error)?;
1188
1189        // BEEF assembly at the wallet layer for EntireTransactions requests.
1190        // The storage layer returns beef = None (stub); we build it here where
1191        // we have access to StorageProvider (required by get_valid_beef_for_txid).
1192        if matches!(args.include, Some(OutputInclude::EntireTransactions)) && result.beef.is_none()
1193        {
1194            // Collect unique txids from outpoints (format: "txid.vout")
1195            let mut unique_txids: Vec<String> = Vec::new();
1196            let mut seen: HashSet<String> = HashSet::new();
1197            for output in &result.outputs {
1198                if let Some(dot_pos) = output.outpoint.find('.') {
1199                    let txid = output.outpoint[..dot_pos].to_string();
1200                    if seen.insert(txid.clone()) {
1201                        unique_txids.push(txid);
1202                    }
1203                }
1204            }
1205
1206            if !unique_txids.is_empty() {
1207                // Map SDK TrustSelf to local beef TrustSelf
1208                let beef_trust_self = match &self.trust_self {
1209                    Some(TrustSelf::Known) => crate::storage::beef::TrustSelf::Known,
1210                    None => crate::storage::beef::TrustSelf::No,
1211                };
1212                let known_txids: HashSet<String> = HashSet::new();
1213                let storage_provider = self.storage.get_active().await.map_err(to_sdk_error)?;
1214
1215                // Build a single merged BEEF from all txids
1216                let mut merged_beef = Beef::new(BEEF_V2);
1217                for txid in &unique_txids {
1218                    let beef_bytes_opt = crate::storage::beef::get_valid_beef_for_txid(
1219                        storage_provider.as_ref(),
1220                        txid,
1221                        beef_trust_self,
1222                        &known_txids,
1223                    )
1224                    .await
1225                    .map_err(to_sdk_error)?;
1226
1227                    if let Some(beef_bytes) = beef_bytes_opt {
1228                        // Parse the returned BEEF and merge bumps and txs
1229                        let mut cursor = Cursor::new(&beef_bytes);
1230                        let parsed = Beef::from_binary(&mut cursor).map_err(|e| {
1231                            to_sdk_error(crate::error::WalletError::Internal(format!(
1232                                "Failed to parse BEEF for {}: {}",
1233                                txid, e
1234                            )))
1235                        })?;
1236                        // Merge bumps
1237                        let bump_offset = merged_beef.bumps.len();
1238                        merged_beef.bumps.extend(parsed.bumps);
1239                        // Merge txs, adjusting bump_index offsets
1240                        for mut beef_tx in parsed.txs {
1241                            if let Some(ref mut idx) = beef_tx.bump_index {
1242                                *idx += bump_offset;
1243                            }
1244                            // Only add if not already present (dedup by txid)
1245                            let tx_txid = beef_tx.txid.clone();
1246                            if !merged_beef.txs.iter().any(|t| t.txid == tx_txid) {
1247                                merged_beef.txs.push(beef_tx);
1248                            }
1249                        }
1250                    }
1251                }
1252
1253                if !merged_beef.txs.is_empty() {
1254                    let mut buf = Vec::new();
1255                    merged_beef.to_binary(&mut buf).map_err(|e| {
1256                        to_sdk_error(crate::error::WalletError::Internal(format!(
1257                            "Failed to serialize merged BEEF: {}",
1258                            e
1259                        )))
1260                    })?;
1261                    result.beef = Some(buf);
1262                }
1263            }
1264        }
1265
1266        // Merge BEEF into BeefParty and verify returned txid-only
1267        if let Some(ref mut beef_bytes) = result.beef {
1268            let beef_lock = self.beef.lock().await;
1269            let verified = crate::wallet::beef_helpers::verify_returned_txid_only_beef(
1270                beef_bytes,
1271                &beef_lock,
1272                self.return_txid_only,
1273            )?;
1274            *beef_bytes = verified;
1275        }
1276
1277        Ok(result)
1278    }
1279
1280    async fn list_certificates(
1281        &self,
1282        args: ListCertificatesArgs,
1283        originator: Option<&str>,
1284    ) -> Result<ListCertificatesResult, SdkWalletError> {
1285        self.validate_originator(originator).map_err(to_sdk_error)?;
1286        bsv::wallet::validation::validate_list_certificates_args(&args)?;
1287
1288        let auth = self.auth_id();
1289        self.storage
1290            .list_certificates(&auth, &args)
1291            .await
1292            .map_err(to_sdk_error)
1293    }
1294
1295    // -----------------------------------------------------------------------
1296    // RELINQUISH methods (2) -- delegate to storage
1297    // -----------------------------------------------------------------------
1298
1299    async fn relinquish_output(
1300        &self,
1301        args: RelinquishOutputArgs,
1302        originator: Option<&str>,
1303    ) -> Result<RelinquishOutputResult, SdkWalletError> {
1304        self.validate_originator(originator).map_err(to_sdk_error)?;
1305        bsv::wallet::validation::validate_relinquish_output_args(&args)?;
1306
1307        let auth = self.auth_id();
1308        self.storage
1309            .relinquish_output(&auth, &args)
1310            .await
1311            .map(|_| bsv::wallet::interfaces::RelinquishOutputResult { relinquished: true })
1312            .map_err(to_sdk_error)
1313    }
1314
1315    async fn relinquish_certificate(
1316        &self,
1317        args: RelinquishCertificateArgs,
1318        originator: Option<&str>,
1319    ) -> Result<RelinquishCertificateResult, SdkWalletError> {
1320        self.validate_originator(originator).map_err(to_sdk_error)?;
1321        bsv::wallet::validation::validate_relinquish_certificate_args(&args)?;
1322
1323        let auth = self.auth_id();
1324        self.storage
1325            .relinquish_certificate(&auth, &args)
1326            .await
1327            .map(|_| bsv::wallet::interfaces::RelinquishCertificateResult { relinquished: true })
1328            .map_err(to_sdk_error)
1329    }
1330
1331    // -----------------------------------------------------------------------
1332    // CERTIFICATE methods (4) -- Plans 04/05
1333    // -----------------------------------------------------------------------
1334
1335    async fn acquire_certificate(
1336        &self,
1337        args: AcquireCertificateArgs,
1338        originator: Option<&str>,
1339    ) -> Result<Certificate, SdkWalletError> {
1340        self.validate_originator(originator).map_err(to_sdk_error)?;
1341        bsv::wallet::validation::validate_acquire_certificate_args(&args)?;
1342
1343        let auth = self.auth_id();
1344
1345        let result = match args.acquisition_protocol {
1346            AcquisitionProtocol::Direct => crate::wallet::certificates::acquire_direct_certificate(
1347                &self.storage,
1348                self,
1349                &auth,
1350                &args,
1351            )
1352            .await
1353            .map_err(to_sdk_error)?,
1354            AcquisitionProtocol::Issuance => {
1355                crate::wallet::certificates::acquire_issuance_certificate(
1356                    &self.storage,
1357                    self,
1358                    &auth,
1359                    &args,
1360                )
1361                .await
1362                .map_err(to_sdk_error)?
1363            }
1364        };
1365
1366        // Convert AcquireCertificateResult to SDK Certificate
1367        let subject_pk = PublicKey::from_string(&result.subject)
1368            .map_err(|e| SdkWalletError::Internal(format!("Invalid subject key: {}", e)))?;
1369        let certifier_pk = PublicKey::from_string(&result.certifier)
1370            .map_err(|e| SdkWalletError::Internal(format!("Invalid certifier key: {}", e)))?;
1371
1372        Ok(Certificate {
1373            cert_type: args.cert_type,
1374            serial_number: args
1375                .serial_number
1376                .unwrap_or(bsv::wallet::interfaces::SerialNumber([0u8; 32])),
1377            subject: subject_pk,
1378            certifier: certifier_pk,
1379            revocation_outpoint: Some(result.revocation_outpoint),
1380            fields: Some(result.fields),
1381            signature: args.signature,
1382        })
1383    }
1384
1385    async fn prove_certificate(
1386        &self,
1387        args: ProveCertificateArgs,
1388        originator: Option<&str>,
1389    ) -> Result<ProveCertificateResult, SdkWalletError> {
1390        self.validate_originator(originator).map_err(to_sdk_error)?;
1391        bsv::wallet::validation::validate_prove_certificate_args(&args)?;
1392
1393        let auth = self.auth_id();
1394        crate::wallet::certificates::prove_certificate(&self.storage, self, &auth, &args)
1395            .await
1396            .map_err(to_sdk_error)
1397    }
1398
1399    async fn discover_by_identity_key(
1400        &self,
1401        args: DiscoverByIdentityKeyArgs,
1402        originator: Option<&str>,
1403    ) -> Result<DiscoverCertificatesResult, SdkWalletError> {
1404        self.validate_originator(originator).map_err(to_sdk_error)?;
1405        bsv::wallet::validation::validate_discover_by_identity_key_args(&args)?;
1406
1407        let resolver = self
1408            .lookup_resolver
1409            .as_ref()
1410            .ok_or_else(|| SdkWalletError::Internal("No lookup resolver configured".to_string()))?;
1411
1412        crate::wallet::discovery::discover_by_identity_key(
1413            &self.settings_manager,
1414            resolver,
1415            &self.overlay_cache,
1416            &args,
1417        )
1418        .await
1419        .map_err(to_sdk_error)
1420    }
1421
1422    async fn discover_by_attributes(
1423        &self,
1424        args: DiscoverByAttributesArgs,
1425        originator: Option<&str>,
1426    ) -> Result<DiscoverCertificatesResult, SdkWalletError> {
1427        self.validate_originator(originator).map_err(to_sdk_error)?;
1428        bsv::wallet::validation::validate_discover_by_attributes_args(&args)?;
1429
1430        let resolver = self
1431            .lookup_resolver
1432            .as_ref()
1433            .ok_or_else(|| SdkWalletError::Internal("No lookup resolver configured".to_string()))?;
1434
1435        crate::wallet::discovery::discover_by_attributes(
1436            &self.settings_manager,
1437            resolver,
1438            &self.overlay_cache,
1439            &args,
1440        )
1441        .await
1442        .map_err(to_sdk_error)
1443    }
1444}
1445
1446// ---------------------------------------------------------------------------
1447// WalletArc -- Clone-able newtype for Wallet
1448// ---------------------------------------------------------------------------
1449//
1450// AuthMiddlewareFactory requires W: Clone, but Wallet cannot implement Clone
1451// (WalletStorageManager contains Mutex). We use a newtype around Arc<Wallet>
1452// that is Clone and implements WalletInterface by delegating to the inner Wallet.
1453// This satisfies orphan rules (WalletArc is a local type).
1454
1455/// Clone-able wrapper around `Arc<Wallet>` implementing `WalletInterface`.
1456///
1457/// Needed because `Wallet` cannot implement `Clone` (contains `Mutex`),
1458/// but `AuthMiddlewareFactory` requires `W: WalletInterface + Clone`.
1459#[derive(Clone)]
1460pub struct WalletArc(pub Arc<Wallet>);
1461
1462impl WalletArc {
1463    /// Create a new WalletArc from an existing Arc<Wallet>.
1464    pub fn new(wallet: Arc<Wallet>) -> Self {
1465        Self(wallet)
1466    }
1467}
1468
1469#[async_trait]
1470impl WalletInterface for WalletArc {
1471    async fn get_public_key(
1472        &self,
1473        args: GetPublicKeyArgs,
1474        originator: Option<&str>,
1475    ) -> Result<GetPublicKeyResult, SdkWalletError> {
1476        self.0.as_ref().get_public_key(args, originator).await
1477    }
1478
1479    async fn encrypt(
1480        &self,
1481        args: EncryptArgs,
1482        originator: Option<&str>,
1483    ) -> Result<EncryptResult, SdkWalletError> {
1484        self.0.as_ref().encrypt(args, originator).await
1485    }
1486
1487    async fn decrypt(
1488        &self,
1489        args: DecryptArgs,
1490        originator: Option<&str>,
1491    ) -> Result<DecryptResult, SdkWalletError> {
1492        self.0.as_ref().decrypt(args, originator).await
1493    }
1494
1495    async fn create_hmac(
1496        &self,
1497        args: CreateHmacArgs,
1498        originator: Option<&str>,
1499    ) -> Result<CreateHmacResult, SdkWalletError> {
1500        self.0.as_ref().create_hmac(args, originator).await
1501    }
1502
1503    async fn verify_hmac(
1504        &self,
1505        args: VerifyHmacArgs,
1506        originator: Option<&str>,
1507    ) -> Result<VerifyHmacResult, SdkWalletError> {
1508        self.0.as_ref().verify_hmac(args, originator).await
1509    }
1510
1511    async fn create_signature(
1512        &self,
1513        args: CreateSignatureArgs,
1514        originator: Option<&str>,
1515    ) -> Result<CreateSignatureResult, SdkWalletError> {
1516        self.0.as_ref().create_signature(args, originator).await
1517    }
1518
1519    async fn verify_signature(
1520        &self,
1521        args: VerifySignatureArgs,
1522        originator: Option<&str>,
1523    ) -> Result<VerifySignatureResult, SdkWalletError> {
1524        self.0.as_ref().verify_signature(args, originator).await
1525    }
1526
1527    async fn reveal_counterparty_key_linkage(
1528        &self,
1529        args: RevealCounterpartyKeyLinkageArgs,
1530        originator: Option<&str>,
1531    ) -> Result<RevealCounterpartyKeyLinkageResult, SdkWalletError> {
1532        self.0
1533            .as_ref()
1534            .reveal_counterparty_key_linkage(args, originator)
1535            .await
1536    }
1537
1538    async fn reveal_specific_key_linkage(
1539        &self,
1540        args: RevealSpecificKeyLinkageArgs,
1541        originator: Option<&str>,
1542    ) -> Result<RevealSpecificKeyLinkageResult, SdkWalletError> {
1543        self.0
1544            .as_ref()
1545            .reveal_specific_key_linkage(args, originator)
1546            .await
1547    }
1548
1549    async fn is_authenticated(
1550        &self,
1551        originator: Option<&str>,
1552    ) -> Result<AuthenticatedResult, SdkWalletError> {
1553        self.0.as_ref().is_authenticated(originator).await
1554    }
1555
1556    async fn wait_for_authentication(
1557        &self,
1558        originator: Option<&str>,
1559    ) -> Result<AuthenticatedResult, SdkWalletError> {
1560        self.0.as_ref().wait_for_authentication(originator).await
1561    }
1562
1563    async fn get_height(
1564        &self,
1565        originator: Option<&str>,
1566    ) -> Result<GetHeightResult, SdkWalletError> {
1567        self.0.as_ref().get_height(originator).await
1568    }
1569
1570    async fn get_header_for_height(
1571        &self,
1572        args: GetHeaderArgs,
1573        originator: Option<&str>,
1574    ) -> Result<GetHeaderResult, SdkWalletError> {
1575        self.0
1576            .as_ref()
1577            .get_header_for_height(args, originator)
1578            .await
1579    }
1580
1581    async fn get_network(
1582        &self,
1583        originator: Option<&str>,
1584    ) -> Result<GetNetworkResult, SdkWalletError> {
1585        self.0.as_ref().get_network(originator).await
1586    }
1587
1588    async fn get_version(
1589        &self,
1590        originator: Option<&str>,
1591    ) -> Result<GetVersionResult, SdkWalletError> {
1592        self.0.as_ref().get_version(originator).await
1593    }
1594
1595    async fn create_action(
1596        &self,
1597        args: CreateActionArgs,
1598        originator: Option<&str>,
1599    ) -> Result<CreateActionResult, SdkWalletError> {
1600        self.0.as_ref().create_action(args, originator).await
1601    }
1602
1603    async fn sign_action(
1604        &self,
1605        args: SignActionArgs,
1606        originator: Option<&str>,
1607    ) -> Result<SignActionResult, SdkWalletError> {
1608        self.0.as_ref().sign_action(args, originator).await
1609    }
1610
1611    async fn internalize_action(
1612        &self,
1613        args: InternalizeActionArgs,
1614        originator: Option<&str>,
1615    ) -> Result<InternalizeActionResult, SdkWalletError> {
1616        self.0.as_ref().internalize_action(args, originator).await
1617    }
1618
1619    async fn abort_action(
1620        &self,
1621        args: AbortActionArgs,
1622        originator: Option<&str>,
1623    ) -> Result<AbortActionResult, SdkWalletError> {
1624        self.0.as_ref().abort_action(args, originator).await
1625    }
1626
1627    async fn list_actions(
1628        &self,
1629        args: ListActionsArgs,
1630        originator: Option<&str>,
1631    ) -> Result<ListActionsResult, SdkWalletError> {
1632        self.0.as_ref().list_actions(args, originator).await
1633    }
1634
1635    async fn list_outputs(
1636        &self,
1637        args: ListOutputsArgs,
1638        originator: Option<&str>,
1639    ) -> Result<ListOutputsResult, SdkWalletError> {
1640        self.0.as_ref().list_outputs(args, originator).await
1641    }
1642
1643    async fn list_certificates(
1644        &self,
1645        args: ListCertificatesArgs,
1646        originator: Option<&str>,
1647    ) -> Result<ListCertificatesResult, SdkWalletError> {
1648        self.0.as_ref().list_certificates(args, originator).await
1649    }
1650
1651    async fn relinquish_output(
1652        &self,
1653        args: RelinquishOutputArgs,
1654        originator: Option<&str>,
1655    ) -> Result<RelinquishOutputResult, SdkWalletError> {
1656        self.0.as_ref().relinquish_output(args, originator).await
1657    }
1658
1659    async fn relinquish_certificate(
1660        &self,
1661        args: RelinquishCertificateArgs,
1662        originator: Option<&str>,
1663    ) -> Result<RelinquishCertificateResult, SdkWalletError> {
1664        self.0
1665            .as_ref()
1666            .relinquish_certificate(args, originator)
1667            .await
1668    }
1669
1670    async fn acquire_certificate(
1671        &self,
1672        args: AcquireCertificateArgs,
1673        originator: Option<&str>,
1674    ) -> Result<Certificate, SdkWalletError> {
1675        self.0.as_ref().acquire_certificate(args, originator).await
1676    }
1677
1678    async fn prove_certificate(
1679        &self,
1680        args: ProveCertificateArgs,
1681        originator: Option<&str>,
1682    ) -> Result<ProveCertificateResult, SdkWalletError> {
1683        self.0.as_ref().prove_certificate(args, originator).await
1684    }
1685
1686    async fn discover_by_identity_key(
1687        &self,
1688        args: DiscoverByIdentityKeyArgs,
1689        originator: Option<&str>,
1690    ) -> Result<DiscoverCertificatesResult, SdkWalletError> {
1691        self.0
1692            .as_ref()
1693            .discover_by_identity_key(args, originator)
1694            .await
1695    }
1696
1697    async fn discover_by_attributes(
1698        &self,
1699        args: DiscoverByAttributesArgs,
1700        originator: Option<&str>,
1701    ) -> Result<DiscoverCertificatesResult, SdkWalletError> {
1702        self.0
1703            .as_ref()
1704            .discover_by_attributes(args, originator)
1705            .await
1706    }
1707}