1use std::boxed::Box;
2use std::collections::BTreeMap;
3use std::env::temp_dir;
4use std::fs::OpenOptions;
5use std::io::Write;
6use std::ops::{Deref, DerefMut};
7use std::path::PathBuf;
8use std::string::ToString;
9use std::sync::Arc;
10use std::time::{Duration, Instant};
11use std::vec::Vec;
12
13use anyhow::{Context, Result};
14use miden_protocol::account::auth::AuthSecretKey;
15use miden_protocol::account::{Account, AccountComponentMetadata, AccountId};
16use miden_protocol::asset::{AssetAmount, FungibleAsset, TokenSymbol};
17use miden_protocol::note::NoteType;
18use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE;
19use miden_protocol::transaction::TransactionId;
20use miden_standards::account::auth::{Approver, AuthSingleSig};
21use miden_standards::account::faucets::TokenName;
22use miden_standards::code_builder::CodeBuilder;
23use rand::Rng;
24use tracing::{debug, info};
25use uuid::Uuid;
26
27use crate::account::component::{
28 AccountComponent,
29 BasicWallet,
30 BurnPolicy,
31 FungibleFaucet,
32 MintPolicy,
33 TokenPolicyManager,
34};
35use crate::account::{AccountBuilder, AccountBuilderSchemaCommitmentExt, AccountType, StorageSlot};
36use crate::auth::AuthSchemeId;
37use crate::crypto::FeltRng;
38pub use crate::keystore::{FilesystemKeyStore, Keystore};
39use crate::note::{Note, NoteConsumability, P2idNote};
40use crate::rpc::RpcError;
41use crate::store::{InputNoteRecord, NoteFilter, TransactionFilter};
42use crate::sync::SyncSummary;
43use crate::test_utils::fee::FeeFunder;
44use crate::transaction::{
45 NoteArgs,
46 TransactionRequest,
47 TransactionRequestBuilder,
48 TransactionRequestError,
49 TransactionResult,
50 TransactionStatus,
51};
52use crate::{Client, ClientError};
53
54pub struct TestClient {
62 client: Client<FilesystemKeyStore>,
63 fee_funder: Option<Arc<dyn FeeFunder>>,
64 pending_funding: BTreeMap<AccountId, Note>,
69}
70
71impl TestClient {
72 pub fn new(client: Client<FilesystemKeyStore>) -> Self {
74 Self {
75 client,
76 fee_funder: None,
77 pending_funding: BTreeMap::new(),
78 }
79 }
80
81 pub(crate) fn stash_funding(&mut self, funded: impl IntoIterator<Item = (AccountId, Note)>) {
83 self.pending_funding.extend(funded);
84 }
85
86 pub fn take_funding(&mut self, account_id: AccountId) -> Option<Note> {
92 self.pending_funding.remove(&account_id)
93 }
94
95 pub async fn submit_new_transaction(
100 &mut self,
101 account_id: AccountId,
102 transaction_request: TransactionRequest,
103 ) -> Result<TransactionId, ClientError> {
104 let transaction_request = self.fund_request(account_id, transaction_request);
105
106 Box::pin(self.client.submit_new_transaction(account_id, transaction_request)).await
107 }
108
109 pub async fn execute_transaction(
113 &mut self,
114 account_id: AccountId,
115 transaction_request: TransactionRequest,
116 ) -> Result<TransactionResult, ClientError> {
117 let transaction_request = self.fund_request(account_id, transaction_request);
118
119 Box::pin(self.client.execute_transaction(account_id, transaction_request)).await
120 }
121
122 #[must_use]
127 pub fn fund_request(
128 &mut self,
129 account_id: AccountId,
130 mut transaction_request: TransactionRequest,
131 ) -> TransactionRequest {
132 if let Some(note) = self.take_funding(account_id) {
133 transaction_request.add_unauthenticated_input_note(note);
134 }
135
136 transaction_request
137 }
138
139 #[must_use]
141 pub fn with_fee_funder(mut self, fee_funder: Option<Arc<dyn FeeFunder>>) -> Self {
142 self.fee_funder = fee_funder;
143 self
144 }
145
146 pub fn fee_funder(&self) -> Option<&Arc<dyn FeeFunder>> {
148 self.fee_funder.as_ref()
149 }
150}
151
152impl From<Client<FilesystemKeyStore>> for TestClient {
153 fn from(client: Client<FilesystemKeyStore>) -> Self {
154 Self::new(client)
155 }
156}
157
158impl Deref for TestClient {
159 type Target = Client<FilesystemKeyStore>;
160
161 fn deref(&self) -> &Self::Target {
162 &self.client
163 }
164}
165
166impl DerefMut for TestClient {
167 fn deref_mut(&mut self) -> &mut Self::Target {
168 &mut self.client
169 }
170}
171
172pub const ACCOUNT_ID_REGULAR: u128 = ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE;
175
176pub const RECALL_HEIGHT_DELTA: u32 = 50;
179
180pub fn create_test_store_path() -> PathBuf {
181 let mut temp_file = temp_dir();
182 temp_file.push(format!("{}.sqlite3", Uuid::new_v4()));
183 temp_file
184}
185
186pub async fn insert_new_wallet(
188 client: &mut TestClient,
189 visibility: AccountType,
190 keystore: &FilesystemKeyStore,
191 auth_scheme: AuthSchemeId,
192) -> Result<(Account, AuthSecretKey)> {
193 let mut init_seed = [0u8; 32];
194 client.rng().fill_bytes(&mut init_seed);
195
196 insert_new_wallet_with_seed(client, visibility, keystore, init_seed, auth_scheme).await
197}
198
199pub async fn insert_new_wallet_with_seed(
201 client: &mut TestClient,
202 visibility: AccountType,
203 keystore: &FilesystemKeyStore,
204 init_seed: [u8; 32],
205 auth_scheme: AuthSchemeId,
206) -> Result<(Account, AuthSecretKey)> {
207 let (account, key_pair) =
208 insert_new_wallet_with_seed_unfunded(client, visibility, keystore, init_seed, auth_scheme)
209 .await?;
210 client.fund_if_needed(&[account.id()]).await?;
211
212 Ok((account, key_pair))
213}
214
215pub async fn insert_new_wallet_unfunded(
221 client: &mut TestClient,
222 visibility: AccountType,
223 keystore: &FilesystemKeyStore,
224 auth_scheme: AuthSchemeId,
225) -> Result<(Account, AuthSecretKey)> {
226 let mut init_seed = [0u8; 32];
227 client.rng().fill_bytes(&mut init_seed);
228
229 insert_new_wallet_with_seed_unfunded(client, visibility, keystore, init_seed, auth_scheme).await
230}
231
232pub async fn insert_new_wallet_with_seed_unfunded(
234 client: &mut TestClient,
235 visibility: AccountType,
236 keystore: &FilesystemKeyStore,
237 init_seed: [u8; 32],
238 auth_scheme: AuthSchemeId,
239) -> Result<(Account, AuthSecretKey)> {
240 let key_pair = match auth_scheme {
241 AuthSchemeId::Falcon512Poseidon2 => AuthSecretKey::new_falcon512_poseidon2(),
242 AuthSchemeId::EcdsaK256Keccak => AuthSecretKey::new_ecdsa_k256_keccak(),
243 other => panic!("unsupported auth scheme: {}", other.as_u8()),
244 };
245 let auth_component =
246 AuthSingleSig::new(Approver::new(key_pair.public_key().to_commitment(), auth_scheme));
247
248 let account = AccountBuilder::new(init_seed)
249 .account_type(visibility)
250 .with_component(auth_component)
251 .with_component(BasicWallet)
252 .build_with_schema_commitment()
253 .unwrap();
254
255 keystore.add_key(&key_pair, account.id()).await.unwrap();
256
257 client.add_account(&account, false).await?;
258
259 info!(account_id = %account.id(), ?visibility, "Inserted new wallet");
260
261 Ok((account, key_pair))
262}
263
264pub async fn insert_new_fungible_faucet(
269 client: &mut TestClient,
270 visibility: AccountType,
271 keystore: &FilesystemKeyStore,
272 auth_scheme: AuthSchemeId,
273) -> Result<(Account, AuthSecretKey)> {
274 let (account, key_pair) =
275 insert_new_fungible_faucet_unfunded(client, visibility, keystore, auth_scheme).await?;
276 client.fund_if_needed(&[account.id()]).await?;
277
278 Ok((account, key_pair))
279}
280
281pub async fn insert_new_fungible_faucet_unfunded(
285 client: &mut TestClient,
286 visibility: AccountType,
287 keystore: &FilesystemKeyStore,
288 auth_scheme: AuthSchemeId,
289) -> Result<(Account, AuthSecretKey)> {
290 let key_pair = match auth_scheme {
291 AuthSchemeId::Falcon512Poseidon2 => AuthSecretKey::new_falcon512_poseidon2(),
292 AuthSchemeId::EcdsaK256Keccak => AuthSecretKey::new_ecdsa_k256_keccak(),
293 other => panic!("unsupported auth scheme: {}", other.as_u8()),
294 };
295 let auth_component =
296 AuthSingleSig::new(Approver::new(key_pair.public_key().to_commitment(), auth_scheme));
297
298 let mut init_seed = [0u8; 32];
300 client.rng().fill_bytes(&mut init_seed);
301
302 let symbol = TokenSymbol::new("TEST").unwrap();
303 let name = TokenName::new(&symbol.to_string()).expect("token symbol is a valid token name");
304 let max_supply = 9_999_999_u64;
305 let faucet = FungibleFaucet::builder()
306 .name(name)
307 .symbol(symbol)
308 .decimals(10)
309 .max_supply(AssetAmount::new(max_supply).unwrap())
310 .build()
311 .unwrap();
312
313 let policy_manager = TokenPolicyManager::builder()
319 .active_mint_policy(MintPolicy::allow_all())
320 .active_burn_policy(BurnPolicy::allow_all())
321 .build();
322 let account = AccountBuilder::new(init_seed)
323 .account_type(visibility)
324 .with_component(auth_component)
325 .with_component(faucet)
326 .with_component(BasicWallet)
327 .with_components(policy_manager)
328 .build_with_schema_commitment()
329 .unwrap();
330
331 keystore.add_key(&key_pair, account.id()).await.unwrap();
332
333 client.add_account(&account, false).await?;
334
335 info!(account_id = %account.id(), ?visibility, "Inserted new fungible faucet");
336
337 Ok((account, key_pair))
338}
339
340pub async fn execute_failing_tx(
342 client: &mut TestClient,
343 account_id: AccountId,
344 tx_request: TransactionRequest,
345 expected_error: ClientError,
346) {
347 info!(account_id = %account_id, "Executing transaction (expecting failure)");
348 assert_eq!(
350 Box::pin(client.submit_new_transaction(account_id, tx_request))
351 .await
352 .unwrap_err()
353 .to_string(),
354 expected_error.to_string()
355 );
356}
357
358pub async fn execute_tx_and_sync(
360 client: &mut TestClient,
361 account_id: AccountId,
362 tx_request: TransactionRequest,
363) -> Result<()> {
364 let transaction_id = Box::pin(client.submit_new_transaction(account_id, tx_request)).await?;
365 info!(tx_id = %transaction_id, account_id = %account_id, "Transaction submitted, waiting for commit");
366 wait_for_tx(client, transaction_id).await?;
367 Ok(())
368}
369
370pub async fn wait_for_tx(client: &mut TestClient, transaction_id: TransactionId) -> Result<()> {
372 let now = Instant::now();
374 debug!(tx_id = %transaction_id, "Waiting for transaction to be committed");
375 loop {
376 client
377 .sync_state()
378 .await
379 .with_context(|| "failed to sync client state while waiting for transaction")?;
380
381 let tracked_transaction = client
383 .get_transactions(TransactionFilter::Ids(vec![transaction_id]))
384 .await
385 .with_context(|| format!("failed to get transaction with ID: {transaction_id}"))?
386 .pop()
387 .with_context(|| format!("transaction with ID {transaction_id} not found"))?;
388
389 match tracked_transaction.status {
390 TransactionStatus::Committed { block_number, .. } => {
391 info!(tx_id = %transaction_id, %block_number, "Transaction committed");
392 break;
393 },
394 TransactionStatus::Pending => {
395 tokio::time::sleep(Duration::from_millis(500)).await;
398 },
399 TransactionStatus::Discarded(cause) => {
400 anyhow::bail!("transaction was discarded with cause: {cause:?}");
401 },
402 }
403
404 if std::env::var("LOG_WAIT_TIMES") == Ok("true".to_string()) {
408 let elapsed = now.elapsed();
409 let wait_times_dir = std::path::PathBuf::from("wait_times");
410 std::fs::create_dir_all(&wait_times_dir)
411 .with_context(|| "failed to create wait_times directory")?;
412
413 let elapsed_time_file = wait_times_dir.join(format!("wait_time_{}", Uuid::new_v4()));
414 let mut file = OpenOptions::new()
415 .create(true)
416 .write(true)
417 .truncate(true)
418 .open(elapsed_time_file)
419 .with_context(|| "failed to create elapsed time file")?;
420 writeln!(file, "{:?}", elapsed.as_millis())
421 .with_context(|| "failed to write elapsed time to file")?;
422 }
423 }
424 Ok(())
425}
426
427pub async fn wait_for_blocks(client: &mut TestClient, amount_of_blocks: u32) -> SyncSummary {
429 let current_block = client.get_sync_height().await.unwrap();
430 let final_block = current_block + amount_of_blocks;
431 debug!(current_block = %current_block, target_block = %final_block, "Waiting for blocks");
432 loop {
433 let summary = client.sync_state().await.unwrap();
434 debug!(sync_height = %summary.block_num, target_block = %final_block, "Synced");
435
436 if summary.block_num >= final_block {
437 return summary;
438 }
439
440 tokio::time::sleep(Duration::from_secs(3)).await;
441 }
442}
443
444pub async fn wait_for_blocks_no_sync(client: &mut TestClient, amount_of_blocks: u32) {
447 let current_block = client.get_sync_height().await.unwrap();
448 let final_block = current_block + amount_of_blocks;
449 debug!(current_block = %current_block, target_block = %final_block, "Waiting for blocks (no sync)");
450 loop {
451 let (latest_block, _) =
452 client.test_rpc_api().get_block_header_by_number(None, false).await.unwrap();
453 debug!(
454 chain_tip = %latest_block.block_num(),
455 target_block = %final_block,
456 "Waiting for blocks (no sync)"
457 );
458
459 if latest_block.block_num() >= final_block {
460 return;
461 }
462
463 tokio::time::sleep(Duration::from_secs(3)).await;
464 }
465}
466
467pub async fn wait_for_consumable_notes(
478 client: &mut TestClient,
479 account_id: AccountId,
480 max_blocks: u32,
481) -> Vec<(InputNoteRecord, Vec<NoteConsumability>)> {
482 let start_block = client.get_sync_height().await.unwrap();
483 let deadline_block = start_block + max_blocks;
484 debug!(
485 %account_id,
486 %start_block,
487 %deadline_block,
488 "Waiting for consumable notes"
489 );
490
491 loop {
492 client.sync_state().await.unwrap();
493 let notes = client.get_consumable_notes(Some(account_id)).await.unwrap();
494 if !notes.is_empty() {
495 let current_block = client.get_sync_height().await.unwrap();
496 debug!(
497 %account_id,
498 count = notes.len(),
499 %current_block,
500 "Found consumable notes"
501 );
502 return notes;
503 }
504
505 let current_block = client.get_sync_height().await.unwrap();
506 assert!(
507 current_block < deadline_block,
508 "account {account_id} has no consumable notes after waiting {max_blocks} blocks \
509 (from block {start_block} to {current_block})"
510 );
511
512 debug!(
513 %account_id,
514 %current_block,
515 %deadline_block,
516 "No consumable notes yet, waiting..."
517 );
518 std::thread::sleep(Duration::from_secs(3));
519 }
520}
521
522pub async fn wait_for_node(client: &mut TestClient) {
529 const NODE_TIME_BETWEEN_ATTEMPTS: u64 = 2;
530 const NUMBER_OF_NODE_ATTEMPTS: u64 = 60;
531 info!(
532 "Waiting for node to be up (checking every {NODE_TIME_BETWEEN_ATTEMPTS}s, max {NUMBER_OF_NODE_ATTEMPTS} tries)"
533 );
534 for _try_number in 0..NUMBER_OF_NODE_ATTEMPTS {
535 match client.sync_state().await {
536 Err(ClientError::RpcError(
537 RpcError::ConnectionError(_) | RpcError::RequestError { .. },
538 )) => {
539 tokio::time::sleep(Duration::from_secs(NODE_TIME_BETWEEN_ATTEMPTS)).await;
540 },
541 Err(other_error) => {
542 panic!("Unexpected error: {other_error}");
543 },
544 _ => return,
545 }
546 }
547
548 panic!("Unable to connect to node");
549}
550
551pub const MINT_AMOUNT: u64 = 1000;
552pub const TRANSFER_AMOUNT: u64 = 59;
553
554pub async fn setup_two_wallets_and_faucet(
556 client: &mut TestClient,
557 account_visibility: AccountType,
558 keystore: &FilesystemKeyStore,
559 auth_scheme: AuthSchemeId,
560) -> Result<(Account, Account, Account)> {
561 let account_headers = client
563 .get_account_headers()
564 .await
565 .with_context(|| "failed to get account headers")?;
566 anyhow::ensure!(account_headers.is_empty(), "Expected empty account headers for clean state");
567
568 let transactions = client
569 .get_transactions(TransactionFilter::All)
570 .await
571 .with_context(|| "failed to get transactions")?;
572 anyhow::ensure!(transactions.is_empty(), "Expected empty transactions for clean state");
573
574 let input_notes = client
575 .get_input_notes(NoteFilter::All)
576 .await
577 .with_context(|| "failed to get input notes")?;
578 anyhow::ensure!(input_notes.is_empty(), "Expected empty input notes for clean state");
579
580 let (faucet_account, _) =
582 insert_new_fungible_faucet_unfunded(client, account_visibility, keystore, auth_scheme)
583 .await
584 .with_context(|| "failed to insert new fungible faucet account")?;
585
586 let (first_basic_account, ..) =
588 insert_new_wallet_unfunded(client, account_visibility, keystore, auth_scheme)
589 .await
590 .with_context(|| "failed to insert first basic wallet account")?;
591
592 let (second_basic_account, ..) =
593 insert_new_wallet_unfunded(client, account_visibility, keystore, auth_scheme)
594 .await
595 .with_context(|| "failed to insert second basic wallet account")?;
596
597 client
599 .fund_if_needed(&[faucet_account.id(), first_basic_account.id(), second_basic_account.id()])
600 .await
601 .with_context(|| "failed to fund and deploy the created accounts")?;
602
603 info!(
604 faucet_id = %faucet_account.id(),
605 wallet_1_id = %first_basic_account.id(),
606 wallet_2_id = %second_basic_account.id(),
607 "Setup complete, syncing state"
608 );
609 client.sync_state().await.with_context(|| "failed to sync client state")?;
610
611 Ok((first_basic_account, second_basic_account, faucet_account))
612}
613
614pub async fn setup_wallet_and_faucet(
616 client: &mut TestClient,
617 account_visibility: AccountType,
618 keystore: &FilesystemKeyStore,
619 auth_scheme: AuthSchemeId,
620) -> Result<(Account, Account)> {
621 let (faucet_account, _) =
622 insert_new_fungible_faucet_unfunded(client, account_visibility, keystore, auth_scheme)
623 .await
624 .with_context(|| "failed to insert new fungible faucet account")?;
625
626 let (basic_account, ..) =
627 insert_new_wallet_unfunded(client, account_visibility, keystore, auth_scheme)
628 .await
629 .with_context(|| "failed to insert new wallet account")?;
630
631 client
633 .fund_if_needed(&[faucet_account.id(), basic_account.id()])
634 .await
635 .with_context(|| "failed to fund and deploy the created accounts")?;
636
637 Ok((basic_account, faucet_account))
638}
639
640pub async fn mint_note(
643 client: &mut TestClient,
644 basic_account_id: AccountId,
645 faucet_account_id: AccountId,
646 note_type: NoteType,
647) -> (TransactionId, Note) {
648 let fungible_asset = FungibleAsset::new(faucet_account_id, MINT_AMOUNT).unwrap();
650 info!(faucet_id = %faucet_account_id, target_id = %basic_account_id, amount = MINT_AMOUNT, "Minting asset");
651 let tx_request = TransactionRequestBuilder::new()
652 .build_mint_fungible_asset(fungible_asset, basic_account_id, note_type, client.rng())
653 .unwrap();
654 let tx_id =
655 Box::pin(client.submit_new_transaction(fungible_asset.faucet_id(), tx_request.clone()))
656 .await
657 .unwrap();
658
659 let note = tx_request.expected_output_own_notes().pop().unwrap();
660 info!(tx_id = %tx_id, note_id = %note.id(), "Mint transaction submitted");
661 (tx_id, note)
662}
663
664pub async fn consume_notes(
667 client: &mut TestClient,
668 account_id: AccountId,
669 input_notes: &[Note],
670) -> TransactionId {
671 let note_ids: Vec<_> = input_notes.iter().map(|n| n.id().to_string()).collect();
672 info!(account_id = %account_id, note_ids = %note_ids.join(", "), "Consuming notes");
673 let tx_request = TransactionRequestBuilder::new()
674 .build_consume_notes(input_notes.to_vec())
675 .unwrap();
676 let tx_id = Box::pin(client.submit_new_transaction(account_id, tx_request)).await.unwrap();
677 info!(tx_id = %tx_id, "Consume transaction submitted");
678 tx_id
679}
680
681pub async fn assert_account_has_single_asset(
683 client: &TestClient,
684 account_id: AccountId,
685 faucet_id: AccountId,
686 expected_amount: u64,
687) {
688 let balance = client
689 .account_reader(account_id)
690 .get_balance(faucet_id)
691 .await
692 .expect("Account should have the asset");
693 assert_eq!(balance, AssetAmount::new(expected_amount).unwrap());
694}
695
696pub async fn assert_note_cannot_be_consumed_twice(
698 client: &mut TestClient,
699 consuming_account_id: AccountId,
700 note_to_consume: Note,
701) {
702 info!(note_id = %note_to_consume.id(), account_id = %consuming_account_id, "Attempting double-consume (expecting failure)");
704
705 let tx_request = TransactionRequestBuilder::new()
707 .build_consume_notes(vec![note_to_consume.clone()])
708 .unwrap();
709
710 match Box::pin(client.submit_new_transaction(consuming_account_id, tx_request)).await {
711 Err(ClientError::TransactionRequestError(
712 TransactionRequestError::InputNoteAlreadyConsumed(_),
713 )) => {},
714 Ok(_) => panic!("Double-spend error: Note should not be consumable!"),
715 err => panic!("Unexpected error {:?} for note ID: {}", err, note_to_consume.id().to_hex()),
716 }
717}
718
719pub fn mint_multiple_fungible_asset(
721 asset: FungibleAsset,
722 target_id: &[AccountId],
723 note_type: NoteType,
724 rng: &mut impl FeltRng,
725) -> TransactionRequest {
726 let notes = target_id
727 .iter()
728 .map(|account_id| {
729 P2idNote::builder()
730 .sender(asset.faucet_id())
731 .target(*account_id)
732 .asset(asset)
733 .note_type(note_type)
734 .generate_serial_number(rng)
735 .build()
736 .expect("note creation failed")
737 .into()
738 })
739 .collect::<Vec<Note>>();
740
741 TransactionRequestBuilder::new().own_output_notes(notes).build().unwrap()
742}
743
744pub async fn execute_tx_and_consume_output_notes(
747 tx_request: TransactionRequest,
748 client: &mut TestClient,
749 executor: AccountId,
750 consumer: AccountId,
751) -> TransactionId {
752 let output_notes = tx_request
753 .expected_output_own_notes()
754 .into_iter()
755 .map(|note| (note, None::<NoteArgs>))
756 .collect::<Vec<(Note, Option<NoteArgs>)>>();
757
758 Box::pin(client.submit_new_transaction(executor, tx_request)).await.unwrap();
759
760 let tx_request = TransactionRequestBuilder::new().input_notes(output_notes).build().unwrap();
761 Box::pin(client.submit_new_transaction(consumer, tx_request)).await.unwrap()
762}
763
764pub async fn mint_and_consume(
767 client: &mut TestClient,
768 basic_account_id: AccountId,
769 faucet_account_id: AccountId,
770 note_type: NoteType,
771) -> TransactionId {
772 info!(
773 faucet_id = %faucet_account_id,
774 target_id = %basic_account_id,
775 amount = MINT_AMOUNT,
776 "Minting and consuming asset"
777 );
778 let tx_request = TransactionRequestBuilder::new()
779 .build_mint_fungible_asset(
780 FungibleAsset::new(faucet_account_id, MINT_AMOUNT).unwrap(),
781 basic_account_id,
782 note_type,
783 client.rng(),
784 )
785 .unwrap();
786
787 let tx_id = Box::pin(execute_tx_and_consume_output_notes(
788 tx_request,
789 client,
790 faucet_account_id,
791 basic_account_id,
792 ))
793 .await;
794 info!(tx_id = %tx_id, "Mint-and-consume transaction submitted");
795 tx_id
796}
797
798pub async fn insert_account_with_custom_component(
800 client: &mut TestClient,
801 custom_code: &str,
802 storage_slots: Vec<StorageSlot>,
803 visibility: AccountType,
804 keystore: &FilesystemKeyStore,
805) -> Result<(Account, AuthSecretKey)> {
806 let component_code = CodeBuilder::default()
807 .compile_component_code("custom::component", custom_code)
808 .map_err(|err| ClientError::TransactionRequestError(err.into()))?;
809 let custom_component = AccountComponent::new(
810 component_code,
811 storage_slots,
812 AccountComponentMetadata::new("miden::testing::custom_component"),
813 )
814 .map_err(ClientError::AccountError)?;
815
816 let mut init_seed = [0u8; 32];
817 client.rng().fill_bytes(&mut init_seed);
818
819 let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.rng());
820 let pub_key = key_pair.public_key();
821
822 let account = AccountBuilder::new(init_seed)
823 .account_type(visibility)
824 .with_component(AuthSingleSig::new(Approver::new(
825 pub_key.to_commitment(),
826 AuthSchemeId::Falcon512Poseidon2,
827 )))
828 .with_component(BasicWallet)
829 .with_component(custom_component)
830 .build_with_schema_commitment()
831 .map_err(ClientError::AccountError)?;
832
833 keystore.add_key(&key_pair, account.id()).await.unwrap();
834 client.add_account(&account, false).await?;
835
836 client.fund_if_needed(&[account.id()]).await?;
837
838 Ok((account, key_pair))
839}