diff --git a/CHANGELOG.md b/CHANGELOG.md index a3c6d9479a..154afba1d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v0.16.0-alpha.4 (2026-07-16) + +### Features + +- Added the canonical `ExpirationTransactionScript` to the transaction-script allowlists for AggLayer bridge and faucet accounts, allowing the network transaction builder to bound their transaction expiration. + ## v0.16.0-alpha.3 (2026-07-15) ### Fixes diff --git a/Cargo.lock b/Cargo.lock index baf7949403..a68c15ec5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1487,7 +1487,7 @@ dependencies = [ [[package]] name = "miden-agglayer" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "alloy-sol-types", "fs-err", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "miden-block-prover" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "miden-protocol", "thiserror", @@ -1860,7 +1860,7 @@ dependencies = [ [[package]] name = "miden-protocol" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "anyhow", "assert_matches", @@ -1932,7 +1932,7 @@ dependencies = [ [[package]] name = "miden-standards" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "anyhow", "assert_matches", @@ -1975,7 +1975,7 @@ dependencies = [ [[package]] name = "miden-testing" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "anyhow", "assert_matches", @@ -2002,7 +2002,7 @@ dependencies = [ [[package]] name = "miden-tx" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "miden-processor", "miden-protocol", @@ -2014,7 +2014,7 @@ dependencies = [ [[package]] name = "miden-tx-batch" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "miden-processor", "miden-protocol", diff --git a/Cargo.toml b/Cargo.toml index ed2d955d4f..c07b3a4cfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ homepage = "https://miden.xyz" license = "MIT" repository = "https://github.com/0xMiden/protocol" rust-version = "1.96.1" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" [profile.release] codegen-units = 1 @@ -36,13 +36,13 @@ lto = true [workspace.dependencies] # Workspace crates -miden-agglayer = { default-features = false, path = "crates/miden-agglayer", version = "0.16.0-alpha.3" } -miden-block-prover = { default-features = false, path = "crates/miden-block-prover", version = "0.16.0-alpha.3" } -miden-protocol = { default-features = false, path = "crates/miden-protocol", version = "0.16.0-alpha.3" } -miden-standards = { default-features = false, path = "crates/miden-standards", version = "0.16.0-alpha.3" } -miden-testing = { default-features = false, path = "crates/miden-testing", version = "0.16.0-alpha.3" } -miden-tx = { default-features = false, path = "crates/miden-tx", version = "0.16.0-alpha.3" } -miden-tx-batch = { default-features = false, path = "crates/miden-tx-batch", version = "0.16.0-alpha.3" } +miden-agglayer = { default-features = false, path = "crates/miden-agglayer", version = "0.16.0-alpha.4" } +miden-block-prover = { default-features = false, path = "crates/miden-block-prover", version = "0.16.0-alpha.4" } +miden-protocol = { default-features = false, path = "crates/miden-protocol", version = "0.16.0-alpha.4" } +miden-standards = { default-features = false, path = "crates/miden-standards", version = "0.16.0-alpha.4" } +miden-testing = { default-features = false, path = "crates/miden-testing", version = "0.16.0-alpha.4" } +miden-tx = { default-features = false, path = "crates/miden-tx", version = "0.16.0-alpha.4" } +miden-tx-batch = { default-features = false, path = "crates/miden-tx-batch", version = "0.16.0-alpha.4" } # Miden dependencies miden-assembly = { default-features = false, version = "0.25" } diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 9e78c02bed..f2dc93bdd4 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -3,12 +3,12 @@ extern crate alloc; use miden_core::{Felt, Word}; -use miden_protocol::account::{Account, AccountBuilder, AccountComponent, AccountId, AccountType}; +use miden_protocol::account::{Account, AccountBuilder, AccountComponent, AccountId}; use miden_protocol::assembly::Library; use miden_protocol::asset::TokenSymbol; use miden_protocol::utils::serde::Deserializable; use miden_standards::account::access::{Authority, Ownable2Step}; -use miden_standards::account::auth::AuthNetworkAccount; +use miden_standards::account::auth::NetworkAccount; use miden_standards::account::policies::{ BurnAllowAll, BurnPolicy, @@ -131,22 +131,15 @@ fn create_agglayer_faucet_component( /// /// The bridge starts with an empty faucet registry. Faucets are registered at runtime /// via CONFIG_AGG_BRIDGE notes that call `bridge_config::register_faucet`. -/// -/// The builder is pre-wired with the [`AuthNetworkAccount`] auth component, initialized with -/// [`AggLayerBridge::allowed_notes()`] so the bridge only accepts its sanctioned input notes. fn create_bridge_account_builder( seed: Word, bridge_admin_id: AccountId, ger_injector_id: AccountId, ger_remover_id: AccountId, ) -> AccountBuilder { - Account::builder(seed.into()) - .account_type(AccountType::Public) + NetworkAccount::builder(seed.into(), AggLayerBridge::allowed_notes()) + .expect("bridge note allowlist is non-empty") .with_component(AggLayerBridge::new(bridge_admin_id, ger_injector_id, ger_remover_id)) - .with_auth_component( - AuthNetworkAccount::with_allowed_notes(AggLayerBridge::allowed_notes()) - .expect("bridge note allowlist is non-empty"), - ) } /// Creates a new bridge account with the standard configuration. @@ -189,8 +182,10 @@ pub fn create_existing_bridge_account( /// mint policy component (`MintOwnerOnly`) and burn policy component (`BurnOwnerOnly`) are /// produced by the manager; `BurnAllowAll` is installed separately as the additional allowed burn /// policy procedure. -/// - The [`AuthNetworkAccount`] auth component, initialized with -/// [`AggLayerFaucet::allowed_notes()`] so the faucet only accepts MINT and BURN notes. +/// - The network-account auth component, installed via [`NetworkAccount::builder`] with +/// [`AggLayerFaucet::allowed_notes()`] so the faucet only accepts MINT and BURN notes. The +/// tx-script allowlist contains only the canonical +/// [`ExpirationTransactionScript`](miden_standards::tx_script::ExpirationTransactionScript). fn create_agglayer_faucet_builder( seed: Word, token_symbol: &str, @@ -212,17 +207,13 @@ fn create_agglayer_faucet_builder( .active_receive_policy(TransferPolicy::allow_all()) .build(); - Account::builder(seed.into()) - .account_type(AccountType::Public) + NetworkAccount::builder(seed.into(), AggLayerFaucet::allowed_notes()) + .expect("faucet note allowlist is non-empty") .with_component(agglayer_component) .with_component(Ownable2Step::new(bridge_account_id)) .with_component(Authority::OwnerControlled) .with_components(token_policy_manager) .with_component(BurnAllowAll) - .with_auth_component( - AuthNetworkAccount::with_allowed_notes(AggLayerFaucet::allowed_notes()) - .expect("faucet note allowlist is non-empty"), - ) } /// Creates a new agglayer faucet account with the specified configuration. @@ -298,3 +289,36 @@ pub fn create_existing_agglayer_faucet_with_callbacks( .build_existing() .expect("agglayer faucet account should be valid") } + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE; + use miden_standards::tx_script::ExpirationTransactionScript; + + use super::*; + + /// Both agglayer network accounts allowlist the canonical [`ExpirationTransactionScript`], + /// which the network transaction builder attaches to every network transaction. + #[test] + fn agglayer_accounts_allowlist_expiration_tx_script() { + let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); + + let bridge = create_existing_bridge_account(Word::default(), id, id, id); + let faucet = create_existing_agglayer_faucet( + Word::default(), + "AGG", + 6, + Felt::from(1000u32), + Felt::ZERO, + id, + ); + + for account in [bridge, faucet] { + let network_account = NetworkAccount::try_from(account).unwrap(); + assert!(network_account.allows_tx_script(&ExpirationTransactionScript::script_root())); + } + } +} diff --git a/crates/miden-standards/src/account/auth/mod.rs b/crates/miden-standards/src/account/auth/mod.rs index 66abea07fa..9704a1e907 100644 --- a/crates/miden-standards/src/account/auth/mod.rs +++ b/crates/miden-standards/src/account/auth/mod.rs @@ -23,6 +23,7 @@ mod network_account; pub use network_account::{ AuthNetworkAccount, NetworkAccount, + NetworkAccountError, NetworkAccountNoteAllowlist, NetworkAccountNoteAllowlistError, NetworkAccountTxScriptAllowlist, diff --git a/crates/miden-standards/src/account/auth/network_account/mod.rs b/crates/miden-standards/src/account/auth/network_account/mod.rs index d1362af9ab..d689a49444 100644 --- a/crates/miden-standards/src/account/auth/network_account/mod.rs +++ b/crates/miden-standards/src/account/auth/network_account/mod.rs @@ -3,7 +3,7 @@ pub use auth_network_account::AuthNetworkAccount; #[allow(clippy::module_inception)] mod network_account; -pub use network_account::NetworkAccount; +pub use network_account::{NetworkAccount, NetworkAccountError}; mod note_allowlist; pub use note_allowlist::{NetworkAccountNoteAllowlist, NetworkAccountNoteAllowlistError}; diff --git a/crates/miden-standards/src/account/auth/network_account/network_account.rs b/crates/miden-standards/src/account/auth/network_account/network_account.rs index 9f3e9aef7b..62062b68bd 100644 --- a/crates/miden-standards/src/account/auth/network_account/network_account.rs +++ b/crates/miden-standards/src/account/auth/network_account/network_account.rs @@ -1,9 +1,17 @@ -use miden_protocol::account::{Account, AccountId, AccountStorage}; +use alloc::collections::BTreeSet; + +use miden_protocol::account::{Account, AccountBuilder, AccountId, AccountStorage, AccountType}; +use miden_protocol::note::NoteScriptRoot; +use miden_protocol::transaction::TransactionScriptRoot; use crate::account::auth::network_account::{ + AuthNetworkAccount, NetworkAccountNoteAllowlist, NetworkAccountNoteAllowlistError, + NetworkAccountTxScriptAllowlist, + NetworkAccountTxScriptAllowlistError, }; +use crate::tx_script::ExpirationTransactionScript; // NETWORK ACCOUNT // ================================================================================================ @@ -22,14 +30,19 @@ use crate::account::auth::network_account::{ /// - the slot MUST be a [`StorageMap`](miden_protocol::account::StorageMap) (not a value slot), /// - the map MUST be non-empty (the allowlist contains at least one allowed /// [`NoteScriptRoot`](miden_protocol::note::NoteScriptRoot)). +/// - Its storage MUST contain a [`NetworkAccountTxScriptAllowlist`] slot, and the slot (a +/// [`StorageMap`](miden_protocol::account::StorageMap)) MUST contain the root of the canonical +/// [`ExpirationTransactionScript`]. The network transaction builder attaches that script to every +/// network transaction it executes, so an account without the root could never be serviced. /// -/// The allowlist slot is the shared abstraction across every network-account component, so -/// off-chain services can identify a network account by inspecting its storage for this slot -/// without needing to know which specific component the account uses. +/// The allowlist slots are the shared abstraction across every network-account component: they let +/// off-chain services identify a network account without knowing which component it uses. +/// [`NetworkAccount::builder`] produces accounts satisfying this specification by construction. #[derive(Clone, Debug, PartialEq, Eq)] pub struct NetworkAccount { account: Account, - allowlist: NetworkAccountNoteAllowlist, + note_allowlist: NetworkAccountNoteAllowlist, + tx_script_allowlist: NetworkAccountTxScriptAllowlist, } impl NetworkAccount { @@ -38,15 +51,62 @@ impl NetworkAccount { /// Returns an error if: /// - the account is not [`public`](Account::is_public), or /// - the account's storage does not contain a valid [`NetworkAccountNoteAllowlist`] slot (see - /// [`NetworkAccountNoteAllowlist::try_from`] for the exact storage-level checks). - pub fn new(account: Account) -> Result { + /// [`NetworkAccountNoteAllowlist::try_from`] for the exact storage-level checks), or + /// - the account's storage does not contain a valid [`NetworkAccountTxScriptAllowlist`] slot, + /// or the allowlist does not contain the canonical [`ExpirationTransactionScript`] root. + pub fn new(account: Account) -> Result { if !account.is_public() { - return Err(NetworkAccountNoteAllowlistError::AccountNotPublic(account.id())); + return Err(NetworkAccountError::AccountNotPublic(account.id())); + } + + let note_allowlist = NetworkAccountNoteAllowlist::try_from(account.storage()) + .map_err(NetworkAccountError::NoteAllowlist)?; + + let tx_script_allowlist = NetworkAccountTxScriptAllowlist::try_from(account.storage()) + .map_err(NetworkAccountError::TxScriptAllowlist)?; + if !tx_script_allowlist + .allowed_script_roots() + .contains(&ExpirationTransactionScript::script_root()) + { + return Err(NetworkAccountError::ExpirationScriptNotAllowlisted); } - let allowlist = NetworkAccountNoteAllowlist::try_from(account.storage())?; + Ok(Self { + account, + note_allowlist, + tx_script_allowlist, + }) + } - Ok(Self { account, allowlist }) + /// Creates an [`AccountBuilder`] pre-configured as a network account. + /// + /// The returned builder is set to [`AccountType::Public`] and has the [`AuthNetworkAccount`] + /// auth component installed with the provided note allowlist. The component's tx-script + /// allowlist contains the canonical [`ExpirationTransactionScript`], which the network + /// transaction builder attaches to every network transaction it executes, so the built + /// account is serviceable by the network by construction. + /// + /// Callers add their functional components to the returned builder and finish with + /// [`AccountBuilder::build`]; the built account satisfies the [`NetworkAccount`] + /// specification. Accounts that need to allowlist additional transaction scripts should + /// construct the [`AuthNetworkAccount`] component manually instead. + /// + /// # Errors + /// + /// Returns an error if `allowed_notes` is empty since the account could not consume any + /// notes. + pub fn builder( + init_seed: [u8; 32], + allowed_notes: BTreeSet, + ) -> Result { + let auth_component = AuthNetworkAccount::with_allowed_notes(allowed_notes)? + .with_allowed_tx_scripts( + [ExpirationTransactionScript::script_root()].into_iter().collect(), + ); + + Ok(AccountBuilder::new(init_seed) + .account_type(AccountType::Public) + .with_auth_component(auth_component)) } /// Consumes `self` and returns the underlying [`Account`]. @@ -71,18 +131,51 @@ impl NetworkAccount { /// Returns the [`NetworkAccountNoteAllowlist`] decoded from the underlying account's storage. pub fn allowed_notes(&self) -> &NetworkAccountNoteAllowlist { - &self.allowlist + &self.note_allowlist + } + + /// Returns the [`NetworkAccountTxScriptAllowlist`] decoded from the underlying account's + /// storage. It always contains at least the canonical [`ExpirationTransactionScript`] root. + pub fn allowed_tx_scripts(&self) -> &NetworkAccountTxScriptAllowlist { + &self.tx_script_allowlist + } + + /// Returns `true` if the account allowlists the transaction script with the given `root`. + /// + /// A transaction executed against this account that carries a transaction script whose root + /// is not allowlisted is rejected by the account's auth procedure. + pub fn allows_tx_script(&self, root: &TransactionScriptRoot) -> bool { + self.tx_script_allowlist.allowed_script_roots().contains(root) } } impl TryFrom for NetworkAccount { - type Error = NetworkAccountNoteAllowlistError; + type Error = NetworkAccountError; fn try_from(account: Account) -> Result { Self::new(account) } } +// NETWORK ACCOUNT ERROR +// ================================================================================================ + +/// Errors that can occur when constructing a [`NetworkAccount`] from an [`Account`]. +#[derive(Debug, thiserror::Error)] +pub enum NetworkAccountError { + #[error("network account must have public account type, but account {0} does not")] + AccountNotPublic(AccountId), + #[error("failed to decode the note-script allowlist from account storage")] + NoteAllowlist(#[source] NetworkAccountNoteAllowlistError), + #[error("failed to decode the tx-script allowlist from account storage")] + TxScriptAllowlist(#[source] NetworkAccountTxScriptAllowlistError), + #[error( + "network account tx-script allowlist must contain the canonical expiration transaction \ + script root" + )] + ExpirationScriptNotAllowlisted, +} + // TESTS // ================================================================================================ @@ -90,6 +183,7 @@ impl TryFrom for NetworkAccount { mod tests { use alloc::collections::BTreeSet; + use miden_protocol::Word; use miden_protocol::account::{AccountBuilder, AccountType}; use miden_protocol::note::NoteScriptRoot; @@ -101,7 +195,11 @@ mod tests { AccountBuilder::new([0; 32]) .account_type(account_type) .with_auth_component( - AuthNetworkAccount::with_allowed_notes(roots).expect("non-empty allowlist"), + AuthNetworkAccount::with_allowed_notes(roots) + .expect("non-empty allowlist") + .with_allowed_tx_scripts( + [ExpirationTransactionScript::script_root()].into_iter().collect(), + ), ) .with_component(BasicWallet) .build() @@ -129,7 +227,7 @@ mod tests { let err = NetworkAccount::new(account).expect_err("private account must be rejected"); assert!(matches!( err, - NetworkAccountNoteAllowlistError::AccountNotPublic(account_id) if account_id == id + NetworkAccountError::AccountNotPublic(account_id) if account_id == id )); } @@ -143,6 +241,47 @@ mod tests { .expect("account building should succeed"); let err = NetworkAccount::new(account).expect_err("missing allowlist must be rejected"); - assert!(matches!(err, NetworkAccountNoteAllowlistError::SlotNotFound)); + assert!(matches!( + err, + NetworkAccountError::NoteAllowlist(NetworkAccountNoteAllowlistError::SlotNotFound) + )); + } + + /// An account whose tx-script allowlist lacks the canonical expiration script root is not a + /// network account: the network transaction builder could never service it. + #[test] + fn account_without_expiration_script_is_rejected() { + let note_root = NoteScriptRoot::from_array([1, 2, 3, 4]); + let account = AccountBuilder::new([0; 32]) + .account_type(AccountType::Public) + .with_auth_component( + AuthNetworkAccount::with_allowed_notes(BTreeSet::from_iter([note_root])) + .expect("non-empty allowlist"), + ) + .with_component(BasicWallet) + .build() + .expect("account building should succeed"); + + let err = NetworkAccount::new(account).expect_err("missing expiration root"); + assert!(matches!(err, NetworkAccountError::ExpirationScriptNotAllowlisted)); + } + + /// `NetworkAccount::builder` produces an account that satisfies the network account + /// specification and allowlists the canonical expiration tx script. + #[test] + fn builder_produces_network_account_with_expiration_script_allowlisted() { + let note_root = NoteScriptRoot::from_array([1, 2, 3, 4]); + + let account = NetworkAccount::builder([0; 32], BTreeSet::from_iter([note_root])) + .expect("non-empty allowlist") + .with_component(BasicWallet) + .build() + .expect("account building should succeed"); + + let network_account = NetworkAccount::new(account).expect("should be a network account"); + assert!(network_account.allows_tx_script(&ExpirationTransactionScript::script_root())); + + let other_root = TransactionScriptRoot::from_raw(Word::from([9u32, 10, 11, 12])); + assert!(!network_account.allows_tx_script(&other_root)); } } diff --git a/crates/miden-standards/src/account/auth/network_account/note_allowlist.rs b/crates/miden-standards/src/account/auth/network_account/note_allowlist.rs index 5d458e39ff..5d1ff3218e 100644 --- a/crates/miden-standards/src/account/auth/network_account/note_allowlist.rs +++ b/crates/miden-standards/src/account/auth/network_account/note_allowlist.rs @@ -2,7 +2,6 @@ use alloc::collections::BTreeSet; use miden_protocol::account::component::{SchemaType, StorageSlotSchema}; use miden_protocol::account::{ - AccountId, AccountStorage, StorageMap, StorageMapKey, @@ -158,8 +157,6 @@ pub enum NetworkAccountNoteAllowlistError { NetworkAccountNoteAllowlist::slot_name() )] UnexpectedSlotType, - #[error("network account must have public account type, but account {0} does not")] - AccountNotPublic(AccountId), } // TESTS diff --git a/crates/miden-standards/src/account/faucets/fungible/mod.rs b/crates/miden-standards/src/account/faucets/fungible/mod.rs index 50cdb04f3c..bbb0544e65 100644 --- a/crates/miden-standards/src/account/faucets/fungible/mod.rs +++ b/crates/miden-standards/src/account/faucets/fungible/mod.rs @@ -36,16 +36,10 @@ use super::{ }; use crate::account::access::{AccessControl, Authority, Pausable, PausableManager}; use crate::account::account_component_code; -use crate::account::auth::{ - AuthGuardedMultisig, - AuthMultisig, - AuthNetworkAccount, - AuthSingleSigAcl, -}; +use crate::account::auth::{AuthGuardedMultisig, AuthMultisig, AuthSingleSigAcl, NetworkAccount}; use crate::account::policies::TokenPolicyManager; use crate::note::{BurnNote, MintNote}; use crate::procedure_root; -use crate::tx_script::ExpirationTransactionScript; #[cfg(test)] mod tests; @@ -643,16 +637,11 @@ pub fn create_network_fungible_faucet( token_policy_manager: TokenPolicyManager, ) -> Result { let note_allowlist = [MintNote::script_root(), BurnNote::script_root()].into_iter().collect(); - let tx_script_allowlist = [ExpirationTransactionScript::script_root()].into_iter().collect(); - let auth_component = AuthNetworkAccount::with_allowed_notes(note_allowlist) - .expect("MintNote + BurnNote allowlist is non-empty") - .with_allowed_tx_scripts(tx_script_allowlist); - let asset_callbacks = AssetCallbackFlag::from(token_policy_manager.has_transfer_policy()); - AccountBuilder::new(init_seed) - .account_type(AccountType::Public) + + NetworkAccount::builder(init_seed, note_allowlist) + .expect("MintNote + BurnNote allowlist is non-empty") .with_asset_callbacks(asset_callbacks) - .with_auth_component(auth_component) .with_component(faucet) .with_components(access_control) .with_components(token_policy_manager) diff --git a/crates/miden-standards/src/account/faucets/non_fungible/mod.rs b/crates/miden-standards/src/account/faucets/non_fungible/mod.rs index 6fd3184984..ddcdc6d126 100644 --- a/crates/miden-standards/src/account/faucets/non_fungible/mod.rs +++ b/crates/miden-standards/src/account/faucets/non_fungible/mod.rs @@ -37,11 +37,10 @@ use super::{ }; use crate::account::access::{AccessControl, Authority, Pausable, PausableManager}; use crate::account::account_component_code; -use crate::account::auth::{AuthNetworkAccount, AuthSingleSigAcl}; +use crate::account::auth::{AuthSingleSigAcl, NetworkAccount}; use crate::account::policies::TokenPolicyManager; use crate::note::{BurnNote, MintNote}; use crate::procedure_root; -use crate::tx_script::ExpirationTransactionScript; #[cfg(test)] mod tests; @@ -508,10 +507,11 @@ pub fn create_user_non_fungible_faucet( /// [`AccountType::Public`]. Setter gating is enforced in-procedure by the owner / role check /// installed via `access_control` ([`AccessControl::Ownable2Step`] or [`AccessControl::Rbac`]). /// -/// The factory builds the [`AuthNetworkAccount`] auth component internally with a note allowlist -/// covering the faucet's own [`MintNote`] and [`BurnNote`] scripts, plus a -/// tx-script allowlist covering the canonical expiration setter -/// ([`ExpirationTransactionScript`]) so the network can bound its own transactions' expiry. +/// The factory builds the account via [`NetworkAccount::builder`] with a note allowlist covering +/// the faucet's own [`MintNote`] and [`BurnNote`] scripts; the builder also allowlists the +/// canonical expiration setter +/// ([`ExpirationTransactionScript`](crate::tx_script::ExpirationTransactionScript)) so the +/// network can bound its own transactions' expiry. pub fn create_network_non_fungible_faucet( init_seed: [u8; 32], faucet: NonFungibleFaucet, @@ -519,14 +519,9 @@ pub fn create_network_non_fungible_faucet( token_policy_manager: TokenPolicyManager, ) -> Result { let note_allowlist = [MintNote::script_root(), BurnNote::script_root()].into_iter().collect(); - let tx_script_allowlist = [ExpirationTransactionScript::script_root()].into_iter().collect(); - let auth_component = AuthNetworkAccount::with_allowed_notes(note_allowlist) - .expect("MintNote + BurnNote allowlist is non-empty") - .with_allowed_tx_scripts(tx_script_allowlist); - AccountBuilder::new(init_seed) - .account_type(AccountType::Public) - .with_auth_component(auth_component) + NetworkAccount::builder(init_seed, note_allowlist) + .expect("MintNote + BurnNote allowlist is non-empty") .with_component(faucet) .with_components(access_control) .with_components(token_policy_manager)