1use miden_assembly::{
2 Assembler, DefaultSourceManager, LibraryPath,
3 ast::{Module, ModuleKind},
4};
5use miden_crypto::dsa::rpo_falcon512::Polynomial;
6use rand::{RngCore, rngs::StdRng};
7use std::sync::Arc;
8use tokio::time::{Duration, sleep};
9
10use miden_client::{
11 Client, ClientError, Felt, Word,
12 account::{
13 Account, AccountBuilder, AccountId, AccountStorageMode, AccountType,
14 component::{BasicFungibleFaucet, BasicWallet, RpoFalcon512},
15 },
16 asset::{Asset, FungibleAsset, TokenSymbol},
17 auth::AuthSecretKey,
18 builder::ClientBuilder,
19 crypto::{FeltRng, SecretKey},
20 keystore::FilesystemKeyStore,
21 note::{
22 Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteInputs, NoteMetadata,
23 NoteRecipient, NoteRelevance, NoteScript, NoteTag, NoteType,
24 },
25 rpc::{Endpoint, TonicRpcClient},
26 store::InputNoteRecord,
27 transaction::{OutputNote, TransactionKernel, TransactionRequestBuilder, TransactionScript},
28};
29use miden_lib::note::utils;
30use miden_objects::{Hasher, NoteError, assembly::Library};
31use serde::de::value::Error;
32
33pub async fn instantiate_client(
44 endpoint: Endpoint,
45 store_path: Option<&str>,
46) -> Result<Client, ClientError> {
47 let timeout_ms = 10_000;
48 let rpc_api = Arc::new(TonicRpcClient::new(&endpoint, timeout_ms));
49
50 let client = ClientBuilder::new()
51 .with_rpc(rpc_api.clone())
52 .with_filesystem_keystore("./keystore")
53 .with_sqlite_store(store_path.unwrap_or("./store.sqlite3"))
54 .in_debug_mode(true)
55 .build()
56 .await?;
57
58 Ok(client)
59}
60
61pub async fn delete_keystore_and_store(store_path: Option<&str>) {
69 let store_path = store_path.unwrap_or("./store.sqlite3");
70 if tokio::fs::metadata(store_path).await.is_ok() {
71 if let Err(e) = tokio::fs::remove_file(store_path).await {
72 eprintln!("failed to remove {}: {}", store_path, e);
73 } else {
74 println!("cleared sqlite store: {}", store_path);
75 }
76 } else {
77 println!("store not found: {}", store_path);
78 }
79
80 let keystore_dir = "./keystore";
81 match tokio::fs::read_dir(keystore_dir).await {
82 Ok(mut dir) => {
83 while let Ok(Some(entry)) = dir.next_entry().await {
84 let file_path = entry.path();
85 if let Err(e) = tokio::fs::remove_file(&file_path).await {
86 eprintln!("failed to remove {}: {}", file_path.display(), e);
87 } else {
88 println!("removed file: {}", file_path.display());
89 }
90 }
91 }
92 Err(e) => eprintln!("failed to read directory {}: {}", keystore_dir, e),
93 }
94}
95
96const N: usize = 512;
107fn mul_modulo_p(a: Polynomial<Felt>, b: Polynomial<Felt>) -> [u64; 1024] {
108 let mut c = [0; 2 * N];
109 for i in 0..N {
110 for j in 0..N {
111 c[i + j] += a.coefficients[i].as_int() * b.coefficients[j].as_int();
112 }
113 }
114 c
115}
116
117fn to_elements(poly: Polynomial<Felt>) -> Vec<Felt> {
127 poly.coefficients.to_vec()
128}
129
130pub fn generate_advice_stack_from_signature(h: Polynomial<Felt>, s2: Polynomial<Felt>) -> Vec<u64> {
141 let pi = mul_modulo_p(h.clone(), s2.clone());
142
143 let mut polynomials = to_elements(h.clone());
145 polynomials.extend(to_elements(s2.clone()));
146 polynomials.extend(pi.iter().map(|a| Felt::new(*a)));
147
148 let digest_polynomials = Hasher::hash_elements(&polynomials);
150 let challenge = (digest_polynomials[0], digest_polynomials[1]);
151 let mut advice_stack = vec![challenge.0.as_int(), challenge.1.as_int()];
152
153 let polynomials: Vec<u64> = polynomials.iter().map(|&e| e.into()).collect();
155 advice_stack.extend_from_slice(&polynomials);
156
157 advice_stack
158}
159
160pub fn create_library(
171 account_code: String,
172 library_path: &str,
173) -> Result<miden_assembly::Library, Box<dyn std::error::Error>> {
174 let assembler: Assembler = TransactionKernel::assembler().with_debug_mode(true);
175 let source_manager = Arc::new(DefaultSourceManager::default());
176 let module = Module::parser(ModuleKind::Library).parse_str(
177 LibraryPath::new(library_path)?,
178 account_code,
179 &source_manager,
180 )?;
181 let library = assembler.clone().assemble_library([module])?;
182 Ok(library)
183}
184
185pub async fn create_basic_account(
196 client: &mut Client,
197 keystore: FilesystemKeyStore<StdRng>,
198) -> Result<(miden_client::account::Account, SecretKey), ClientError> {
199 let mut init_seed = [0_u8; 32];
200 client.rng().fill_bytes(&mut init_seed);
201
202 let key_pair = SecretKey::with_rng(client.rng());
203 let anchor_block = client.get_latest_epoch_block().await.unwrap();
204 let builder = AccountBuilder::new(init_seed)
205 .anchor((&anchor_block).try_into().unwrap())
206 .account_type(AccountType::RegularAccountUpdatableCode)
207 .storage_mode(AccountStorageMode::Public)
208 .with_component(RpoFalcon512::new(key_pair.public_key().clone()))
209 .with_component(BasicWallet);
210 let (account, seed) = builder.build().unwrap();
211 client.add_account(&account, Some(seed), false).await?;
212 keystore
213 .add_key(&AuthSecretKey::RpoFalcon512(key_pair.clone()))
214 .unwrap();
215
216 Ok((account, key_pair))
217}
218
219pub async fn create_basic_faucet(
230 client: &mut Client,
231 keystore: FilesystemKeyStore<StdRng>,
232) -> Result<miden_client::account::Account, ClientError> {
233 let mut init_seed = [0u8; 32];
234 client.rng().fill_bytes(&mut init_seed);
235 let key_pair = SecretKey::with_rng(client.rng());
236 let anchor_block = client.get_latest_epoch_block().await.unwrap();
237 let symbol = TokenSymbol::new("MID").unwrap();
238 let decimals = 8;
239 let max_supply = Felt::new(1_000_000);
240 let builder = AccountBuilder::new(init_seed)
241 .anchor((&anchor_block).try_into().unwrap())
242 .account_type(AccountType::FungibleFaucet)
243 .storage_mode(AccountStorageMode::Public)
244 .with_component(RpoFalcon512::new(key_pair.public_key()))
245 .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply).unwrap());
246 let (account, seed) = builder.build().unwrap();
247 client.add_account(&account, Some(seed), false).await?;
248 keystore
249 .add_key(&AuthSecretKey::RpoFalcon512(key_pair))
250 .unwrap();
251 Ok(account)
252}
253
254pub async fn setup_accounts_and_faucets(
271 client: &mut Client,
272 keystore: FilesystemKeyStore<StdRng>,
273 num_accounts: usize,
274 num_faucets: usize,
275 balances: Vec<Vec<u64>>,
276) -> Result<(Vec<Account>, Vec<Account>), ClientError> {
277 let mut accounts = Vec::with_capacity(num_accounts);
278 for i in 0..num_accounts {
279 let (account, _) = create_basic_account(client, keystore.clone()).await?;
280 println!("Created Account #{i} => ID: {:?}", account.id());
281 accounts.push(account);
282 }
283
284 let mut faucets = Vec::with_capacity(num_faucets);
285 for j in 0..num_faucets {
286 let faucet = create_basic_faucet(client, keystore.clone()).await?;
287 println!("Created Faucet #{j} => ID: {:?}", faucet.id());
288 faucets.push(faucet);
289 }
290
291 client.sync_state().await?;
292
293 for (acct_index, account) in accounts.iter().enumerate() {
294 for (faucet_index, faucet) in faucets.iter().enumerate() {
295 let amount_to_mint = balances[acct_index][faucet_index];
296 if amount_to_mint == 0 {
297 continue;
298 }
299
300 println!(
301 "Minting {amount_to_mint} tokens from Faucet #{faucet_index} to Account #{acct_index}"
302 );
303
304 let fungible_asset = FungibleAsset::new(faucet.id(), amount_to_mint).unwrap();
305 let tx_req = TransactionRequestBuilder::mint_fungible_asset(
306 fungible_asset,
307 account.id(),
308 NoteType::Public,
309 client.rng(),
310 )
311 .unwrap()
312 .build()
313 .unwrap();
314
315 let tx_exec = client.new_transaction(faucet.id(), tx_req).await?;
316 client.submit_transaction(tx_exec.clone()).await?;
317
318 let minted_note = if let OutputNote::Full(note) = tx_exec.created_notes().get_note(0) {
319 note.clone()
320 } else {
321 panic!("Expected OutputNote::Full, but got something else");
322 };
323
324 wait_for_notes(client, account, 1).await?;
325 client.sync_state().await?;
326
327 let consume_req = TransactionRequestBuilder::new()
328 .with_authenticated_input_notes([(minted_note.id(), None)])
329 .build()
330 .unwrap();
331
332 let tx_exec = client.new_transaction(account.id(), consume_req).await?;
333 client.submit_transaction(tx_exec).await?;
334 client.sync_state().await?;
335 }
336 }
337
338 Ok((accounts, faucets))
339}
340
341pub async fn mint_from_faucet_for_account(
359 client: &mut Client,
360 account: &Account,
361 faucet: &Account,
362 amount: u64,
363 tx_script: Option<TransactionScript>, ) -> Result<(), ClientError> {
365 if amount == 0 {
366 return Ok(());
367 }
368
369 let asset = FungibleAsset::new(faucet.id(), amount).unwrap();
370 let mint_req = TransactionRequestBuilder::mint_fungible_asset(
371 asset,
372 account.id(),
373 NoteType::Public,
374 client.rng(),
375 )?
376 .build()?;
377 let mint_exec = client.new_transaction(faucet.id(), mint_req).await?;
378 client.submit_transaction(mint_exec.clone()).await?;
379
380 let minted_note = match mint_exec.created_notes().get_note(0) {
381 OutputNote::Full(note) => note.clone(),
382 _ => panic!("Expected full minted note"),
383 };
384
385 wait_for_notes(client, account, 1).await?;
386 client.sync_state().await?;
387
388 let consume_req = if let Some(script) = tx_script {
389 TransactionRequestBuilder::new()
390 .with_authenticated_input_notes([(minted_note.id(), None)])
391 .with_custom_script(script)
392 .build()?
393 } else {
394 TransactionRequestBuilder::new()
395 .with_authenticated_input_notes([(minted_note.id(), None)])
396 .build()?
397 };
398
399 let consume_exec = client.new_transaction(account.id(), consume_req).await?;
400 client.submit_transaction(consume_exec.clone()).await?;
401 client.sync_state().await?;
402
403 Ok(())
404}
405
406pub async fn create_public_note(
424 client: &mut Client,
425 note_code: String,
426 account_library: Option<Library>,
427 creator_account: Account,
428 assets: Option<NoteAssets>,
429 note_inputs: Option<NoteInputs>,
430) -> Result<Note, Error> {
431 let assembler = if let Some(library) = account_library {
432 TransactionKernel::assembler()
433 .with_library(&library)
434 .unwrap()
435 } else {
436 TransactionKernel::assembler()
437 }
438 .with_debug_mode(true);
439
440 let rng = client.rng();
441 let serial_num = rng.draw_word();
442 let note_script = NoteScript::compile(note_code, assembler.clone()).unwrap();
443
444 let note_inputs = note_inputs.unwrap_or_else(|| NoteInputs::new([].to_vec()).unwrap());
445 let assets = assets.unwrap_or_else(|| NoteAssets::new(vec![]).unwrap());
446
447 let recipient = NoteRecipient::new(serial_num, note_script, note_inputs.clone());
448 let tag = NoteTag::for_public_use_case(0, 0, NoteExecutionMode::Local).unwrap();
449 let metadata = NoteMetadata::new(
450 creator_account.id(),
451 NoteType::Public,
452 tag,
453 NoteExecutionHint::always(),
454 Felt::new(0),
455 )
456 .unwrap();
457
458 let note = Note::new(assets, metadata, recipient);
459
460 let note_req = TransactionRequestBuilder::new()
461 .with_own_output_notes(vec![OutputNote::Full(note.clone())])
462 .build()
463 .unwrap();
464 let tx_result = client
465 .new_transaction(creator_account.id(), note_req)
466 .await
467 .unwrap();
468
469 let _ = client.submit_transaction(tx_result).await;
470 client.sync_state().await.unwrap();
471
472 Ok(note)
473}
474
475pub async fn wait_for_note(
489 client: &mut Client,
490 account_id: &Account,
491 expected: &Note,
492) -> Result<(), ClientError> {
493 loop {
494 client.sync_state().await?;
495
496 let notes: Vec<(InputNoteRecord, Vec<(AccountId, NoteRelevance)>)> =
497 client.get_consumable_notes(Some(account_id.id())).await?;
498
499 let found = notes.iter().any(|(rec, _)| rec.id() == expected.id());
500
501 if found {
502 println!("✅ note found {}", expected.id().to_hex());
503 break;
504 }
505
506 println!("Note {} not found. Waiting...", expected.id().to_hex());
507 sleep(Duration::from_secs(3)).await;
508 }
509 Ok(())
510}
511
512pub async fn wait_for_notes(
526 client: &mut Client,
527 account_id: &miden_client::account::Account,
528 expected: usize,
529) -> Result<(), ClientError> {
530 loop {
531 client.sync_state().await?;
532 let notes = client.get_consumable_notes(Some(account_id.id())).await?;
533 if notes.len() >= expected {
534 break;
535 }
536 println!(
537 "{} consumable notes found for account {}. Waiting...",
538 notes.len(),
539 account_id.id().to_hex()
540 );
541 sleep(Duration::from_secs(3)).await;
542 }
543 Ok(())
544}
545
546pub fn create_tx_script(
557 script_code: String,
558 library: Option<Library>,
559) -> Result<TransactionScript, Error> {
560 let assembler = TransactionKernel::assembler();
561
562 let assembler = match library {
563 Some(lib) => assembler.with_library(lib),
564 None => Ok(assembler.with_debug_mode(true)),
565 }
566 .unwrap();
567 let tx_script = TransactionScript::compile(script_code, [], assembler).unwrap();
568
569 Ok(tx_script)
570}
571
572pub fn create_exact_p2id_note(
587 sender: AccountId,
588 target: AccountId,
589 assets: Vec<Asset>,
590 note_type: NoteType,
591 aux: Felt,
592 serial_num: Word,
593) -> Result<Note, NoteError> {
594 let recipient = utils::build_p2id_recipient(target, serial_num)?;
595 let tag = NoteTag::from_account_id(target, NoteExecutionMode::Local)?;
596
597 let metadata = NoteMetadata::new(sender, note_type, tag, NoteExecutionHint::always(), aux)?;
598 let vault = NoteAssets::new(assets)?;
599
600 Ok(Note::new(vault, metadata, recipient))
601}