Part 8: Complete Flows
In this final section, we'll bring everything together and walk through the complete deposit and withdrawal flows, verifying that all the components work as a unified banking system.
What You'll Build in This Partβ
By the end of this section, you will have:
- Understood the complete deposit flow from note creation to balance update
- Understood the complete withdraw flow including P2ID note creation
- Verified the entire system works with an end-to-end MockChain test
- Completed the Miden Bank tutorial! π
Building on Parts 0-7β
You've built all the pieces. Now let's see them work together:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β COMPLETE BANK SYSTEM β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Components Built: β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β bank-account β Storage + deposit() + withdraw() β β
β βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ€ β
β β deposit-note β Note script β bank_account::deposit() β β
β βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ€ β
β β withdraw-note β Note script β bank_account::withdraw() β β
β βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ€ β
β β init-tx-script β Transaction script β initialize() β β
β βββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββ β
β β
β Storage Layout: β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Slot 0: initialized β Word: [1, 0, 0, 0] when ready β β
β β Slot 1: balances β Map: user_key β [balance, 0, 0, 0]β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The Complete Deposit Flowβ
Let's trace through exactly what happens when a user deposits tokens:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DEPOSIT FLOW β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. USER CREATES DEPOSIT NOTE β
β ββββββββββββββββββββββββ β
β β Deposit Note β β
β β sender: User β β
β β assets: [1000 tok] β β
β β script: deposit-noteβ β
β β target: Bank β β
β ββββββββββββββββββββββββ β
β β β
β βΌ β
β 2. BANK CONSUMES NOTE (Transaction begins) β
β ββββββββββββββββββββββββ β
β β Bank Account β β
β β vault += 1000 tokensβ βββ Protocol adds assets to vault β
β ββββββββββββββββββββββββ β
β β β
β βΌ β
β 3. NOTE SCRIPT EXECUTES β
β depositor = active_note::get_sender() β User's AccountId β
β assets = active_note::get_assets() β [1000 tokens] β
β for asset in assets: β
β bank_account::deposit(depositor, asset) βββ Cross-componentβ
β β β
β βΌ β
β 4. DEPOSIT METHOD RUNS (in bank-account context) β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β β require_initialized() β Passes β β
β β amount <= MAX_DEPOSIT β 1000 <= 100k β β
β β native_account::add_asset() β Confirm β β
β β balances[User] += 1000 β Update β β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β 5. TRANSACTION COMPLETES β
β Bank storage: balances[User] = 1000 β
β Bank vault: +1000 tokens β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The Complete Withdraw Flowβ
Now let's trace the withdrawal process:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WITHDRAW FLOW β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. USER CREATES WITHDRAW REQUEST NOTE β
β ββββββββββββββββββββββββββββββββ β
β β Withdraw Request Note β β
β β sender: User β β
β β inputs: [serial, tag, β β
β β aux, note_type] β β
β β assets: [withdraw amount] β β
β β target: Bank β β
β ββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β 2. BANK CONSUMES REQUEST (Transaction begins) β
β ββββββββββββββββββββββββββββββββ β
β β Note script executes: β β
β β sender = get_sender() β β
β β inputs = get_inputs() β β
β β asset = Asset from inputs β β
β β bank_account::withdraw(...) β β
β ββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β 3. WITHDRAW METHOD RUNS β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β require_initialized() β Passes β β
β β current_balance = get_balance(User) β 1000 β β
β β VALIDATE: 1000 >= 400 β Passes β β CRITICAL
β β balances[User] = 1000 - 400 β 600 β β
β β create_p2id_note(...) β Output note β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β 4. P2ID NOTE CREATED (inside create_p2id_note) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β script_root = p2id_note_root() β MAST digest β β
β β recipient = Recipient::compute( β β
β β serial_num, script_root, β β
β β [user.suffix, user.prefix, 0, 0, 0, 0, 0, 0] β β
β β ) β β
β β note_idx = output_note::create(tag, aux, ...) β β
β β native_account::remove_asset(400 tokens) β β
β β output_note::add_asset(400 tokens, note_idx) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β 5. TRANSACTION COMPLETES β
β Bank storage: balances[User] = 600 β
β Bank vault: -400 tokens β
β Output: P2ID note with 400 tokens β User β
β β β
β βΌ β
β 6. USER CONSUMES P2ID NOTE (separate transaction) β
β User's wallet receives 400 tokens β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Try It: Complete End-to-End Testβ
Let's create a comprehensive test that exercises the entire bank system:
use integration::helpers::{
build_project_in_dir, create_testing_account_from_package, create_testing_note_from_package,
AccountCreationConfig, NoteCreationConfig,
};
use miden_client::{
account::StorageMap,
note::{Note, NoteAssets, NoteExecutionHint, NoteMetadata, NoteTag, NoteType},
transaction::OutputNote,
Felt, Word,
};
use miden_lib::note::utils::build_p2id_recipient;
use miden_objects::{
account::AccountId,
asset::{Asset, FungibleAsset},
transaction::TransactionScript,
};
use miden_testing::{Auth, MockChain};
use std::{path::Path, sync::Arc};
/// Compute a P2ID note tag for a local account.
fn compute_p2id_tag_for_local_account(account_id: AccountId) -> NoteTag {
const LOCAL_ANY_PREFIX: u32 = 0xC000_0000;
const TAG_BITS: u8 = 14;
let prefix_u64 = account_id.prefix().as_u64();
let shifted = (prefix_u64 >> 34) as u32;
let mask = u32::MAX << (30 - TAG_BITS);
let account_bits = shifted & mask;
let tag_value = LOCAL_ANY_PREFIX | account_bits;
NoteTag::LocalAny(tag_value)
}
/// Complete end-to-end test of the Miden Bank
///
/// This test exercises:
/// 1. Bank initialization via transaction script
/// 2. Deposit via deposit-note
/// 3. Withdrawal via withdraw-request-note
/// 4. Balance verification at each step
#[tokio::test]
async fn test_complete_bank_flow() -> anyhow::Result<()> {
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("β MIDEN BANK - COMPLETE FLOW TEST β");
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SETUP
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
println!("\nπ¦ Setting up test environment...");
let mut builder = MockChain::builder();
let deposit_amount: u64 = 1000;
let withdraw_amount: u64 = 400;
// Create a faucet to mint test assets
let faucet =
builder.add_existing_basic_faucet(Auth::BasicAuth, "TEST", deposit_amount, Some(10))?;
// Create note sender account (the depositor)
let sender = builder.add_existing_wallet_with_assets(
Auth::BasicAuth,
[FungibleAsset::new(faucet.id(), deposit_amount)?.into()],
)?;
println!(" β Faucet and sender wallet created");
// Build all packages
let bank_package = Arc::new(build_project_in_dir(
Path::new("../contracts/bank-account"),
true,
)?);
let deposit_note_package = Arc::new(build_project_in_dir(
Path::new("../contracts/deposit-note"),
true,
)?);
let init_tx_script_package = Arc::new(build_project_in_dir(
Path::new("../contracts/init-tx-script"),
true,
)?);
let withdraw_request_note_package = Arc::new(build_project_in_dir(
Path::new("../contracts/withdraw-request-note"),
true,
)?);
println!(" β All packages built");
// Create bank account with storage slots
let bank_cfg = AccountCreationConfig {
storage_slots: vec![
miden_client::account::StorageSlot::Value(Word::default()),
miden_client::account::StorageSlot::Map(StorageMap::with_entries([])?),
],
..Default::default()
};
let mut bank_account =
create_testing_account_from_package(bank_package.clone(), bank_cfg).await?;
println!(" β Bank account created: {:?}", bank_account.id());
// Create deposit note with assets
let fungible_asset = FungibleAsset::new(faucet.id(), deposit_amount)?;
let note_assets = NoteAssets::new(vec![Asset::Fungible(fungible_asset)])?;
let deposit_note = create_testing_note_from_package(
deposit_note_package.clone(),
sender.id(),
NoteCreationConfig {
assets: note_assets,
..Default::default()
},
)?;
// Craft withdraw request note with 11-Felt input layout
let p2id_tag = compute_p2id_tag_for_local_account(sender.id());
let p2id_tag_u32 = match p2id_tag {
NoteTag::LocalAny(v) => v,
_ => panic!("Expected LocalAny tag"),
};
let p2id_tag_felt = Felt::new(p2id_tag_u32 as u64);
let p2id_output_note_serial_num = Word::from([
Felt::new(0x1234567890abcdef),
Felt::new(0xfedcba0987654321),
Felt::new(0xdeadbeefcafebabe),
Felt::new(0x0123456789abcdef),
]);
let aux = Felt::new(0);
let note_type_felt = Felt::new(1); // Public
// Note inputs: 11 Felts
// [0-3]: withdraw asset (amount, 0, faucet_suffix, faucet_prefix)
// [4-7]: serial_num
// [8]: tag
// [9]: aux
// [10]: note_type
let withdraw_request_note_inputs = vec![
Felt::new(withdraw_amount),
Felt::new(0),
faucet.id().suffix(),
faucet.id().prefix().as_felt(),
p2id_output_note_serial_num[0],
p2id_output_note_serial_num[1],
p2id_output_note_serial_num[2],
p2id_output_note_serial_num[3],
p2id_tag_felt,
aux,
note_type_felt,
];
let withdraw_request_note = create_testing_note_from_package(
withdraw_request_note_package.clone(),
sender.id(),
NoteCreationConfig {
inputs: withdraw_request_note_inputs,
..Default::default()
},
)?;
// Add to builder
builder.add_account(bank_account.clone())?;
builder.add_output_note(OutputNote::Full(deposit_note.clone().into()));
builder.add_output_note(OutputNote::Full(withdraw_request_note.clone().into()));
let mut mock_chain = builder.build()?;
println!(" β MockChain built");
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// STEP 1: Initialize the bank
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
println!("\n1οΈβ£ INITIALIZING BANK...");
let init_program = init_tx_script_package.unwrap_program();
let init_tx_script = TransactionScript::new((*init_program).clone());
let init_tx_context = mock_chain
.build_tx_context(bank_account.id(), &[], &[])?
.tx_script(init_tx_script)
.build()?;
let executed_init = init_tx_context.execute().await?;
bank_account.apply_delta(&executed_init.account_delta())?;
mock_chain.add_pending_executed_transaction(&executed_init)?;
mock_chain.prove_next_block()?;
println!(" β Bank initialized (storage[0] = [1, 0, 0, 0])");
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// STEP 2: Deposit tokens
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
println!("\n2οΈβ£ DEPOSITING TOKENS...");
println!(" Deposit amount: {} tokens", deposit_amount);
let deposit_tx_context = mock_chain
.build_tx_context(bank_account.id(), &[deposit_note.id()], &[])?
.build()?;
let executed_deposit = deposit_tx_context.execute().await?;
bank_account.apply_delta(&executed_deposit.account_delta())?;
mock_chain.add_pending_executed_transaction(&executed_deposit)?;
mock_chain.prove_next_block()?;
// Verify balance after deposit
let depositor_key = Word::from([
sender.id().prefix().as_felt(),
sender.id().suffix(),
faucet.id().prefix().as_felt(),
faucet.id().suffix(),
]);
let balance_after_deposit = bank_account.storage().get_map_item(1, depositor_key)?;
println!(
" β Bank processed deposit, balance: {} tokens",
balance_after_deposit[3].as_int()
);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// STEP 3: Withdraw tokens
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
println!("\n3οΈβ£ WITHDRAWING TOKENS...");
println!(" Withdraw amount: {} tokens", withdraw_amount);
// Build expected P2ID output note
let recipient = build_p2id_recipient(sender.id(), p2id_output_note_serial_num)?;
let p2id_output_note_asset = FungibleAsset::new(faucet.id(), withdraw_amount)?;
let p2id_output_note_assets = NoteAssets::new(vec![p2id_output_note_asset.into()])?;
let p2id_output_note_metadata = NoteMetadata::new(
bank_account.id(),
NoteType::Public,
p2id_tag,
NoteExecutionHint::none(),
aux,
)?;
let p2id_output_note = Note::new(
p2id_output_note_assets,
p2id_output_note_metadata,
recipient,
);
let withdraw_tx_context = mock_chain
.build_tx_context(bank_account.id(), &[withdraw_request_note.id()], &[])?
.extend_expected_output_notes(vec![OutputNote::Full(p2id_output_note.into())])
.build()?;
let executed_withdraw = withdraw_tx_context.execute().await?;
bank_account.apply_delta(&executed_withdraw.account_delta())?;
mock_chain.add_pending_executed_transaction(&executed_withdraw)?;
mock_chain.prove_next_block()?;
println!(" β Bank processed withdraw request");
println!(" β P2ID output note created for sender");
// Verify final balance
let final_balance = bank_account.storage().get_map_item(1, depositor_key)?;
let final_balance_amount = final_balance[3].as_int();
let expected_final = deposit_amount - withdraw_amount;
println!(" β Final balance verified: {} tokens", final_balance_amount);
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SUMMARY
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
println!("\nββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
println!("β TEST SUMMARY β");
println!("β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£");
println!(
"β Initial deposit: {:>6} tokens β",
deposit_amount
);
println!(
"β Withdrawal: -{:>6} tokens β",
withdraw_amount
);
println!(
"β Final balance: {:>6} tokens β",
final_balance_amount
);
println!("β β");
println!("β β
All operations completed successfully! β");
println!("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
assert_eq!(
final_balance_amount, expected_final,
"Final balance should be deposit - withdraw"
);
Ok(())
}
Run the complete test from the project root:
cargo test --package integration part8_complete_flow -- --nocapture
Expected output
Compiling integration v0.1.0 (/path/to/miden-bank/integration)
Finished `test` profile [unoptimized + debuginfo] target(s)
Running tests/part8_complete_flow_test.rs
running 1 test
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MIDEN BANK - COMPLETE FLOW TEST β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¦ Setting up test environment...
β Faucet and sender wallet created
β All packages built
β Bank account created: 0x...
β MockChain built
1οΈβ£ INITIALIZING BANK...
β Bank initialized (storage[0] = [1, 0, 0, 0])
2οΈβ£ DEPOSITING TOKENS...
Deposit amount: 1000 tokens
β Bank processed deposit, balance: 1000 tokens
3οΈβ£ WITHDRAWING TOKENS...
Withdraw amount: 400 tokens
β Bank processed withdraw request
β P2ID output note created for sender
β Final balance verified: 600 tokens
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TEST SUMMARY β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β Initial deposit: 1000 tokens β
β Withdrawal: - 400 tokens β
β Final balance: 600 tokens β
β β
β β
All operations completed successfully! β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
test test_complete_bank_flow ... ok
test result: ok. 1 passed; 0 failed; 0 ignored
Summary: All Componentsβ
Here's the complete picture of what you've built:
| Component | Type | Purpose |
|---|---|---|
bank-account | Account Component | Manages balances and vault |
deposit-note | Note Script | Processes incoming deposits |
withdraw-request-note | Note Script | Requests withdrawals |
init-tx-script | Transaction Script | Initializes the bank |
| Storage Slot | Type | Content |
|---|---|---|
| 0 | Value | Initialization flag |
| 1 | StorageMap | Depositor balances |
| API | Purpose |
|---|---|
active_note::get_sender() | Identify note creator |
active_note::get_assets() | Get attached assets |
active_note::get_inputs() | Get note parameters |
native_account::add_asset() | Receive into vault |
native_account::remove_asset() | Send from vault |
output_note::create() | Create output note |
output_note::add_asset() | Attach assets to note |
Key Security Patternsβ
Remember these critical patterns from this tutorial:
// β DANGEROUS: Silent underflow!
let new_balance = current_balance - withdraw_amount;
// β
SAFE: Validate first
assert!(
current_balance.as_u64() >= withdraw_amount.as_u64(),
"Insufficient balance"
);
let new_balance = Felt::from_u64_unchecked(
current_balance.as_u64() - withdraw_amount.as_u64()
);
Never use <, > on Felt values directly. Always convert to u64 first:
// β BROKEN: Produces incorrect results
if current_balance < withdraw_amount { ... }
// β
CORRECT: Use as_u64()
if current_balance.as_u64() < withdraw_amount.as_u64() { ... }
Congratulations! πβ
You've completed the Miden Bank tutorial! You now understand:
- β
Account components with storage (
ValueandStorageMap) - β Constants and constraints for business rules
- β Asset management with vault operations
- β Note scripts for processing incoming notes
- β Cross-component calls via generated bindings
- β Transaction scripts for owner operations
- β Output notes for sending assets (P2ID pattern)
- β Security patterns for safe arithmetic
Continue Learningβ
- Testing with MockChain - Deep dive into testing patterns
- Debugging Guide - Troubleshoot common issues
- Common Pitfalls - Avoid known gotchas
Build Moreβ
Use these patterns to build:
- Token faucets
- DEX contracts
- NFT marketplaces
- Multi-signature wallets
- And more!
Explore the complete banking application:
Happy building on Miden! π