aztec_account/
single_account_provider.rs

1//! A simple [`AccountProvider`] that manages a single account.
2
3use async_trait::async_trait;
4
5use crate::account::{AccountContract, EntrypointOptions};
6use crate::error::Error;
7use crate::types::{AztecAddress, CompleteAddress};
8use crate::wallet::{AccountProvider, Aliased, ChainInfo, MessageHashOrIntent};
9use aztec_core::fee::GasSettings;
10use aztec_core::tx::{AuthWitness, ExecutionPayload};
11use aztec_pxe_client::TxExecutionRequest;
12
13/// A simple [`AccountProvider`] that manages a single account.
14///
15/// This is the most common pattern: one secret key, one account contract,
16/// one wallet. For multi-account wallets, implement [`AccountProvider`] directly.
17///
18/// Stores an [`AccountContract`] and creates fresh account instances on
19/// demand (avoiding the trait-object clone problem).
20pub struct SingleAccountProvider {
21    complete_address: CompleteAddress,
22    account_contract: Box<dyn AccountContract>,
23    alias: String,
24}
25
26impl SingleAccountProvider {
27    /// Create a new single-account provider.
28    pub fn new(
29        complete_address: CompleteAddress,
30        account_contract: Box<dyn AccountContract>,
31        alias: impl Into<String>,
32    ) -> Self {
33        Self {
34            complete_address,
35            account_contract,
36            alias: alias.into(),
37        }
38    }
39}
40
41#[async_trait]
42impl AccountProvider for SingleAccountProvider {
43    async fn create_tx_execution_request(
44        &self,
45        from: &AztecAddress,
46        exec: ExecutionPayload,
47        gas_settings: GasSettings,
48        chain_info: &ChainInfo,
49        fee_payer: Option<AztecAddress>,
50        fee_payment_method: Option<u8>,
51    ) -> Result<TxExecutionRequest, Error> {
52        if *from != self.complete_address.address {
53            return Err(Error::InvalidData(format!("account not found: {from}")));
54        }
55
56        let account = self.account_contract.account(self.complete_address.clone());
57        let options = EntrypointOptions {
58            fee_payer,
59            gas_settings: Some(gas_settings.clone()),
60            fee_payment_method,
61        };
62
63        let tx_request = account
64            .create_tx_execution_request(exec, gas_settings, chain_info, options)
65            .await?;
66
67        // Bridge from the account crate's structured TxExecutionRequest
68        // to the PXE crate's opaque JSON TxExecutionRequest
69        let data =
70            serde_json::to_value(&tx_request).map_err(|e| Error::InvalidData(e.to_string()))?;
71
72        Ok(TxExecutionRequest { data })
73    }
74
75    async fn create_auth_wit(
76        &self,
77        from: &AztecAddress,
78        intent: MessageHashOrIntent,
79        chain_info: &ChainInfo,
80    ) -> Result<AuthWitness, Error> {
81        if *from != self.complete_address.address {
82            return Err(Error::InvalidData(format!("account not found: {from}")));
83        }
84
85        // Resolve intent → hash so the AuthorizationProvider always receives
86        // a resolved hash to sign, keeping auth providers simple.
87        let resolved = match &intent {
88            MessageHashOrIntent::Hash { .. } => intent,
89            MessageHashOrIntent::Intent { .. } | MessageHashOrIntent::InnerHash { .. } => {
90                let hash = aztec_core::hash::compute_auth_wit_message_hash(&intent, chain_info);
91                MessageHashOrIntent::Hash { hash }
92            }
93        };
94
95        let account = self.account_contract.account(self.complete_address.clone());
96        let mut witness = account
97            .create_auth_wit(resolved.clone(), chain_info)
98            .await?;
99
100        // Set the request_hash on the witness so consumers can identify which
101        // message this witness authorizes.
102        if let MessageHashOrIntent::Hash { hash } = &resolved {
103            witness.request_hash = *hash;
104        }
105
106        Ok(witness)
107    }
108
109    async fn get_complete_address(
110        &self,
111        address: &AztecAddress,
112    ) -> Result<Option<CompleteAddress>, Error> {
113        if *address == self.complete_address.address {
114            Ok(Some(self.complete_address.clone()))
115        } else {
116            Ok(None)
117        }
118    }
119
120    async fn get_accounts(&self) -> Result<Vec<Aliased<AztecAddress>>, Error> {
121        Ok(vec![Aliased {
122            alias: self.alias.clone(),
123            item: self.complete_address.address,
124        }])
125    }
126}