1use async_trait::async_trait;
9use tokio::time::{sleep, Duration, Instant};
10
11use crate::abi::{AbiType, ContractArtifact, FunctionSelector};
12use crate::account_provider::AccountProvider;
13use crate::error::Error;
14use crate::node::{
15 create_aztec_node_client, wait_for_tx, AztecNode, HttpNodeClient, TxValidationResult, WaitOpts,
16};
17use crate::pxe::{self, Pxe, RegisterContractRequest};
18use crate::tx::{AuthWitness, ExecutionPayload, FunctionCall, TxHash, TxStatus};
19use crate::types::{AztecAddress, ContractInstanceWithAddress, Fr};
20use crate::wallet::{
21 Aliased, ChainInfo, ContractClassMetadata, ContractMetadata, EventMetadataDefinition,
22 ExecuteUtilityOptions, MessageHashOrIntent, PrivateEvent, PrivateEventFilter,
23 PrivateEventMetadata, ProfileOptions, SendOptions, SendResult, SimulateOptions,
24 TxProfileResult, TxSimulationResult, UtilityExecutionResult, Wallet,
25};
26use aztec_core::constants::protocol_contract_address;
27
28pub struct BaseWallet<P, N, A> {
35 pxe: P,
36 node: N,
37 accounts: A,
38}
39
40impl<P: Pxe, N: AztecNode, A: AccountProvider> BaseWallet<P, N, A> {
41 fn decode_assertion_message_from_error_types(
42 error_types: &serde_json::Value,
43 selector_key: &str,
44 ) -> Option<String> {
45 let entry = error_types.get(selector_key)?;
46 match entry.get("error_kind").and_then(|v| v.as_str()) {
47 Some("string") | Some("fmtstring") => entry
48 .get("string")
49 .and_then(|v| v.as_str())
50 .map(str::to_owned),
51 _ => None,
52 }
53 }
54
55 fn known_protocol_assertion_message(
56 contract_address: &AztecAddress,
57 selector_key: &str,
58 ) -> Option<String> {
59 if *contract_address == protocol_contract_address::auth_registry()
60 && selector_key == "17089945683942782951"
61 {
62 return Some("unauthorized".to_owned());
63 }
64 None
65 }
66
67 fn known_assertion_message_by_selector(selector_key: &str) -> Option<String> {
68 match selector_key {
69 "17089945683942782951" => Some("unauthorized".to_owned()),
70 "7136484461999155778" => Some(
71 "Invalid authwit nonce. When 'from' and 'msg_sender' are the same, 'authwit_nonce' must be zero"
72 .to_owned(),
73 ),
74 "1998584279744703196" => Some("attempt to subtract with overflow".to_owned()),
75 _ => None,
76 }
77 }
78
79 fn error_selector_key_from_hex(selector_hex: &str) -> Option<String> {
80 let raw = selector_hex.strip_prefix("0x").unwrap_or(selector_hex);
81 u128::from_str_radix(raw, 16)
82 .ok()
83 .map(|value| value.to_string())
84 }
85
86 async fn public_function_error_types(
87 &self,
88 contract_address: &AztecAddress,
89 function_selector: &FunctionSelector,
90 ) -> Result<Option<serde_json::Value>, Error> {
91 let Some(instance) = self.pxe.get_contract_instance(contract_address).await? else {
92 return Ok(None);
93 };
94 let Some(artifact) = self
95 .pxe
96 .get_contract_artifact(&instance.inner.current_contract_class_id)
97 .await?
98 else {
99 return Ok(None);
100 };
101
102 Ok(artifact
103 .find_function_by_selector(function_selector)
104 .and_then(|function| function.error_types.clone()))
105 }
106
107 async fn find_registered_error_message_by_selector(
108 &self,
109 selector_key: &str,
110 ) -> Result<Option<String>, Error> {
111 for address in self.pxe.get_contracts().await? {
112 let Some(instance) = self.pxe.get_contract_instance(&address).await? else {
113 continue;
114 };
115 let Some(artifact) = self
116 .pxe
117 .get_contract_artifact(&instance.inner.current_contract_class_id)
118 .await?
119 else {
120 continue;
121 };
122 for function in &artifact.functions {
123 let Some(error_types) = &function.error_types else {
124 continue;
125 };
126 if let Some(message) =
127 Self::decode_assertion_message_from_error_types(error_types, selector_key)
128 {
129 return Ok(Some(message));
130 }
131 }
132 }
133 Ok(None)
134 }
135
136 async fn decode_public_assertion_message(
137 &self,
138 revert_reason: &serde_json::Value,
139 ) -> Option<String> {
140 let revert_data = revert_reason.get("revertData")?.as_array()?;
141 let selector_key = revert_data
142 .first()
143 .and_then(|value| value.as_str())
144 .and_then(Self::error_selector_key_from_hex)?;
145
146 let failing_function = revert_reason
147 .get("functionErrorStack")?
148 .as_array()?
149 .last()?
150 .as_object()?;
151 let contract_address = failing_function
152 .get("contractAddress")
153 .and_then(|value| value.as_str())
154 .and_then(|hex| Fr::from_hex(hex).ok())
155 .map(AztecAddress)?;
156 let function_selector = failing_function
157 .get("functionSelector")
158 .and_then(|value| value.as_str())
159 .and_then(|hex| FunctionSelector::from_hex(hex).ok())?;
160
161 if let Ok(Some(error_types)) = self
162 .public_function_error_types(&contract_address, &function_selector)
163 .await
164 {
165 if let Some(message) =
166 Self::decode_assertion_message_from_error_types(&error_types, &selector_key)
167 {
168 return Some(format!("Assertion failed: {message}"));
169 }
170 }
171
172 if let Ok(Some(message)) = self
173 .find_registered_error_message_by_selector(&selector_key)
174 .await
175 {
176 return Some(format!("Assertion failed: {message}"));
177 }
178
179 Self::known_protocol_assertion_message(&contract_address, &selector_key)
180 .or_else(|| Self::known_assertion_message_by_selector(&selector_key))
181 .map(|message| format!("Assertion failed: {message}"))
182 }
183
184 pub fn new(pxe: P, node: N, accounts: A) -> Self {
186 Self {
187 pxe,
188 node,
189 accounts,
190 }
191 }
192
193 pub fn pxe(&self) -> &P {
195 &self.pxe
196 }
197
198 pub fn node(&self) -> &N {
200 &self.node
201 }
202
203 pub fn account_provider(&self) -> &A {
205 &self.accounts
206 }
207
208 fn merge_execution_payload(
210 mut exec: ExecutionPayload,
211 auth_witnesses: &[AuthWitness],
212 capsules: &[crate::tx::Capsule],
213 ) -> ExecutionPayload {
214 exec.auth_witnesses.extend_from_slice(auth_witnesses);
215 exec.capsules.extend_from_slice(capsules);
216 exec
217 }
218
219 fn build_scopes(from: &AztecAddress, additional: &[AztecAddress]) -> Vec<AztecAddress> {
221 let mut scopes = additional.to_vec();
222 if *from != AztecAddress(Fr::zero()) && !scopes.contains(from) {
223 scopes.push(*from);
224 }
225 scopes
226 }
227
228 fn has_no_private_return_values(result: &serde_json::Value) -> bool {
229 match result.get("returnValues") {
230 Some(serde_json::Value::Array(arr)) => arr.is_empty(),
231 Some(serde_json::Value::Object(obj)) => obj
232 .get("returnValues")
233 .and_then(|v| v.as_array())
234 .map(|arr| arr.is_empty())
235 .unwrap_or(false),
236 _ => false,
237 }
238 }
239
240 async fn wait_for_submission_checkpoint(&self, tx_hash: &TxHash) -> Result<(), Error> {
241 let start_block = self.node.get_block_number().await.unwrap_or(0);
242 let wait_opts = WaitOpts {
243 timeout: Duration::from_secs(15),
244 ..WaitOpts::default()
245 };
246
247 match wait_for_tx(&self.node, tx_hash, wait_opts).await {
248 Ok(_) => {}
249 Err(Error::Timeout(_)) | Err(Error::InvalidData(_)) => {
250 let deadline = Instant::now() + Duration::from_secs(30);
251 let mut next_log = Instant::now();
252 loop {
253 match self.node.get_tx_receipt(tx_hash).await {
254 Ok(receipt) if Instant::now() >= next_log => {
255 tracing::debug!(
256 tx_hash = %tx_hash,
257 status = ?receipt.status,
258 block = ?receipt.block_number,
259 error = ?receipt.error,
260 "wait_for_submission_checkpoint polling"
261 );
262 next_log = Instant::now() + Duration::from_secs(2);
263 }
264 Err(err) if Instant::now() >= next_log => {
265 tracing::debug!(
266 tx_hash = %tx_hash,
267 error = %err,
268 "wait_for_submission_checkpoint receipt error"
269 );
270 next_log = Instant::now() + Duration::from_secs(2);
271 }
272 _ => {}
273 }
274 let current_block = self.node.get_block_number().await?;
275 if current_block > start_block {
276 break;
277 }
278 if Instant::now() >= deadline {
279 return Err(Error::Timeout(
280 "transaction was submitted but did not become checkpoint-visible"
281 .into(),
282 ));
283 }
284 sleep(Duration::from_millis(500)).await;
285 }
286 }
287 Err(err) => return Err(err),
288 }
289
290 self.wait_for_world_state(tx_hash).await
294 }
295
296 async fn wait_for_world_state(&self, tx_hash: &TxHash) -> Result<(), Error> {
301 let effect = match self.node.get_tx_effect(tx_hash).await {
304 Ok(Some(e)) => e,
305 _ => {
306 sleep(Duration::from_secs(1)).await;
308 return Ok(());
309 }
310 };
311
312 let first_nullifier = effect
313 .pointer("/data/nullifiers/0")
314 .and_then(|v| v.as_str())
315 .and_then(|s| crate::types::Fr::from_hex(s).ok());
316
317 let Some(nullifier) = first_nullifier else {
318 sleep(Duration::from_secs(1)).await;
319 return Ok(());
320 };
321
322 let deadline = Instant::now() + Duration::from_secs(15);
323 loop {
324 if let Ok(Some(_)) = self
325 .node
326 .get_nullifier_membership_witness(0, &nullifier)
327 .await
328 {
329 return Ok(());
330 }
331 if Instant::now() >= deadline {
332 return Ok(());
334 }
335 sleep(Duration::from_millis(200)).await;
336 }
337 }
338
339 async fn simulate_public_calls(
340 &self,
341 tx_hash: &TxHash,
342 tx_json: &serde_json::Value,
343 ) -> Result<serde_json::Value, Error> {
344 let simulation = self.node.simulate_public_calls(tx_json, false).await?;
345 if let Some(revert_reason) = simulation.get("revertReason") {
346 if !revert_reason.is_null() {
347 let debug_logs = simulation
348 .get("debugLogs")
349 .cloned()
350 .unwrap_or(serde_json::Value::Null);
351 let decoded_assertion = self.decode_public_assertion_message(revert_reason).await;
352 let decoded_prefix = decoded_assertion
353 .map(|message| format!("{message} "))
354 .unwrap_or_default();
355 return Err(Error::InvalidData(format!(
356 "node public-call preflight for tx {} reverted: {}reason={} debugLogs={}",
357 tx_hash, decoded_prefix, revert_reason, debug_logs
358 )));
359 }
360 }
361 Ok(simulation)
362 }
363
364 fn should_ignore_public_preflight_error(err: &Error) -> bool {
371 match err {
372 Error::InvalidData(msg) => msg.contains("is not deployed"),
373 _ => false,
374 }
375 }
376
377 fn is_anchor_header_validation_lag(result: &TxValidationResult) -> bool {
378 match result {
379 TxValidationResult::Invalid { reason } => reason
380 .iter()
381 .any(|msg| msg.contains("Block header not found")),
382 _ => false,
383 }
384 }
385
386 async fn validate_tx_when_anchor_available(
387 &self,
388 tx_json: &serde_json::Value,
389 ) -> Result<TxValidationResult, Error> {
390 let deadline = Instant::now() + Duration::from_secs(300);
391 loop {
392 let result = self.node.is_valid_tx(tx_json).await?;
393 if !Self::is_anchor_header_validation_lag(&result) {
394 return Ok(result);
395 }
396 if Instant::now() >= deadline {
397 return Ok(result);
398 }
399 sleep(Duration::from_millis(500)).await;
400 }
401 }
402}
403
404#[async_trait]
405impl<P: Pxe, N: AztecNode, A: AccountProvider> Wallet for BaseWallet<P, N, A> {
406 async fn get_chain_info(&self) -> Result<ChainInfo, Error> {
407 let info = self.node.get_node_info().await?;
408 Ok(ChainInfo {
409 chain_id: Fr::from(info.l1_chain_id),
410 version: Fr::from(info.rollup_version),
411 })
412 }
413
414 async fn get_accounts(&self) -> Result<Vec<Aliased<AztecAddress>>, Error> {
415 self.accounts.get_accounts().await
416 }
417
418 async fn get_address_book(&self) -> Result<Vec<Aliased<AztecAddress>>, Error> {
419 let senders = self.pxe.get_senders().await?;
420 Ok(senders
421 .into_iter()
422 .map(|addr| Aliased {
423 alias: String::new(),
424 item: addr,
425 })
426 .collect())
427 }
428
429 async fn register_sender(
430 &self,
431 address: AztecAddress,
432 _alias: Option<String>,
433 ) -> Result<AztecAddress, Error> {
434 self.pxe.register_sender(&address).await
435 }
436
437 async fn get_contract_metadata(
438 &self,
439 address: AztecAddress,
440 ) -> Result<ContractMetadata, Error> {
441 let instance = self.pxe.get_contract_instance(&address).await?;
442 let on_chain = self.node.get_contract(&address).await?;
443
444 let is_published = on_chain.is_some();
445 let is_updated = on_chain
446 .as_ref()
447 .map(|contract| {
448 contract.current_contract_class_id != contract.original_contract_class_id
449 })
450 .unwrap_or(false);
451 let updated_contract_class_id = on_chain
452 .as_ref()
453 .and_then(|contract| is_updated.then_some(contract.current_contract_class_id));
454
455 Ok(ContractMetadata {
457 instance,
458 is_contract_initialized: is_published,
459 is_contract_published: is_published,
460 is_contract_updated: is_updated,
461 updated_contract_class_id,
462 })
463 }
464
465 async fn get_contract_class_metadata(
466 &self,
467 class_id: Fr,
468 ) -> Result<ContractClassMetadata, Error> {
469 let artifact = self.pxe.get_contract_artifact(&class_id).await?;
470 let on_chain = self.node.get_contract_class(&class_id).await?;
471
472 Ok(ContractClassMetadata {
473 is_artifact_registered: artifact.is_some(),
474 is_contract_class_publicly_registered: on_chain.is_some(),
475 })
476 }
477
478 async fn register_contract(
479 &self,
480 instance: ContractInstanceWithAddress,
481 artifact: Option<ContractArtifact>,
482 secret_key: Option<Fr>,
483 ) -> Result<ContractInstanceWithAddress, Error> {
484 let existing_instance = self.pxe.get_contract_instance(&instance.address).await?;
485
486 match (existing_instance, artifact) {
487 (Some(existing), Some(artifact))
488 if existing.current_contract_class_id != instance.current_contract_class_id =>
489 {
490 self.pxe
491 .update_contract(&instance.address, &artifact)
492 .await?;
493 }
494 (Some(_), Some(_)) | (Some(_), None) => {}
495 (None, artifact) => {
496 let artifact = match artifact {
497 Some(artifact) => artifact,
498 None => self
499 .pxe
500 .get_contract_artifact(&instance.current_contract_class_id)
501 .await?
502 .ok_or_else(|| {
503 Error::InvalidData(format!(
504 "cannot register contract at {} without an artifact; class {} is not registered in PXE",
505 instance.address, instance.current_contract_class_id
506 ))
507 })?,
508 };
509
510 self.pxe
511 .register_contract(RegisterContractRequest {
512 instance: instance.clone(),
513 artifact: Some(artifact),
514 })
515 .await?;
516 }
517 }
518
519 if let Some(sk) = secret_key {
520 let complete_address = self
521 .accounts
522 .get_complete_address(&instance.address)
523 .await?
524 .ok_or_else(|| {
525 Error::InvalidData(format!(
526 "cannot register account for {}: account provider does not expose its complete address",
527 instance.address
528 ))
529 })?;
530 self.pxe
531 .register_account(&sk, &complete_address.partial_address)
532 .await?;
533 }
534
535 Ok(instance)
536 }
537
538 async fn get_private_events(
539 &self,
540 event_metadata: &EventMetadataDefinition,
541 filter: PrivateEventFilter,
542 ) -> Result<Vec<PrivateEvent>, Error> {
543 let pxe_filter = pxe::PrivateEventFilter {
544 contract_address: filter.contract_address,
545 tx_hash: filter.tx_hash,
546 from_block: filter.from_block,
547 to_block: filter.to_block,
548 after_log: filter.after_log.map(|l| pxe::LogId {
549 block_number: l.block_number,
550 block_hash: pxe::BlockHash::default(),
551 tx_hash: TxHash::zero(),
552 tx_index: 0,
553 log_index: l.log_index,
554 }),
555 scopes: filter.scopes,
556 };
557
558 let packed = self
559 .pxe
560 .get_private_events(&event_metadata.event_selector, pxe_filter)
561 .await?;
562
563 Ok(decode_private_events(packed, event_metadata))
564 }
565
566 async fn simulate_tx(
567 &self,
568 exec: ExecutionPayload,
569 opts: SimulateOptions,
570 ) -> Result<TxSimulationResult, Error> {
571 let mut exec = Self::merge_execution_payload(exec, &opts.auth_witnesses, &opts.capsules);
572 if let Some(ref fee_payload) = opts.fee_execution_payload {
573 exec.calls.extend(fee_payload.calls.clone());
574 exec.auth_witnesses
575 .extend(fee_payload.auth_witnesses.clone());
576 exec.capsules.extend(fee_payload.capsules.clone());
577 exec.extra_hashed_args
578 .extend(fee_payload.extra_hashed_args.clone());
579 if let Some(payer) = fee_payload.fee_payer {
580 exec.fee_payer = Some(payer);
581 }
582 }
583 let chain_info = self.get_chain_info().await?;
584 let gas_settings = opts.gas_settings.clone().unwrap_or_default();
585
586 let tx_request = self
587 .accounts
588 .create_tx_execution_request(&opts.from, exec, gas_settings, &chain_info, None, None)
589 .await?;
590
591 let scopes = Self::build_scopes(&opts.from, &opts.additional_scopes);
592
593 let pxe_opts = pxe::SimulateTxOpts {
594 simulate_public: true,
595 skip_tx_validation: opts.skip_validation,
596 skip_fee_enforcement: opts.skip_fee_enforcement,
597 overrides: None,
598 scopes: scopes.clone(),
599 };
600
601 let result = self.pxe.simulate_tx(&tx_request, pxe_opts).await?;
602 let mut return_values = result.data.clone();
603
604 let mut gas_used = result.data.get("gasUsed").map(|g| crate::fee::Gas {
610 da_gas: g.get("daGas").and_then(|v| v.as_u64()).unwrap_or(0),
611 l2_gas: g.get("l2Gas").and_then(|v| v.as_u64()).unwrap_or(0),
612 });
613
614 let proven = self.pxe.prove_tx(&tx_request, scopes).await?;
615 let tx = proven.to_tx();
616 if !tx.public_function_calldata.is_empty() {
617 let tx_hash = proven.tx_hash.ok_or_else(|| {
618 Error::InvalidData("PXE prove_tx result did not include a tx hash".into())
619 })?;
620 let tx_json = tx.to_json_value()?;
621 let simulation = self.simulate_public_calls(&tx_hash, &tx_json).await?;
622 let no_private_returns = Self::has_no_private_return_values(&return_values);
623 if no_private_returns {
624 if let Some(public_return_values) = simulation.get("publicReturnValues") {
625 return_values = public_return_values.clone();
626 }
627 }
628 if let Some(total_gas) = simulation.get("gasUsed").and_then(|g| g.get("totalGas")) {
631 gas_used = Some(crate::fee::Gas {
632 da_gas: total_gas.get("daGas").and_then(|v| v.as_u64()).unwrap_or(0),
633 l2_gas: total_gas.get("l2Gas").and_then(|v| v.as_u64()).unwrap_or(0),
634 });
635 }
636 }
637
638 Ok(TxSimulationResult {
639 return_values,
640 gas_used,
641 })
642 }
643
644 async fn execute_utility(
645 &self,
646 call: FunctionCall,
647 opts: ExecuteUtilityOptions,
648 ) -> Result<UtilityExecutionResult, Error> {
649 let pxe_opts = pxe::ExecuteUtilityOpts {
650 authwits: opts.auth_witnesses,
651 scopes: vec![opts.scope],
652 };
653
654 let result = self.pxe.execute_utility(&call, pxe_opts).await?;
655
656 Ok(UtilityExecutionResult {
657 result: serde_json::to_value(&result.result).unwrap_or(serde_json::Value::Null),
658 stats: result.stats,
659 })
660 }
661
662 async fn profile_tx(
663 &self,
664 exec: ExecutionPayload,
665 opts: ProfileOptions,
666 ) -> Result<TxProfileResult, Error> {
667 let exec = Self::merge_execution_payload(exec, &opts.auth_witnesses, &opts.capsules);
668 let chain_info = self.get_chain_info().await?;
669 let gas_settings = opts.gas_settings.clone().unwrap_or_default();
670
671 let tx_request = self
672 .accounts
673 .create_tx_execution_request(&opts.from, exec, gas_settings, &chain_info, None, None)
674 .await?;
675
676 let scopes = Self::build_scopes(&opts.from, &opts.additional_scopes);
677
678 let profile_mode = match opts.profile_mode {
679 Some(super::wallet::ProfileMode::ExecutionSteps) => pxe::ProfileMode::ExecutionSteps,
680 Some(super::wallet::ProfileMode::Gates) => pxe::ProfileMode::Gates,
681 Some(super::wallet::ProfileMode::Full) | None => pxe::ProfileMode::Full,
682 };
683
684 let pxe_opts = pxe::ProfileTxOpts {
685 profile_mode,
686 skip_proof_generation: opts.skip_proof_generation,
687 scopes,
688 };
689
690 let result = self.pxe.profile_tx(&tx_request, pxe_opts).await?;
691
692 Ok(TxProfileResult {
693 return_values: result.data.clone(),
694 gas_used: None,
695 profile_data: result.data,
696 })
697 }
698
699 async fn send_tx(
700 &self,
701 exec: ExecutionPayload,
702 opts: SendOptions,
703 ) -> Result<SendResult, Error> {
704 let mut exec = Self::merge_execution_payload(exec, &opts.auth_witnesses, &opts.capsules);
705 if let Some(ref fee_payload) = opts.fee_execution_payload {
707 exec.calls.extend(fee_payload.calls.clone());
708 exec.auth_witnesses
709 .extend(fee_payload.auth_witnesses.clone());
710 exec.capsules.extend(fee_payload.capsules.clone());
711 exec.extra_hashed_args
712 .extend(fee_payload.extra_hashed_args.clone());
713 if let Some(payer) = fee_payload.fee_payer {
714 exec.fee_payer = Some(payer);
715 }
716 }
717 let chain_info = self.get_chain_info().await?;
718 let gas_settings = opts.gas_settings.clone().unwrap_or_default();
719 let fee_payer = exec.fee_payer;
720
721 let tx_request = self
722 .accounts
723 .create_tx_execution_request(
724 &opts.from,
725 exec,
726 gas_settings,
727 &chain_info,
728 fee_payer,
729 opts.fee_execution_payload.as_ref().map(|_| 2u8), )
731 .await?;
732
733 let scopes = Self::build_scopes(&opts.from, &opts.additional_scopes);
734
735 let (tx_hash, tx_json) = {
736 let proven = self.pxe.prove_tx(&tx_request, scopes.clone()).await?;
737 let tx_hash = proven.tx_hash.ok_or_else(|| {
738 Error::InvalidData("PXE prove_tx result did not include a tx hash".into())
739 })?;
740
741 let tx = proven.to_tx();
742 let tx_json = tx.to_json_value()?;
743 if !tx.public_function_calldata.is_empty() {
744 match self.simulate_public_calls(&tx_hash, &tx_json).await {
745 Ok(_) => {}
746 Err(err) if Self::should_ignore_public_preflight_error(&err) => {
747 tracing::debug!(
748 tx_hash = %tx_hash,
749 error = %err,
750 "ignoring public-call preflight error"
751 );
752 }
753 Err(err) => return Err(err),
754 }
755 }
756 (tx_hash, tx_json)
757 };
758
759 match self.validate_tx_when_anchor_available(&tx_json).await? {
760 TxValidationResult::Valid => {}
761 TxValidationResult::Invalid { reason } => {
762 return Err(Error::InvalidData(format!(
763 "node rejected tx {} during preflight validation: {}",
764 tx_hash,
765 reason.join(", ")
766 )));
767 }
768 TxValidationResult::Skipped { reason } => {
769 tracing::debug!(
770 tx_hash = %tx_hash,
771 reasons = %reason.join(", "),
772 "node skipped tx preflight validation"
773 );
774 }
775 }
776 self.node.send_tx(&tx_json).await?;
777 self.wait_for_submission_checkpoint(&tx_hash).await?;
778
779 Ok(SendResult { tx_hash })
780 }
781
782 async fn wait_for_contract(&self, address: AztecAddress) -> Result<(), Error> {
783 let timeout = Duration::from_secs(30);
784 let interval = Duration::from_millis(250);
785 let stabilization = Duration::from_secs(2);
786 let start = Instant::now();
787
788 loop {
789 if let Some(contract) = self.node.get_contract(&address).await? {
790 let class_id = contract.current_contract_class_id;
791 if self.node.get_contract_class(&class_id).await?.is_some() {
792 sleep(stabilization).await;
793 if let Some(contract) = self.node.get_contract(&address).await? {
794 if self
795 .node
796 .get_contract_class(&contract.current_contract_class_id)
797 .await?
798 .is_some()
799 {
800 return Ok(());
801 }
802 }
803 }
804 }
805
806 if start.elapsed() >= timeout {
807 return Err(Error::Timeout(format!(
808 "contract {address} did not become node-visible within {:?}",
809 timeout
810 )));
811 }
812
813 sleep(interval).await;
814 }
815 }
816
817 async fn wait_for_tx_proven(&self, tx_hash: TxHash) -> Result<(), Error> {
818 wait_for_tx(
819 &self.node,
820 &tx_hash,
821 WaitOpts {
822 wait_for_status: TxStatus::Proven,
823 timeout: Duration::from_secs(60),
824 interval: Duration::from_millis(500),
825 ..WaitOpts::default()
826 },
827 )
828 .await
829 .map(|_| ())
830 }
831
832 async fn create_auth_wit(
833 &self,
834 from: AztecAddress,
835 message_hash_or_intent: MessageHashOrIntent,
836 ) -> Result<AuthWitness, Error> {
837 let chain_info = self.get_chain_info().await?;
838 self.accounts
839 .create_auth_wit(&from, message_hash_or_intent, &chain_info)
840 .await
841 }
842
843 async fn get_public_storage_at(&self, contract: &AztecAddress, slot: &Fr) -> Result<Fr, Error> {
844 self.node.get_public_storage_at(0, contract, slot).await
846 }
847}
848
849fn decode_private_events(
851 packed: Vec<pxe::PackedPrivateEvent>,
852 event_metadata: &EventMetadataDefinition,
853) -> Vec<PrivateEvent> {
854 let field_names = resolve_event_field_names(event_metadata);
855 packed
856 .into_iter()
857 .map(|pe| {
858 let mut event_map = serde_json::Map::new();
859 for (i, name) in field_names.iter().enumerate() {
860 if let Some(field) = pe.packed_event.get(i) {
861 event_map.insert(
862 name.clone(),
863 serde_json::to_value(field).unwrap_or_default(),
864 );
865 }
866 }
867
868 PrivateEvent {
869 event: serde_json::Value::Object(event_map),
870 metadata: PrivateEventMetadata {
871 tx_hash: pe.tx_hash,
872 block_number: Some(pe.l2_block_number),
873 log_index: None,
874 },
875 }
876 })
877 .collect()
878}
879
880fn resolve_event_field_names(event_metadata: &EventMetadataDefinition) -> Vec<String> {
881 if !event_metadata.field_names.is_empty() {
882 return event_metadata.field_names.clone();
883 }
884
885 match &event_metadata.abi_type {
886 AbiType::Struct { fields, .. } => fields.iter().map(|field| field.name.clone()).collect(),
887 _ => vec![],
888 }
889}
890
891pub fn create_wallet<P: Pxe, N: AztecNode, A: AccountProvider>(
893 pxe: P,
894 node: N,
895 accounts: A,
896) -> BaseWallet<P, N, A> {
897 BaseWallet::new(pxe, node, accounts)
898}
899
900#[cfg(feature = "embedded-pxe")]
905pub async fn create_embedded_wallet<A: AccountProvider>(
906 node_url: impl Into<String>,
907 accounts: A,
908) -> Result<
909 BaseWallet<aztec_pxe::EmbeddedPxe<HttpNodeClient>, HttpNodeClient, A>,
910 crate::error::Error,
911> {
912 let node = create_aztec_node_client(node_url);
913 let pxe = aztec_pxe::EmbeddedPxe::create_ephemeral(node.clone()).await?;
914 Ok(BaseWallet::new(pxe, node, accounts))
915}
916
917#[cfg(test)]
922#[allow(clippy::unwrap_used, clippy::expect_used)]
923mod tests {
924 use super::*;
925 use crate::abi::{AbiParameter, AbiType, EventSelector};
926 use crate::fee::GasSettings;
927 use crate::node::{NodeInfo, PublicLogFilter, PublicLogsResponse};
928 use crate::pxe::{
929 BlockHeader, ExecuteUtilityOpts, PackedPrivateEvent, ProfileTxOpts, SimulateTxOpts,
930 TxExecutionRequest, TxProfileResult as PxeTxProfileResult, TxProvingResult,
931 TxSimulationResult as PxeTxSimulationResult, UtilityExecutionResult as PxeUtilityResult,
932 };
933 use crate::tx::{TxExecutionResult, TxReceipt, TxStatus};
934 use crate::types::{CompleteAddress, ContractInstance, PublicKeys};
935 use std::sync::Mutex;
936
937 struct MockNode {
942 info: NodeInfo,
943 contract: Mutex<Option<ContractInstanceWithAddress>>,
944 contract_class: Mutex<Option<serde_json::Value>>,
945 sent_txs: Mutex<Vec<serde_json::Value>>,
946 }
947
948 impl MockNode {
949 fn new() -> Self {
950 Self {
951 info: NodeInfo {
952 node_version: "test-0.1.0".into(),
953 l1_chain_id: 31337,
954 rollup_version: 1,
955 enr: None,
956 l1_contract_addresses: serde_json::json!({}),
957 protocol_contract_addresses: serde_json::json!({}),
958 real_proofs: false,
959 l2_circuits_vk_tree_root: None,
960 l2_protocol_contracts_hash: None,
961 },
962 contract: Mutex::new(None),
963 contract_class: Mutex::new(None),
964 sent_txs: Mutex::new(vec![]),
965 }
966 }
967
968 fn with_contract(self, c: ContractInstanceWithAddress) -> Self {
969 *self.contract.lock().unwrap() = Some(c);
970 self
971 }
972
973 fn with_contract_class(self, c: serde_json::Value) -> Self {
974 *self.contract_class.lock().unwrap() = Some(c);
975 self
976 }
977 }
978
979 #[async_trait]
980 impl AztecNode for MockNode {
981 async fn get_node_info(&self) -> Result<NodeInfo, Error> {
982 Ok(self.info.clone())
983 }
984
985 async fn get_block_number(&self) -> Result<u64, Error> {
986 Ok(0)
987 }
988
989 async fn get_proven_block_number(&self) -> Result<u64, Error> {
990 Ok(0)
991 }
992
993 async fn get_tx_receipt(&self, _tx_hash: &TxHash) -> Result<TxReceipt, Error> {
994 Ok(TxReceipt {
995 tx_hash: TxHash::zero(),
996 status: TxStatus::Checkpointed,
997 execution_result: Some(TxExecutionResult::Success),
998 error: None,
999 transaction_fee: None,
1000 block_hash: None,
1001 block_number: None,
1002 epoch_number: None,
1003 })
1004 }
1005
1006 async fn get_tx_effect(
1007 &self,
1008 _tx_hash: &TxHash,
1009 ) -> Result<Option<serde_json::Value>, Error> {
1010 Ok(None)
1011 }
1012 async fn get_tx_by_hash(
1013 &self,
1014 _tx_hash: &TxHash,
1015 ) -> Result<Option<serde_json::Value>, Error> {
1016 Ok(None)
1017 }
1018
1019 async fn get_public_logs(
1020 &self,
1021 _filter: PublicLogFilter,
1022 ) -> Result<PublicLogsResponse, Error> {
1023 Ok(PublicLogsResponse {
1024 logs: vec![],
1025 max_logs_hit: false,
1026 })
1027 }
1028
1029 async fn send_tx(&self, tx: &serde_json::Value) -> Result<(), Error> {
1030 self.sent_txs.lock().unwrap().push(tx.clone());
1031 Ok(())
1032 }
1033
1034 async fn get_contract(
1035 &self,
1036 _address: &AztecAddress,
1037 ) -> Result<Option<ContractInstanceWithAddress>, Error> {
1038 Ok(self.contract.lock().unwrap().clone())
1039 }
1040
1041 async fn get_contract_class(&self, _id: &Fr) -> Result<Option<serde_json::Value>, Error> {
1042 Ok(self.contract_class.lock().unwrap().clone())
1043 }
1044
1045 async fn get_block_header(&self, _block_number: u64) -> Result<serde_json::Value, Error> {
1046 Ok(serde_json::json!({"blockNumber": 1}))
1047 }
1048 async fn get_block(&self, _block_number: u64) -> Result<Option<serde_json::Value>, Error> {
1049 Ok(None)
1050 }
1051 async fn get_note_hash_membership_witness(
1052 &self,
1053 _block_number: u64,
1054 _note_hash: &Fr,
1055 ) -> Result<Option<serde_json::Value>, Error> {
1056 Ok(None)
1057 }
1058 async fn get_nullifier_membership_witness(
1059 &self,
1060 _block_number: u64,
1061 _nullifier: &Fr,
1062 ) -> Result<Option<serde_json::Value>, Error> {
1063 Ok(None)
1064 }
1065 async fn get_low_nullifier_membership_witness(
1066 &self,
1067 _block_number: u64,
1068 _nullifier: &Fr,
1069 ) -> Result<Option<serde_json::Value>, Error> {
1070 Ok(None)
1071 }
1072 async fn get_public_storage_at(
1073 &self,
1074 _block_number: u64,
1075 _contract: &AztecAddress,
1076 _slot: &Fr,
1077 ) -> Result<Fr, Error> {
1078 Ok(Fr::zero())
1079 }
1080 async fn get_public_data_witness(
1081 &self,
1082 _block_number: u64,
1083 _leaf_slot: &Fr,
1084 ) -> Result<Option<serde_json::Value>, Error> {
1085 Ok(None)
1086 }
1087 async fn get_l1_to_l2_message_membership_witness(
1088 &self,
1089 _block_number: u64,
1090 _entry_key: &Fr,
1091 ) -> Result<Option<serde_json::Value>, Error> {
1092 Ok(None)
1093 }
1094 async fn simulate_public_calls(
1095 &self,
1096 _tx: &serde_json::Value,
1097 _skip_fee_enforcement: bool,
1098 ) -> Result<serde_json::Value, Error> {
1099 Ok(serde_json::Value::Null)
1100 }
1101 async fn is_valid_tx(
1102 &self,
1103 _tx: &serde_json::Value,
1104 ) -> Result<crate::node::TxValidationResult, Error> {
1105 Ok(crate::node::TxValidationResult::Valid)
1106 }
1107 async fn get_private_logs_by_tags(&self, _tags: &[Fr]) -> Result<serde_json::Value, Error> {
1108 Ok(serde_json::json!([]))
1109 }
1110 async fn get_public_logs_by_tags_from_contract(
1111 &self,
1112 _contract: &AztecAddress,
1113 _tags: &[Fr],
1114 ) -> Result<serde_json::Value, Error> {
1115 Ok(serde_json::json!([]))
1116 }
1117 async fn register_contract_function_signatures(
1118 &self,
1119 _signatures: &[String],
1120 ) -> Result<(), Error> {
1121 Ok(())
1122 }
1123 async fn get_block_hash_membership_witness(
1124 &self,
1125 _block_number: u64,
1126 _block_hash: &Fr,
1127 ) -> Result<Option<serde_json::Value>, Error> {
1128 Ok(None)
1129 }
1130 async fn find_leaves_indexes(
1131 &self,
1132 _block_number: u64,
1133 _tree_id: &str,
1134 _leaves: &[Fr],
1135 ) -> Result<Vec<Option<u64>>, Error> {
1136 Ok(vec![])
1137 }
1138 }
1139
1140 struct MockPxe {
1145 senders: Mutex<Vec<AztecAddress>>,
1146 contract_instance: Mutex<Option<ContractInstanceWithAddress>>,
1147 contract_artifact: Mutex<Option<ContractArtifact>>,
1148 registered_contracts: Mutex<Vec<RegisterContractRequest>>,
1149 updated_contracts: Mutex<Vec<(AztecAddress, ContractArtifact)>>,
1150 registered_accounts: Mutex<Vec<(Fr, Fr)>>,
1151 simulate_opts: Mutex<Vec<SimulateTxOpts>>,
1152 prove_scopes: Mutex<Vec<Vec<AztecAddress>>>,
1153 profile_opts: Mutex<Vec<ProfileTxOpts>>,
1154 utility_opts: Mutex<Vec<ExecuteUtilityOpts>>,
1155 simulate_result: PxeTxSimulationResult,
1156 profile_result: PxeTxProfileResult,
1157 proving_result: TxProvingResult,
1158 utility_result: PxeUtilityResult,
1159 packed_events: Vec<PackedPrivateEvent>,
1160 }
1161
1162 impl MockPxe {
1163 fn new() -> Self {
1164 Self {
1165 senders: Mutex::new(vec![]),
1166 contract_instance: Mutex::new(None),
1167 contract_artifact: Mutex::new(None),
1168 registered_contracts: Mutex::new(vec![]),
1169 updated_contracts: Mutex::new(vec![]),
1170 registered_accounts: Mutex::new(vec![]),
1171 simulate_opts: Mutex::new(vec![]),
1172 prove_scopes: Mutex::new(vec![]),
1173 profile_opts: Mutex::new(vec![]),
1174 utility_opts: Mutex::new(vec![]),
1175 simulate_result: PxeTxSimulationResult {
1176 data: serde_json::json!({"returnValues": [42]}),
1177 },
1178 profile_result: PxeTxProfileResult {
1179 data: serde_json::json!({"profileData": "test"}),
1180 },
1181 proving_result: TxProvingResult {
1182 tx_hash: Some(
1183 TxHash::from_hex(
1184 "0x00000000000000000000000000000000000000000000000000000000deadbeef",
1185 )
1186 .expect("test hash"),
1187 ),
1188 private_execution_result: serde_json::json!({}),
1189 public_inputs: aztec_core::tx::PrivateKernelTailCircuitPublicInputs::from_bytes(
1190 vec![0],
1191 ),
1192 chonk_proof: aztec_core::tx::ChonkProof::from_bytes(vec![0]),
1193 contract_class_log_fields: vec![],
1194 public_function_calldata: vec![],
1195 stats: None,
1196 },
1197 utility_result: PxeUtilityResult {
1198 result: vec![Fr::from(99u64)],
1199 stats: None,
1200 },
1201 packed_events: vec![],
1202 }
1203 }
1204
1205 fn with_senders(self, senders: Vec<AztecAddress>) -> Self {
1206 *self.senders.lock().unwrap() = senders;
1207 self
1208 }
1209
1210 fn with_contract_instance(self, inst: ContractInstanceWithAddress) -> Self {
1211 *self.contract_instance.lock().unwrap() = Some(inst);
1212 self
1213 }
1214
1215 fn with_contract_artifact(self, art: ContractArtifact) -> Self {
1216 *self.contract_artifact.lock().unwrap() = Some(art);
1217 self
1218 }
1219
1220 fn with_packed_events(mut self, events: Vec<PackedPrivateEvent>) -> Self {
1221 self.packed_events = events;
1222 self
1223 }
1224 }
1225
1226 #[async_trait]
1227 impl Pxe for MockPxe {
1228 async fn get_synced_block_header(&self) -> Result<BlockHeader, Error> {
1229 Ok(BlockHeader {
1230 data: serde_json::json!({}),
1231 })
1232 }
1233
1234 async fn get_contract_instance(
1235 &self,
1236 _address: &AztecAddress,
1237 ) -> Result<Option<ContractInstanceWithAddress>, Error> {
1238 Ok(self.contract_instance.lock().unwrap().clone())
1239 }
1240
1241 async fn get_contract_artifact(&self, _id: &Fr) -> Result<Option<ContractArtifact>, Error> {
1242 Ok(self.contract_artifact.lock().unwrap().clone())
1243 }
1244
1245 async fn get_contracts(&self) -> Result<Vec<AztecAddress>, Error> {
1246 Ok(vec![])
1247 }
1248
1249 async fn register_account(
1250 &self,
1251 secret_key: &Fr,
1252 partial_address: &Fr,
1253 ) -> Result<CompleteAddress, Error> {
1254 self.registered_accounts
1255 .lock()
1256 .unwrap()
1257 .push((*secret_key, *partial_address));
1258 Ok(CompleteAddress {
1259 address: AztecAddress(Fr::from(1u64)),
1260 public_keys: PublicKeys::default(),
1261 partial_address: *partial_address,
1262 })
1263 }
1264
1265 async fn get_registered_accounts(&self) -> Result<Vec<CompleteAddress>, Error> {
1266 Ok(vec![])
1267 }
1268
1269 async fn register_sender(&self, sender: &AztecAddress) -> Result<AztecAddress, Error> {
1270 self.senders.lock().unwrap().push(*sender);
1271 Ok(*sender)
1272 }
1273
1274 async fn get_senders(&self) -> Result<Vec<AztecAddress>, Error> {
1275 Ok(self.senders.lock().unwrap().clone())
1276 }
1277
1278 async fn remove_sender(&self, _sender: &AztecAddress) -> Result<(), Error> {
1279 Ok(())
1280 }
1281
1282 async fn register_contract_class(&self, _artifact: &ContractArtifact) -> Result<(), Error> {
1283 Ok(())
1284 }
1285
1286 async fn register_contract(&self, request: RegisterContractRequest) -> Result<(), Error> {
1287 self.registered_contracts.lock().unwrap().push(request);
1288 Ok(())
1289 }
1290
1291 async fn update_contract(
1292 &self,
1293 address: &AztecAddress,
1294 artifact: &ContractArtifact,
1295 ) -> Result<(), Error> {
1296 self.updated_contracts
1297 .lock()
1298 .unwrap()
1299 .push((*address, artifact.clone()));
1300 Ok(())
1301 }
1302
1303 async fn simulate_tx(
1304 &self,
1305 _tx_request: &TxExecutionRequest,
1306 opts: SimulateTxOpts,
1307 ) -> Result<PxeTxSimulationResult, Error> {
1308 self.simulate_opts.lock().unwrap().push(opts);
1309 Ok(self.simulate_result.clone())
1310 }
1311
1312 async fn prove_tx(
1313 &self,
1314 _tx_request: &TxExecutionRequest,
1315 scopes: Vec<AztecAddress>,
1316 ) -> Result<TxProvingResult, Error> {
1317 self.prove_scopes.lock().unwrap().push(scopes);
1318 Ok(self.proving_result.clone())
1319 }
1320
1321 async fn profile_tx(
1322 &self,
1323 _tx_request: &TxExecutionRequest,
1324 opts: ProfileTxOpts,
1325 ) -> Result<PxeTxProfileResult, Error> {
1326 self.profile_opts.lock().unwrap().push(opts);
1327 Ok(self.profile_result.clone())
1328 }
1329
1330 async fn execute_utility(
1331 &self,
1332 _call: &FunctionCall,
1333 opts: ExecuteUtilityOpts,
1334 ) -> Result<PxeUtilityResult, Error> {
1335 self.utility_opts.lock().unwrap().push(opts);
1336 Ok(self.utility_result.clone())
1337 }
1338
1339 async fn get_private_events(
1340 &self,
1341 _event_selector: &EventSelector,
1342 _filter: pxe::PrivateEventFilter,
1343 ) -> Result<Vec<PackedPrivateEvent>, Error> {
1344 Ok(self.packed_events.clone())
1345 }
1346
1347 async fn stop(&self) -> Result<(), Error> {
1348 Ok(())
1349 }
1350 }
1351
1352 struct MockAccountProvider {
1357 accounts: Vec<Aliased<AztecAddress>>,
1358 complete_addresses: Vec<CompleteAddress>,
1359 created_execs: Mutex<Vec<ExecutionPayload>>,
1360 }
1361
1362 impl MockAccountProvider {
1363 fn new(accounts: Vec<Aliased<AztecAddress>>) -> Self {
1364 Self {
1365 accounts,
1366 complete_addresses: vec![],
1367 created_execs: Mutex::new(vec![]),
1368 }
1369 }
1370
1371 fn single(address: AztecAddress) -> Self {
1372 Self {
1373 accounts: vec![Aliased {
1374 alias: "test".to_owned(),
1375 item: address,
1376 }],
1377 complete_addresses: vec![],
1378 created_execs: Mutex::new(vec![]),
1379 }
1380 }
1381
1382 fn single_with_complete(address: AztecAddress, partial_address: Fr) -> Self {
1383 Self {
1384 accounts: vec![Aliased {
1385 alias: "test".to_owned(),
1386 item: address,
1387 }],
1388 complete_addresses: vec![CompleteAddress {
1389 address,
1390 public_keys: PublicKeys::default(),
1391 partial_address,
1392 }],
1393 created_execs: Mutex::new(vec![]),
1394 }
1395 }
1396 }
1397
1398 #[async_trait]
1399 impl AccountProvider for MockAccountProvider {
1400 async fn create_tx_execution_request(
1401 &self,
1402 from: &AztecAddress,
1403 exec: ExecutionPayload,
1404 _gas_settings: GasSettings,
1405 _chain_info: &ChainInfo,
1406 _fee_payer: Option<AztecAddress>,
1407 _fee_payment_method: Option<u8>,
1408 ) -> Result<TxExecutionRequest, Error> {
1409 if !self.accounts.iter().any(|a| a.item == *from) {
1410 return Err(Error::InvalidData(format!("account not found: {from}")));
1411 }
1412 self.created_execs.lock().unwrap().push(exec);
1413 Ok(TxExecutionRequest {
1414 data: serde_json::json!({
1415 "origin": from.to_string(),
1416 "calls": [],
1417 }),
1418 })
1419 }
1420
1421 async fn create_auth_wit(
1422 &self,
1423 from: &AztecAddress,
1424 _intent: MessageHashOrIntent,
1425 _chain_info: &ChainInfo,
1426 ) -> Result<AuthWitness, Error> {
1427 if !self.accounts.iter().any(|a| a.item == *from) {
1428 return Err(Error::InvalidData(format!("account not found: {from}")));
1429 }
1430 Ok(AuthWitness {
1431 fields: vec![Fr::from(1u64), Fr::from(2u64)],
1432 ..Default::default()
1433 })
1434 }
1435
1436 async fn get_complete_address(
1437 &self,
1438 address: &AztecAddress,
1439 ) -> Result<Option<CompleteAddress>, Error> {
1440 Ok(self
1441 .complete_addresses
1442 .iter()
1443 .find(|complete| complete.address == *address)
1444 .cloned())
1445 }
1446
1447 async fn get_accounts(&self) -> Result<Vec<Aliased<AztecAddress>>, Error> {
1448 Ok(self.accounts.clone())
1449 }
1450 }
1451
1452 fn sample_instance() -> ContractInstanceWithAddress {
1457 ContractInstanceWithAddress {
1458 address: AztecAddress(Fr::from(1u64)),
1459 inner: ContractInstance {
1460 version: 1,
1461 salt: Fr::from(42u64),
1462 deployer: AztecAddress(Fr::from(2u64)),
1463 current_contract_class_id: Fr::from(100u64),
1464 original_contract_class_id: Fr::from(100u64),
1465 initialization_hash: Fr::from(0u64),
1466 public_keys: PublicKeys::default(),
1467 },
1468 }
1469 }
1470
1471 fn sample_artifact() -> ContractArtifact {
1472 ContractArtifact {
1473 name: "TestContract".to_owned(),
1474 functions: vec![],
1475 outputs: None,
1476 file_map: None,
1477 context_inputs_sizes: None,
1478 }
1479 }
1480
1481 fn test_address() -> AztecAddress {
1482 AztecAddress(Fr::from(1u64))
1483 }
1484
1485 fn make_wallet(
1486 pxe: MockPxe,
1487 node: MockNode,
1488 accounts: MockAccountProvider,
1489 ) -> BaseWallet<MockPxe, MockNode, MockAccountProvider> {
1490 BaseWallet::new(pxe, node, accounts)
1491 }
1492
1493 #[test]
1498 fn base_wallet_is_send_sync() {
1499 fn assert_send_sync<T: Send + Sync>() {}
1500 assert_send_sync::<BaseWallet<MockPxe, MockNode, MockAccountProvider>>();
1501 }
1502
1503 #[tokio::test]
1504 async fn test_get_chain_info() {
1505 let wallet = make_wallet(
1506 MockPxe::new(),
1507 MockNode::new(),
1508 MockAccountProvider::new(vec![]),
1509 );
1510 let info = wallet.get_chain_info().await.expect("get chain info");
1511 assert_eq!(info.chain_id, Fr::from(31337u64));
1512 assert_eq!(info.version, Fr::from(1u64));
1513 }
1514
1515 #[tokio::test]
1516 async fn test_get_accounts() {
1517 let addr = test_address();
1518 let wallet = make_wallet(
1519 MockPxe::new(),
1520 MockNode::new(),
1521 MockAccountProvider::single(addr),
1522 );
1523 let accounts = wallet.get_accounts().await.expect("get accounts");
1524 assert_eq!(accounts.len(), 1);
1525 assert_eq!(accounts[0].item, addr);
1526 assert_eq!(accounts[0].alias, "test");
1527 }
1528
1529 #[tokio::test]
1530 async fn test_get_address_book() {
1531 let senders = vec![AztecAddress(Fr::from(10u64)), AztecAddress(Fr::from(20u64))];
1532 let wallet = make_wallet(
1533 MockPxe::new().with_senders(senders.clone()),
1534 MockNode::new(),
1535 MockAccountProvider::new(vec![]),
1536 );
1537 let book = wallet.get_address_book().await.expect("get address book");
1538 assert_eq!(book.len(), 2);
1539 assert_eq!(book[0].item, senders[0]);
1540 assert!(book[0].alias.is_empty());
1541 }
1542
1543 #[tokio::test]
1544 async fn test_register_sender() {
1545 let wallet = make_wallet(
1546 MockPxe::new(),
1547 MockNode::new(),
1548 MockAccountProvider::new(vec![]),
1549 );
1550 let addr = AztecAddress(Fr::from(99u64));
1551 let result = wallet
1552 .register_sender(addr, Some("bob".into()))
1553 .await
1554 .expect("register sender");
1555 assert_eq!(result, addr);
1556 }
1557
1558 #[tokio::test]
1559 async fn test_register_contract() {
1560 let instance = sample_instance();
1561 let artifact = sample_artifact();
1562 let wallet = make_wallet(
1563 MockPxe::new(),
1564 MockNode::new(),
1565 MockAccountProvider::new(vec![]),
1566 );
1567 let result = wallet
1568 .register_contract(instance.clone(), Some(artifact.clone()), None)
1569 .await
1570 .expect("register contract");
1571 assert_eq!(result.address, instance.address);
1572
1573 let registered = wallet.pxe.registered_contracts.lock().unwrap();
1574 assert_eq!(registered.len(), 1);
1575 assert_eq!(registered[0].instance.address, instance.address);
1576 assert_eq!(
1577 registered[0]
1578 .artifact
1579 .as_ref()
1580 .expect("artifact attached")
1581 .name,
1582 artifact.name,
1583 );
1584 }
1585
1586 #[tokio::test]
1587 async fn test_register_contract_with_secret_key() {
1588 let partial_address = Fr::from(777u64);
1589 let wallet = make_wallet(
1590 MockPxe::new(),
1591 MockNode::new(),
1592 MockAccountProvider::single_with_complete(test_address(), partial_address),
1593 );
1594 let instance = sample_instance();
1595 let sk = Fr::from(12345u64);
1596 wallet
1597 .register_contract(instance.clone(), Some(sample_artifact()), Some(sk))
1598 .await
1599 .expect("register contract with sk");
1600
1601 let accounts = wallet.pxe.registered_accounts.lock().unwrap();
1604 assert_eq!(accounts.len(), 1);
1605 assert_eq!(accounts[0].0, sk);
1606 assert_eq!(accounts[0].1, partial_address);
1607 }
1608
1609 #[tokio::test]
1610 async fn test_register_contract_falls_back_to_pxe_artifact() {
1611 let wallet = make_wallet(
1612 MockPxe::new().with_contract_artifact(sample_artifact()),
1613 MockNode::new(),
1614 MockAccountProvider::new(vec![]),
1615 );
1616 let instance = sample_instance();
1617 wallet
1618 .register_contract(instance.clone(), None, None)
1619 .await
1620 .expect("register with PXE-stored artifact");
1621
1622 let registered = wallet.pxe.registered_contracts.lock().unwrap();
1623 assert_eq!(registered.len(), 1);
1624 assert_eq!(
1625 registered[0]
1626 .artifact
1627 .as_ref()
1628 .expect("artifact resolved from PXE")
1629 .name,
1630 "TestContract",
1631 );
1632 }
1633
1634 #[tokio::test]
1635 async fn test_register_contract_reuses_existing_registration() {
1636 let wallet = make_wallet(
1637 MockPxe::new().with_contract_instance(sample_instance()),
1638 MockNode::new(),
1639 MockAccountProvider::new(vec![]),
1640 );
1641
1642 wallet
1643 .register_contract(sample_instance(), None, None)
1644 .await
1645 .expect("reuse existing registration");
1646
1647 assert!(wallet.pxe.registered_contracts.lock().unwrap().is_empty());
1648 }
1649
1650 #[tokio::test]
1651 async fn test_register_contract_updates_existing_registration() {
1652 let existing = sample_instance();
1653 let mut updated = sample_instance();
1654 updated.inner.current_contract_class_id = Fr::from(200u64);
1655 let artifact = sample_artifact();
1656 let wallet = make_wallet(
1657 MockPxe::new().with_contract_instance(existing),
1658 MockNode::new(),
1659 MockAccountProvider::new(vec![]),
1660 );
1661
1662 wallet
1663 .register_contract(updated.clone(), Some(artifact.clone()), None)
1664 .await
1665 .expect("update existing registration");
1666
1667 let updated_contracts = wallet.pxe.updated_contracts.lock().unwrap();
1668 assert_eq!(updated_contracts.len(), 1);
1669 assert_eq!(updated_contracts[0].0, updated.address);
1670 assert_eq!(updated_contracts[0].1.name, artifact.name);
1671 }
1672
1673 #[tokio::test]
1674 async fn test_get_contract_metadata_not_published() {
1675 let instance = sample_instance();
1676 let wallet = make_wallet(
1677 MockPxe::new().with_contract_instance(instance.clone()),
1678 MockNode::new(),
1679 MockAccountProvider::new(vec![]),
1680 );
1681 let meta = wallet
1682 .get_contract_metadata(instance.address)
1683 .await
1684 .expect("get contract metadata");
1685 assert!(meta.instance.is_some());
1686 assert!(!meta.is_contract_published);
1687 assert!(!meta.is_contract_initialized);
1688 }
1689
1690 #[tokio::test]
1691 async fn test_get_contract_metadata_published() {
1692 let instance = sample_instance();
1693 let wallet = make_wallet(
1694 MockPxe::new().with_contract_instance(instance.clone()),
1695 MockNode::new().with_contract(instance.clone()),
1696 MockAccountProvider::new(vec![]),
1697 );
1698 let meta = wallet
1699 .get_contract_metadata(instance.address)
1700 .await
1701 .expect("get contract metadata");
1702 assert!(meta.instance.is_some());
1703 assert!(meta.is_contract_published);
1704 assert!(meta.is_contract_initialized);
1705 }
1706
1707 #[tokio::test]
1708 async fn test_get_contract_metadata_updated() {
1709 let instance = sample_instance();
1710 let mut on_chain = sample_instance();
1711 on_chain.inner.current_contract_class_id = Fr::from(200u64);
1712 let wallet = make_wallet(
1713 MockPxe::new().with_contract_instance(instance),
1714 MockNode::new().with_contract(on_chain),
1715 MockAccountProvider::new(vec![]),
1716 );
1717
1718 let meta = wallet
1719 .get_contract_metadata(test_address())
1720 .await
1721 .expect("get updated contract metadata");
1722 assert!(meta.is_contract_updated);
1723 assert_eq!(meta.updated_contract_class_id, Some(Fr::from(200u64)));
1724 }
1725
1726 #[tokio::test]
1727 async fn test_get_contract_class_metadata() {
1728 let art = sample_artifact();
1729 let wallet = make_wallet(
1730 MockPxe::new().with_contract_artifact(art),
1731 MockNode::new().with_contract_class(serde_json::json!({"id": "0x64"})),
1732 MockAccountProvider::new(vec![]),
1733 );
1734 let meta = wallet
1735 .get_contract_class_metadata(Fr::from(100u64))
1736 .await
1737 .expect("get contract class metadata");
1738 assert!(meta.is_artifact_registered);
1739 assert!(meta.is_contract_class_publicly_registered);
1740 }
1741
1742 #[tokio::test]
1743 async fn test_get_contract_class_metadata_not_registered() {
1744 let wallet = make_wallet(
1745 MockPxe::new(),
1746 MockNode::new(),
1747 MockAccountProvider::new(vec![]),
1748 );
1749 let meta = wallet
1750 .get_contract_class_metadata(Fr::from(100u64))
1751 .await
1752 .expect("get contract class metadata");
1753 assert!(!meta.is_artifact_registered);
1754 assert!(!meta.is_contract_class_publicly_registered);
1755 }
1756
1757 #[tokio::test]
1758 async fn test_simulate_tx() {
1759 let addr = test_address();
1760 let wallet = make_wallet(
1761 MockPxe::new(),
1762 MockNode::new(),
1763 MockAccountProvider::single(addr),
1764 );
1765 let result = wallet
1766 .simulate_tx(
1767 ExecutionPayload::default(),
1768 SimulateOptions {
1769 from: addr,
1770 ..Default::default()
1771 },
1772 )
1773 .await
1774 .expect("simulate tx");
1775 assert_eq!(
1776 result.return_values,
1777 serde_json::json!({"returnValues": [42]})
1778 );
1779
1780 let simulate_opts = wallet.pxe.simulate_opts.lock().unwrap();
1781 assert_eq!(simulate_opts.len(), 1);
1782 assert!(simulate_opts[0].skip_fee_enforcement);
1783 }
1784
1785 #[tokio::test]
1786 async fn test_send_tx() {
1787 let addr = test_address();
1788 let wallet = make_wallet(
1789 MockPxe::new(),
1790 MockNode::new(),
1791 MockAccountProvider::single(addr),
1792 );
1793 let result = wallet
1794 .send_tx(
1795 ExecutionPayload::default(),
1796 SendOptions {
1797 from: addr,
1798 ..Default::default()
1799 },
1800 )
1801 .await
1802 .expect("send tx");
1803 assert_eq!(
1804 result.tx_hash,
1805 TxHash::from_hex("0x00000000000000000000000000000000000000000000000000000000deadbeef")
1806 .unwrap()
1807 );
1808
1809 let sent = wallet.node.sent_txs.lock().unwrap();
1811 assert_eq!(sent.len(), 1);
1812
1813 let scopes = wallet.pxe.prove_scopes.lock().unwrap();
1814 assert_eq!(scopes.as_slice(), &[vec![addr]]);
1815 }
1816
1817 #[tokio::test]
1818 async fn test_create_auth_wit() {
1819 let addr = test_address();
1820 let wallet = make_wallet(
1821 MockPxe::new(),
1822 MockNode::new(),
1823 MockAccountProvider::single(addr),
1824 );
1825 let wit = wallet
1826 .create_auth_wit(
1827 addr,
1828 MessageHashOrIntent::Hash {
1829 hash: Fr::from(42u64),
1830 },
1831 )
1832 .await
1833 .expect("create auth wit");
1834 assert_eq!(wit.fields.len(), 2);
1835 assert_eq!(wit.fields[0], Fr::from(1u64));
1836 }
1837
1838 #[tokio::test]
1839 async fn test_create_auth_wit_unknown_account() {
1840 let wallet = make_wallet(
1841 MockPxe::new(),
1842 MockNode::new(),
1843 MockAccountProvider::new(vec![]),
1844 );
1845 let result = wallet
1846 .create_auth_wit(
1847 AztecAddress(Fr::from(999u64)),
1848 MessageHashOrIntent::Hash {
1849 hash: Fr::from(1u64),
1850 },
1851 )
1852 .await;
1853 assert!(result.is_err());
1854 }
1855
1856 #[tokio::test]
1857 async fn test_execute_utility() {
1858 let wallet = make_wallet(
1859 MockPxe::new(),
1860 MockNode::new(),
1861 MockAccountProvider::new(vec![]),
1862 );
1863 let call = FunctionCall {
1864 to: AztecAddress(Fr::from(1u64)),
1865 selector: crate::abi::FunctionSelector::from_hex("0xaabbccdd").expect("valid selector"),
1866 args: vec![],
1867 function_type: crate::abi::FunctionType::Utility,
1868 is_static: true,
1869 hide_msg_sender: false,
1870 };
1871 let result = wallet
1872 .execute_utility(call, ExecuteUtilityOptions::default())
1873 .await
1874 .expect("execute utility");
1875 assert_ne!(result.result, serde_json::Value::Null);
1876
1877 let utility_opts = wallet.pxe.utility_opts.lock().unwrap();
1878 assert_eq!(
1879 utility_opts.as_slice(),
1880 &[ExecuteUtilityOpts {
1881 authwits: vec![],
1882 scopes: vec![AztecAddress(Fr::zero())],
1883 }]
1884 );
1885 }
1886
1887 #[tokio::test]
1888 async fn test_profile_tx() {
1889 let addr = test_address();
1890 let wallet = make_wallet(
1891 MockPxe::new(),
1892 MockNode::new(),
1893 MockAccountProvider::single(addr),
1894 );
1895 let result = wallet
1896 .profile_tx(
1897 ExecutionPayload::default(),
1898 ProfileOptions {
1899 from: addr,
1900 ..Default::default()
1901 },
1902 )
1903 .await
1904 .expect("profile tx");
1905 assert_ne!(result.profile_data, serde_json::Value::Null);
1906
1907 let profile_opts = wallet.pxe.profile_opts.lock().unwrap();
1908 assert_eq!(profile_opts.len(), 1);
1909 assert!(profile_opts[0].skip_proof_generation);
1910 }
1911
1912 #[tokio::test]
1913 async fn test_wallet_options_are_merged_into_execution_payload() {
1914 let addr = test_address();
1915 let wallet = make_wallet(
1916 MockPxe::new(),
1917 MockNode::new(),
1918 MockAccountProvider::single(addr),
1919 );
1920
1921 wallet
1922 .simulate_tx(
1923 ExecutionPayload::default(),
1924 SimulateOptions {
1925 from: addr,
1926 auth_witnesses: vec![AuthWitness {
1927 fields: vec![Fr::from(9u64)],
1928 ..Default::default()
1929 }],
1930 capsules: vec![crate::tx::Capsule {
1931 contract_address: AztecAddress(Fr::zero()),
1932 storage_slot: Fr::zero(),
1933 data: vec![Fr::from(1u64), Fr::from(2u64), Fr::from(3u64)],
1934 }],
1935 ..Default::default()
1936 },
1937 )
1938 .await
1939 .expect("simulate tx with wallet options");
1940
1941 let created_execs = wallet.accounts.created_execs.lock().unwrap();
1942 assert_eq!(created_execs.len(), 1);
1943 assert_eq!(created_execs[0].auth_witnesses.len(), 1);
1944 assert_eq!(created_execs[0].capsules.len(), 1);
1945 }
1946
1947 #[tokio::test]
1948 async fn test_get_private_events() {
1949 let packed = vec![PackedPrivateEvent {
1950 packed_event: vec![Fr::from(100u64), Fr::from(200u64)],
1951 tx_hash: TxHash::zero(),
1952 l2_block_number: 5,
1953 l2_block_hash: pxe::BlockHash::default(),
1954 event_selector: EventSelector(Fr::from(1u64)),
1955 }];
1956 let wallet = make_wallet(
1957 MockPxe::new().with_packed_events(packed),
1958 MockNode::new(),
1959 MockAccountProvider::new(vec![]),
1960 );
1961
1962 let event_metadata = EventMetadataDefinition {
1963 event_selector: EventSelector(Fr::from(1u64)),
1964 abi_type: AbiType::Struct {
1965 name: "Transfer".to_owned(),
1966 fields: vec![
1967 AbiParameter {
1968 name: "amount".to_owned(),
1969 typ: AbiType::Field,
1970 visibility: None,
1971 },
1972 AbiParameter {
1973 name: "sender".to_owned(),
1974 typ: AbiType::Field,
1975 visibility: None,
1976 },
1977 ],
1978 },
1979 field_names: vec!["amount".to_owned(), "sender".to_owned()],
1980 };
1981
1982 let events = wallet
1983 .get_private_events(
1984 &event_metadata,
1985 PrivateEventFilter {
1986 contract_address: AztecAddress(Fr::from(1u64)),
1987 ..Default::default()
1988 },
1989 )
1990 .await
1991 .expect("get private events");
1992
1993 assert_eq!(events.len(), 1);
1994 assert_eq!(events[0].metadata.block_number, Some(5));
1995 assert_eq!(events[0].metadata.tx_hash, TxHash::zero());
1996
1997 let event = &events[0].event;
1999 assert!(event.get("amount").is_some());
2000 assert!(event.get("sender").is_some());
2001 }
2002
2003 #[tokio::test]
2004 async fn test_create_wallet_factory() {
2005 let wallet = create_wallet(
2006 MockPxe::new(),
2007 MockNode::new(),
2008 MockAccountProvider::new(vec![]),
2009 );
2010 let info = wallet.get_chain_info().await.expect("get chain info");
2011 assert_eq!(info.chain_id, Fr::from(31337u64));
2012 }
2013}