Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b1f583e
feat(standards): add the pass-through transaction script
claude Aug 25, 2026
84ed47a
docs: address review comments on the pass-through script
claude Aug 25, 2026
5671c34
refactor(standards): sweep the vault by asset instead of iterating notes
claude Aug 25, 2026
a179be1
refactor(standards): namespace the pass-through script by its output …
claude Aug 25, 2026
fba243c
fix(standards): repair the doc link and pin the asset bound to the sc…
claude Aug 25, 2026
c45dc12
docs(standards): fix the constant doc comments in the pass-through sc…
claude Aug 25, 2026
a6fda68
docs(standards): trim the pass-through module and constant comments
claude Aug 25, 2026
09ccc2a
test(standards): cover the pass-through component's untested paths
claude Aug 25, 2026
66e81ec
test(standards): tighten the pass-through root check
claude Aug 25, 2026
62799d8
fix(scripts): put the mirror note on the previous-release component list
claude Aug 25, 2026
8ea966d
Apply suggestions from code review
mmagician Aug 25, 2026
49dec91
docs(standards): warn that the sweep is a full-balance drain primitive
claude Aug 25, 2026
35d81ca
fix(standards): assert the pass-through account did not hold the swep…
claude Aug 25, 2026
6fe84a3
docs(standards): scope what the sweep's assert actually bounds
claude Aug 25, 2026
c66cf89
docs(standards): name the permissionless vector on the sweep component
claude Aug 25, 2026
d4a98dd
docs(standards): reconcile the sweep's safety rule with the fee-note …
claude Aug 25, 2026
18807a1
fix(standards): derive the payload bound from the locals frame
claude Aug 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Added `active_note::get_storage_info` and `active_note::get_bounded_storage`, and switched the standard and agglayer note scripts with a bounded storage layout over to the latter ([#3563](https://github.com/0xMiden/protocol/pull/3563)).
- [BREAKING] AggLayer bridge and faucet accounts now map note repricing to an initial `FEE_MNGR` role instead of the built-in `ADMIN` role ([#3571](https://github.com/0xMiden/protocol/issues/3571)).
- [BREAKING] AggLayer bridge accounts now map emergency pause to an initial `PAUSER` role, while unpause remains restricted to `ADMIN` ([#3572](https://github.com/0xMiden/protocol/issues/3572)).
- Added the `pass_through::single_p2id` transaction script with its `PassThroughSingleP2idTransactionScript` type and the `PassThrough` account component, forwarding the account's balance of the named assets into a single P2ID note and asserting the vault is left as the transaction found it ([#3709](https://github.com/0xMiden/protocol/issues/3709)).

### Changes

Expand Down
1 change: 1 addition & 0 deletions crates/miden-standards/asm/components/miden-project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ members = [
"inspection/code_inspection",
"inspection/schema_commitment",
"note/note_creator",
"pass_through",
"upgrade/manager",
"wallets/basic_wallet",
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "miden-standards-pass-through"
version.workspace = true

[lib]
kind = "account-component"
namespace = "miden::standards::components::pass_through"
path = "pass_through.masm"

[dependencies]
miden-core.workspace = true
miden-protocol.workspace = true
miden-standards.workspace = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# The MASM code of the Pass Through Account Component.
#
# See the `PassThrough` Rust type's documentation for more details.

pub use {sweep_asset_to_note} from miden::standards::pass_through
pub use {assert_vault_unchanged} from miden::standards::pass_through
1 change: 1 addition & 0 deletions crates/miden-standards/asm/standards/mod.masm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod inspection
pub mod interop
pub mod note
pub mod notes
pub mod pass_through
pub mod tx_scripts
pub mod utils
pub mod wallets
112 changes: 112 additions & 0 deletions crates/miden-standards/asm/standards/pass_through.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# The account procedures a pass-through transaction needs.
#
# See the `PassThrough` Rust type's documentation for more details.

use miden::core::word
use miden::protocol::active_account
use miden::protocol::native_account
use miden::protocol::output_note
use {AssetId} from miden::protocol::types

# ERRORS
# =================================================================================================

const ERR_PASS_THROUGH_ACCOUNT_ALREADY_HELD_ASSET = "a pass-through account must not hold the swept asset before the transaction"

const ERR_PASS_THROUGH_ACCOUNT_VAULT_CHANGED = "the account's vault is not the one it had at the start of the pass-through transaction"

# PUBLIC INTERFACE
# =================================================================================================

#! Moves the account's entire balance of the given asset into the output note at `note_idx`.
#!
#! Moving the whole balance is what makes a pass-through transaction independent of how many notes
#! it consumes: whatever the input notes deposited is a single balance by the time this runs, so
#! one call per asset drains it regardless of the number of notes.
#!
#! Does nothing if the account holds none of the asset.
#!
#! Unlike wallets::basic::move_asset_to_note, which makes the caller name the amount, this
#! procedure reads the balance itself, so it needs no prior knowledge of what the vault holds.
#! ERR_PASS_THROUGH_ACCOUNT_ALREADY_HELD_ASSET bounds it to what the transaction deposited, but
#! nothing bounds who moves that: any note script the account consumes can call this and redirect
#! what earlier notes deposited, and on an account whose auth procedure authenticates nobody -
#! which is what keeps a pass-through account's commitment unchanged - any third party can execute
#! a transaction as the account and name themselves as the destination.
#!
#! Assets passing through are therefore only safe if the input note's own script constrains where
#! they go, or if they were already unrestricted before they arrived. TX_FEE notes are the latter:
#! any account may consume one, so routing them through a pass-through account takes nothing away.
#! Routing a destination-restricted note through one instead destroys that restriction, since the
#! assets become claimable by whoever executes the next transaction as the account.
#!
#! Inputs: [ASSET_ID, note_idx, pad(11)]
#! Outputs: [pad(16)]
#!
#! Where:
#! - ASSET_ID is the ID of the asset to move.
#! - note_idx is the index of the output note to move the balance into.
#!
#! Panics if:
#! - the account held the asset when the transaction started.
#! - the maximum number of assets per note is exceeded.
#!
#! Invocation: call
@account_procedure
pub proc sweep_asset_to_note(asset_id: AssetId, note_idx: u16)
# only what this transaction deposited may be moved out, so the account must have started
# without the asset
Comment on lines +57 to +58

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd move this comment wording to the procedure comment above or delete it since this is already mentioned in the procedure comment

dupw exec.native_account::get_initial_asset exec.word::eqz
assert.err=ERR_PASS_THROUGH_ACCOUNT_ALREADY_HELD_ASSET
# => [ASSET_ID, note_idx, pad(11)]

dupw exec.active_account::get_asset
# => [ASSET_VALUE, ASSET_ID, note_idx, pad(11)]

dupw exec.word::eqz
# => [is_absent, ASSET_VALUE, ASSET_ID, note_idx, pad(11)]

if.true
# the account holds none of the asset, so there is nothing to move
dropw dropw drop
# => [pad(16)]
Comment on lines +69 to +72

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does nothing if the account holds none of the asset.

Is this desired? If you call sweep_asset_to_note and don't have the asset you're intending to move to the output note, should this not fail?

If you call this procedure now with an asset you don't have, the call to this procedure would succeed, but it would have no side effects.

else
swapw
# => [ASSET_ID, ASSET_VALUE, note_idx, pad(11)]

# remove the balance from the vault and add it to the note
# (see basic_wallet::move_asset_to_note for the same sequence)
dupw.1 dupw.1
# => [ASSET_ID, ASSET_VALUE, ASSET_ID, ASSET_VALUE, note_idx, pad(11)]

exec.native_account::remove_asset dropw
# => [ASSET_ID, ASSET_VALUE, note_idx, pad(11)]

exec.output_note::add_asset
# => [pad(16)]
end
end

#! Asserts that the account's vault is the one it had at the start of the transaction.
#!
#! A pass-through transaction script calls this once it has moved everything out, so that an asset
#! it failed to name fails the transaction rather than silently changing the account.
#!
#! Inputs: [pad(16)]
#! Outputs: [pad(16)]
#!
#! Panics if:
#! - the account's vault root differs from its initial one.
#!
#! Invocation: call
@account_procedure
pub proc assert_vault_unchanged
exec.native_account::get_initial_vault_root
# => [INITIAL_VAULT_ROOT, pad(16)]

exec.active_account::get_vault_root
# => [VAULT_ROOT, INITIAL_VAULT_ROOT, pad(16)]

assert_eqw.err=ERR_PASS_THROUGH_ACCOUNT_VAULT_CHANGED
# => [pad(16)]
end
1 change: 1 addition & 0 deletions crates/miden-standards/asm/standards/tx_scripts/mod.masm
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod expiration
pub mod pass_through
pub mod send_notes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# The canonical pass-through transaction scripts.
#
# What happens to the assets moved out depends on the exact script used (e.g. move all notes'
# assets into a single P2ID, move each note's assets into its own P2ID, etc.).

pub mod single_p2id
Loading
Loading