aztec_account/
signerless.rs

1//! Signerless account for unsigned transactions.
2
3use async_trait::async_trait;
4
5use crate::account::{Account, AuthorizationProvider, EntrypointOptions};
6use crate::entrypoint::DefaultMultiCallEntrypoint;
7use crate::tx::{AuthWitness, ExecutionPayload};
8use crate::types::{AztecAddress, CompleteAddress};
9use crate::wallet::{ChainInfo, MessageHashOrIntent};
10
11use aztec_core::fee::GasSettings;
12use aztec_core::Error;
13
14use super::account::TxExecutionRequest;
15
16/// An account that requires no signing. Uses the `DefaultMultiCallEntrypoint`
17/// to batch function calls without authorization.
18///
19/// Used for:
20/// - Fee-sponsored transactions where the sponsor pays
21/// - Protocol-level operations that don't need account identity
22/// - Testing scenarios
23pub struct SignerlessAccount {
24    entrypoint: DefaultMultiCallEntrypoint,
25}
26
27impl SignerlessAccount {
28    /// Create a new signerless account.
29    pub fn new() -> Self {
30        Self {
31            entrypoint: DefaultMultiCallEntrypoint::new(),
32        }
33    }
34}
35
36impl Default for SignerlessAccount {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42#[async_trait]
43impl AuthorizationProvider for SignerlessAccount {
44    async fn create_auth_wit(
45        &self,
46        _intent: MessageHashOrIntent,
47        _chain_info: &ChainInfo,
48    ) -> Result<AuthWitness, Error> {
49        Err(Error::InvalidData(
50            "SignerlessAccount does not support authorization witnesses".into(),
51        ))
52    }
53}
54
55#[async_trait]
56impl Account for SignerlessAccount {
57    fn complete_address(&self) -> &CompleteAddress {
58        panic!("SignerlessAccount does not have a complete address")
59    }
60
61    fn address(&self) -> AztecAddress {
62        panic!("SignerlessAccount does not have an address")
63    }
64
65    async fn create_tx_execution_request(
66        &self,
67        exec: ExecutionPayload,
68        gas_settings: GasSettings,
69        chain_info: &ChainInfo,
70        _options: EntrypointOptions,
71    ) -> Result<TxExecutionRequest, Error> {
72        self.entrypoint
73            .create_tx_execution_request(exec, gas_settings, chain_info)
74    }
75
76    async fn wrap_execution_payload(
77        &self,
78        exec: ExecutionPayload,
79        _options: EntrypointOptions,
80    ) -> Result<ExecutionPayload, Error> {
81        self.entrypoint.wrap_execution_payload(exec)
82    }
83}
84
85#[cfg(test)]
86#[allow(clippy::unwrap_used, clippy::expect_used)]
87mod tests {
88    use super::*;
89    use aztec_core::abi::{AbiValue, FunctionSelector, FunctionType};
90    use aztec_core::constants::protocol_contract_address;
91    use aztec_core::fee::Gas;
92    use aztec_core::tx::FunctionCall;
93    use aztec_core::types::Fr;
94
95    fn sample_chain_info() -> ChainInfo {
96        ChainInfo {
97            chain_id: Fr::from(31337u64),
98            version: Fr::from(1u64),
99        }
100    }
101
102    #[tokio::test]
103    async fn create_auth_wit_returns_error() {
104        let account = SignerlessAccount::new();
105        let chain_info = sample_chain_info();
106        let result = account
107            .create_auth_wit(
108                MessageHashOrIntent::Hash {
109                    hash: Fr::from(1u64),
110                },
111                &chain_info,
112            )
113            .await;
114        assert!(result.is_err());
115        assert!(result.unwrap_err().to_string().contains("does not support"));
116    }
117
118    #[tokio::test]
119    async fn create_tx_execution_request_delegates_to_multi_call() {
120        let account = SignerlessAccount::new();
121        let chain_info = sample_chain_info();
122
123        let exec = ExecutionPayload {
124            calls: vec![FunctionCall {
125                to: AztecAddress::from(1u64),
126                selector: FunctionSelector::from_hex("0x11223344").expect("valid"),
127                args: vec![AbiValue::Field(Fr::from(42u64))],
128                function_type: FunctionType::Private,
129                is_static: false,
130                hide_msg_sender: false,
131            }],
132            ..Default::default()
133        };
134
135        let gas_settings = GasSettings {
136            gas_limits: Some(Gas {
137                da_gas: 100,
138                l2_gas: 200,
139            }),
140            ..GasSettings::default()
141        };
142
143        let req = account
144            .create_tx_execution_request(
145                exec,
146                gas_settings.clone(),
147                &chain_info,
148                EntrypointOptions::default(),
149            )
150            .await
151            .expect("create tx");
152
153        assert_eq!(
154            req.origin,
155            protocol_contract_address::multi_call_entrypoint()
156        );
157        assert_eq!(req.tx_context.gas_settings, gas_settings);
158        assert_eq!(req.args_of_calls.len(), 6);
159    }
160
161    #[tokio::test]
162    async fn wrap_execution_payload_delegates() {
163        let account = SignerlessAccount::new();
164        let exec = ExecutionPayload {
165            calls: vec![FunctionCall {
166                to: AztecAddress::from(1u64),
167                selector: FunctionSelector::from_hex("0x11223344").expect("valid"),
168                args: vec![],
169                function_type: FunctionType::Private,
170                is_static: false,
171                hide_msg_sender: false,
172            }],
173            ..Default::default()
174        };
175
176        let wrapped = account
177            .wrap_execution_payload(exec, EntrypointOptions::default())
178            .await
179            .expect("wrap");
180
181        assert_eq!(wrapped.calls.len(), 1);
182        assert_eq!(
183            wrapped.calls[0].to,
184            protocol_contract_address::multi_call_entrypoint()
185        );
186    }
187
188    #[test]
189    fn is_send_sync() {
190        fn assert_send_sync<T: Send + Sync>() {}
191        assert_send_sync::<SignerlessAccount>();
192    }
193
194    #[test]
195    fn trait_object_safety() {
196        fn _assert(_: Box<dyn Account>) {}
197    }
198
199    #[test]
200    fn default_impl() {
201        let _ = SignerlessAccount::default();
202    }
203}