Skip to main content

miden_client/transaction/request/
mod.rs

1//! Contains structures and functions related to transaction creation.
2
3use alloc::boxed::Box;
4use alloc::collections::{BTreeMap, BTreeSet};
5use alloc::string::{String, ToString};
6use alloc::vec::Vec;
7use core::num::NonZeroU16;
8
9use miden_protocol::Word;
10use miden_protocol::account::{AccountCodeInterface, AccountId};
11use miden_protocol::asset::{Asset, NonFungibleAsset};
12use miden_protocol::crypto::merkle::MerkleError;
13use miden_protocol::crypto::merkle::store::MerkleStore;
14use miden_protocol::errors::{
15    AccountError,
16    AssetError,
17    AssetVaultError,
18    NoteError,
19    StorageMapError,
20    TransactionInputError,
21    TransactionScriptError,
22};
23use miden_protocol::note::{
24    Note,
25    NoteDetails,
26    NoteDetailsCommitment,
27    NoteId,
28    NoteRecipient,
29    NoteScript,
30    NoteTag,
31    PartialNote,
32};
33use miden_protocol::transaction::{InputNote, InputNotes, TransactionArgs, TransactionScript};
34use miden_protocol::vm::AdviceMap;
35use miden_standards::errors::CodeBuilderError;
36use miden_standards::tx_script::{SendNotesTransactionScript, SendNotesTransactionScriptError};
37use miden_tx::utils::serde::{
38    ByteReader,
39    ByteWriter,
40    Deserializable,
41    DeserializationError,
42    Serializable,
43};
44use thiserror::Error;
45
46mod builder;
47pub use builder::{
48    PaymentNoteDescription,
49    PswapTransactionData,
50    SwapTransactionData,
51    TransactionRequestBuilder,
52};
53
54mod foreign;
55pub use foreign::ForeignAccount;
56pub(crate) use foreign::account_proof_into_inputs;
57
58use crate::store::InputNoteRecord;
59
60// TRANSACTION REQUEST
61// ================================================================================================
62
63pub type NoteArgs = Word;
64
65/// Specifies a transaction script to be executed in a transaction.
66///
67/// A transaction script is a program which is executed after scripts of all input notes have been
68/// executed.
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub enum TransactionScriptTemplate {
71    /// Specifies the exact transaction script to be executed in a transaction.
72    CustomScript(TransactionScript),
73    /// Specifies that the transaction script must create the specified output notes.
74    ///
75    /// It is up to the client to determine how the output notes will be created and this will
76    /// depend on the capabilities of the account the transaction request will be applied to.
77    /// For example, for Basic Wallets, this may involve invoking `create_note` procedure.
78    SendNotes(Vec<PartialNote>),
79}
80
81/// Specifies a transaction request that can be executed by an account.
82///
83/// A request contains information about input notes to be consumed by the transaction (if any),
84/// description of the transaction script to be executed (if any), and a set of notes expected
85/// to be generated by the transaction or by consuming notes generated by the transaction.
86#[derive(Clone, Debug, PartialEq, Eq)]
87pub struct TransactionRequest {
88    /// Notes to be consumed by the transaction.
89    /// includes both authenticated and unauthenticated notes.
90    /// Notes which ID is present in the store are considered authenticated,
91    /// the ones which ID is does not exist are considered unauthenticated.
92    input_notes: Vec<Note>,
93    /// Optional arguments of the input notes to be consumed by the transaction. This
94    /// includes both authenticated and unauthenticated notes.
95    input_notes_args: Vec<(NoteId, Option<NoteArgs>)>,
96    /// Template for the creation of the transaction script.
97    script_template: Option<TransactionScriptTemplate>,
98    /// A map of recipients of the output notes expected to be generated by the transaction.
99    expected_output_recipients: BTreeMap<Word, NoteRecipient>,
100    /// A map of details and tags of notes we expect to be created as part of future transactions
101    /// with their respective tags.
102    ///
103    /// For example, after a swap note is consumed, a payback note is expected to be created.
104    expected_future_notes: BTreeMap<NoteDetailsCommitment, (NoteDetails, NoteTag)>,
105    /// Initial state of the `AdviceMap` that provides data during runtime.
106    advice_map: AdviceMap,
107    /// Initial state of the `MerkleStore` that provides data during runtime.
108    merkle_store: MerkleStore,
109    /// Foreign account data requirements keyed by account ID. At execution time, account data
110    /// will be retrieved from the network, and injected as advice inputs. Additionally, the
111    /// account's code will be added to the executor and prover.
112    foreign_accounts: BTreeMap<AccountId, ForeignAccount>,
113    /// The number of blocks in relation to the transaction's reference block after which the
114    /// transaction will expire. If `None`, the transaction will not expire.
115    expiration_delta: Option<u16>,
116    /// Indicates whether to **silently** ignore invalid input notes when executing the
117    /// transaction. This will allow the transaction to be executed even if some input notes
118    /// are invalid.
119    ignore_invalid_input_notes: bool,
120    /// Optional [`Word`] that will be pushed to the operand stack before the transaction script
121    /// execution.
122    script_arg: Option<Word>,
123    /// Optional [`Word`] that will be pushed to the stack for the authentication procedure
124    /// during transaction execution.
125    auth_arg: Option<Word>,
126    /// Whether the auth arg carries fee conversion info set through
127    /// [`TransactionRequestBuilder::fee_conversion_info`], which only accounts with a
128    /// fee-conversion-aware auth component can consume.
129    declares_fee_conversion_info: bool,
130    /// Note scripts that the node's NTX builder will need in its script registry.
131    ///
132    /// See [`TransactionRequestBuilder::expected_ntx_scripts`] for details.
133    expected_ntx_scripts: Vec<NoteScript>,
134}
135
136impl TransactionRequest {
137    // PUBLIC ACCESSORS
138    // --------------------------------------------------------------------------------------------
139
140    /// Returns a reference to the transaction request's input note list.
141    pub fn input_notes(&self) -> &[Note] {
142        &self.input_notes
143    }
144
145    /// Returns a list of all input note IDs.
146    pub fn input_note_ids(&self) -> impl Iterator<Item = NoteId> {
147        self.input_notes.iter().map(Note::id)
148    }
149
150    /// Returns the assets held by the transaction's input notes.
151    pub fn incoming_assets(&self) -> (BTreeMap<AccountId, u64>, Vec<NonFungibleAsset>) {
152        collect_assets(self.input_notes.iter().flat_map(|note| note.assets().iter()))
153    }
154
155    /// Returns a map of note IDs to their respective [`NoteArgs`]. The result will include
156    /// exclusively note IDs for notes for which [`NoteArgs`] have been defined.
157    pub fn get_note_args(&self) -> BTreeMap<NoteId, NoteArgs> {
158        self.input_notes_args
159            .iter()
160            .filter_map(|(note, args)| args.map(|a| (*note, a)))
161            .collect()
162    }
163
164    /// Returns the expected output own notes of the transaction.
165    ///
166    /// In this context "own notes" refers to notes that are expected to be created directly by the
167    /// transaction script, rather than notes that are created as a result of consuming other
168    /// notes.
169    pub fn expected_output_own_notes(&self) -> Vec<Note> {
170        match &self.script_template {
171            Some(TransactionScriptTemplate::SendNotes(notes)) => notes
172                .iter()
173                .map(|partial| {
174                    Note::with_attachments(
175                        partial.assets().clone(),
176                        *partial.partial_metadata(),
177                        self.expected_output_recipients
178                            .get(&partial.recipient_digest())
179                            .expect("Recipient should be included if it's an own note")
180                            .clone(),
181                        partial.attachments().clone(),
182                    )
183                })
184                .collect(),
185            _ => vec![],
186        }
187    }
188
189    /// Returns an iterator over the expected output notes.
190    pub fn expected_output_recipients(&self) -> impl Iterator<Item = &NoteRecipient> {
191        self.expected_output_recipients.values()
192    }
193
194    /// Returns an iterator over expected future notes.
195    pub fn expected_future_notes(&self) -> impl Iterator<Item = &(NoteDetails, NoteTag)> {
196        self.expected_future_notes.values()
197    }
198
199    /// Returns the [`TransactionScriptTemplate`].
200    pub fn script_template(&self) -> &Option<TransactionScriptTemplate> {
201        &self.script_template
202    }
203
204    /// Returns the [`AdviceMap`] for the transaction request.
205    pub fn advice_map(&self) -> &AdviceMap {
206        &self.advice_map
207    }
208
209    /// Returns a mutable reference to the [`AdviceMap`] for the transaction request.
210    pub fn advice_map_mut(&mut self) -> &mut AdviceMap {
211        &mut self.advice_map
212    }
213
214    /// Returns the [`MerkleStore`] for the transaction request.
215    pub fn merkle_store(&self) -> &MerkleStore {
216        &self.merkle_store
217    }
218
219    /// Returns the required foreign accounts keyed by account ID.
220    pub fn foreign_accounts(&self) -> &BTreeMap<AccountId, ForeignAccount> {
221        &self.foreign_accounts
222    }
223
224    /// Returns whether to ignore invalid input notes or not.
225    pub fn ignore_invalid_input_notes(&self) -> bool {
226        self.ignore_invalid_input_notes
227    }
228
229    /// Returns the script argument for the transaction request.
230    pub fn script_arg(&self) -> &Option<Word> {
231        &self.script_arg
232    }
233
234    /// Returns the auth argument for the transaction request.
235    pub fn auth_arg(&self) -> &Option<Word> {
236        &self.auth_arg
237    }
238
239    /// Returns whether the auth arg carries fee conversion info set through
240    /// [`TransactionRequestBuilder::fee_conversion_info`].
241    pub fn declares_fee_conversion_info(&self) -> bool {
242        self.declares_fee_conversion_info
243    }
244
245    /// Returns the expected NTX scripts that the node's NTX builder will need in its registry.
246    pub fn expected_ntx_scripts(&self) -> &[NoteScript] {
247        &self.expected_ntx_scripts
248    }
249
250    /// Builds the [`InputNotes`] needed for the transaction execution.
251    ///
252    /// Authenticated input notes are provided by the caller (typically fetched from the store).
253    /// Any requested notes not present in that authenticated set are treated as unauthenticated.
254    /// The transaction input notes will include both authenticated and unauthenticated notes in
255    /// the order they were provided in the transaction request.
256    pub(crate) fn build_input_notes(
257        &self,
258        authenticated_note_records: Vec<InputNoteRecord>,
259    ) -> Result<InputNotes<InputNote>, TransactionRequestError> {
260        let mut input_notes: BTreeMap<NoteId, InputNote> = BTreeMap::new();
261
262        // Add provided authenticated input notes to the input notes map.
263        for authenticated_note_record in authenticated_note_records {
264            // Authenticated note records always carry metadata (their inclusion proof
265            // injected it), so `id()` is `Some`.
266            let authenticated_note_id = authenticated_note_record
267                .id()
268                .expect("authenticated note record carries metadata so id() is Some");
269
270            if !authenticated_note_record.is_authenticated() {
271                return Err(TransactionRequestError::InputNoteNotAuthenticated(
272                    authenticated_note_id,
273                ));
274            }
275
276            if authenticated_note_record.is_consumed() {
277                return Err(TransactionRequestError::InputNoteAlreadyConsumed(
278                    authenticated_note_id,
279                ));
280            }
281
282            input_notes.insert(
283                authenticated_note_id,
284                authenticated_note_record
285                    .try_into()
286                    .expect("Authenticated note record should be convertible to InputNote"),
287            );
288        }
289
290        // Add unauthenticated input notes to the input notes map.
291        let authenticated_note_ids: BTreeSet<NoteId> = input_notes.keys().copied().collect();
292        for note in self.input_notes().iter().filter(|n| !authenticated_note_ids.contains(&n.id()))
293        {
294            input_notes.insert(note.id(), InputNote::Unauthenticated { note: note.clone() });
295        }
296
297        Ok(InputNotes::new(
298            self.input_note_ids()
299                .map(|note_id| {
300                    input_notes
301                        .remove(&note_id)
302                        .expect("The input note map was checked to contain all input notes")
303                })
304                .collect(),
305        )?)
306    }
307
308    /// Converts the [`TransactionRequest`] into [`TransactionArgs`] in order to be executed by a
309    /// Miden host.
310    pub(crate) fn into_transaction_args(
311        self,
312        tx_script: Option<(TransactionScript, Option<Word>)>,
313    ) -> TransactionArgs {
314        let note_args = self.get_note_args();
315        let TransactionRequest {
316            expected_output_recipients,
317            advice_map,
318            merkle_store,
319            ..
320        } = self;
321
322        let mut tx_args = TransactionArgs::new(advice_map).with_note_args(note_args);
323
324        // A script argument without a script has nothing to bind to, so it is only applied when a
325        // transaction script is present. With no argument the default empty word is used, which is
326        // equivalent to setting no argument at all.
327        if let Some((tx_script, script_args)) = tx_script {
328            let script_args = script_args.or(self.script_arg).unwrap_or_default();
329            tx_args = tx_args.with_tx_script_and_args(tx_script, script_args);
330        }
331
332        if let Some(auth_argument) = self.auth_arg {
333            tx_args = tx_args.with_auth_args(auth_argument);
334        }
335
336        tx_args
337            .extend_output_note_recipients(expected_output_recipients.into_values().map(Box::new));
338        tx_args.extend_merkle_store(merkle_store.inner_nodes());
339
340        tx_args
341    }
342
343    /// Builds the transaction script based on the account capabilities and the transaction request.
344    ///
345    /// Returns the script together with the `TX_SCRIPT_ARGS` word it must be executed with, if the
346    /// script determines it. The `SendNotes` script reads the notes it creates from the advice
347    /// provider and only receives their payload commitment on the stack, so its argument
348    /// is fixed by the notes the script was built for, and passing anything else
349    /// makes the script fail to resolve its payload. A caller-supplied
350    /// [`TransactionScriptTemplate::CustomScript`] carries no such constraint and yields `None`, so
351    /// the request's own [`TransactionRequestBuilder::script_arg`] applies to it.
352    ///
353    /// Scripts supplied by the caller via [`TransactionScriptTemplate::CustomScript`] are expected
354    /// to have already been compiled against the client's source manager (e.g. via
355    /// [`Client::code_builder`](crate::Client::code_builder)).
356    pub(crate) fn build_transaction_script(
357        &self,
358        code_interface: &AccountCodeInterface,
359    ) -> Result<Option<(TransactionScript, Option<Word>)>, TransactionRequestError> {
360        match &self.script_template {
361            Some(TransactionScriptTemplate::CustomScript(script)) => {
362                Ok(Some((script.clone(), None)))
363            },
364            Some(TransactionScriptTemplate::SendNotes(notes)) => {
365                let script = match self.expiration_delta.and_then(NonZeroU16::new) {
366                    Some(delta) => SendNotesTransactionScript::with_expiration_delta(
367                        code_interface,
368                        notes,
369                        delta,
370                    )?,
371                    None => SendNotesTransactionScript::new(code_interface, notes)?,
372                };
373                Ok(Some((script.tx_script().clone(), Some(script.tx_script_args()))))
374            },
375            None => Ok(None),
376        }
377    }
378}
379
380// SERIALIZATION
381// ================================================================================================
382
383impl Serializable for TransactionRequest {
384    fn write_into<W: ByteWriter>(&self, target: &mut W) {
385        self.input_notes.write_into(target);
386        self.input_notes_args.write_into(target);
387        match &self.script_template {
388            None => target.write_u8(0),
389            Some(TransactionScriptTemplate::CustomScript(script)) => {
390                target.write_u8(1);
391                script.write_into(target);
392            },
393            Some(TransactionScriptTemplate::SendNotes(notes)) => {
394                target.write_u8(2);
395                notes.write_into(target);
396            },
397        }
398        self.expected_output_recipients.write_into(target);
399        self.expected_future_notes.write_into(target);
400        self.advice_map.write_into(target);
401        self.merkle_store.write_into(target);
402        let foreign_accounts: Vec<_> = self.foreign_accounts.values().cloned().collect();
403        foreign_accounts.write_into(target);
404        self.expiration_delta.write_into(target);
405        target.write_u8(u8::from(self.ignore_invalid_input_notes));
406        self.script_arg.write_into(target);
407        self.auth_arg.write_into(target);
408        target.write_u8(u8::from(self.declares_fee_conversion_info));
409        self.expected_ntx_scripts.write_into(target);
410    }
411}
412
413impl Deserializable for TransactionRequest {
414    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
415        let input_notes = Vec::<Note>::read_from(source)?;
416        let input_notes_args = Vec::<(NoteId, Option<NoteArgs>)>::read_from(source)?;
417
418        let script_template = match source.read_u8()? {
419            0 => None,
420            1 => {
421                let transaction_script = TransactionScript::read_from(source)?;
422                Some(TransactionScriptTemplate::CustomScript(transaction_script))
423            },
424            2 => {
425                let notes = Vec::<PartialNote>::read_from(source)?;
426                Some(TransactionScriptTemplate::SendNotes(notes))
427            },
428            _ => {
429                return Err(DeserializationError::InvalidValue(
430                    "Invalid script template type".to_string(),
431                ));
432            },
433        };
434
435        let expected_output_recipients = BTreeMap::<Word, NoteRecipient>::read_from(source)?;
436        let expected_future_notes =
437            BTreeMap::<NoteDetailsCommitment, (NoteDetails, NoteTag)>::read_from(source)?;
438
439        let advice_map = AdviceMap::read_from(source)?;
440        let merkle_store = MerkleStore::read_from(source)?;
441        let mut foreign_accounts = BTreeMap::new();
442        for foreign_account in Vec::<ForeignAccount>::read_from(source)? {
443            foreign_accounts.entry(foreign_account.account_id()).or_insert(foreign_account);
444        }
445        let expiration_delta = Option::<u16>::read_from(source)?;
446        let ignore_invalid_input_notes = source.read_u8()? == 1;
447        let script_arg = Option::<Word>::read_from(source)?;
448        let auth_arg = Option::<Word>::read_from(source)?;
449        let declares_fee_conversion_info = source.read_u8()? == 1;
450        let expected_ntx_scripts = Vec::<NoteScript>::read_from(source)?;
451
452        Ok(TransactionRequest {
453            input_notes,
454            input_notes_args,
455            script_template,
456            expected_output_recipients,
457            expected_future_notes,
458            advice_map,
459            merkle_store,
460            foreign_accounts,
461            expiration_delta,
462            ignore_invalid_input_notes,
463            script_arg,
464            auth_arg,
465            declares_fee_conversion_info,
466            expected_ntx_scripts,
467        })
468    }
469}
470
471// HELPERS
472// ================================================================================================
473
474/// Accumulates fungible totals and collectable non-fungible assets from an iterator of assets.
475pub(crate) fn collect_assets<'a>(
476    assets: impl Iterator<Item = &'a Asset>,
477) -> (BTreeMap<AccountId, u64>, Vec<NonFungibleAsset>) {
478    let mut fungible_balance_map = BTreeMap::new();
479    let mut non_fungible_set = Vec::new();
480
481    assets.for_each(|asset| match asset {
482        Asset::Fungible(fungible) => {
483            let amount = fungible.amount().as_u64();
484            fungible_balance_map
485                .entry(fungible.faucet_id())
486                .and_modify(|balance| *balance += amount)
487                .or_insert(amount);
488        },
489        Asset::NonFungible(non_fungible) => {
490            if !non_fungible_set.contains(non_fungible) {
491                non_fungible_set.push(*non_fungible);
492            }
493        },
494    });
495
496    (fungible_balance_map, non_fungible_set)
497}
498
499impl Default for TransactionRequestBuilder {
500    fn default() -> Self {
501        Self::new()
502    }
503}
504
505// TRANSACTION REQUEST ERROR
506// ================================================================================================
507
508// Errors related to a [TransactionRequest]
509#[derive(Debug, Error)]
510pub enum TransactionRequestError {
511    #[error("failed to build the send-notes transaction script")]
512    SendNotesTransactionScriptError(#[from] SendNotesTransactionScriptError),
513    #[error("account error")]
514    AccountError(#[from] AccountError),
515    #[error("asset error")]
516    AssetError(#[from] AssetError),
517    #[error("duplicate input note: note {0} was added more than once to the transaction")]
518    DuplicateInputNote(NoteId),
519    #[error("transaction expiration delta must be greater than zero")]
520    ZeroExpirationDelta,
521    #[error(
522        "the account proof does not contain the required foreign account data; re-fetch the proof and retry"
523    )]
524    ForeignAccountDataMissing,
525    #[error(
526        "foreign account {0} has incompatible visibility; use `ForeignAccount::public()` for public accounts and `ForeignAccount::private()` for private accounts"
527    )]
528    InvalidForeignAccountId(AccountId),
529    #[error(
530        "note {0} cannot be used as an authenticated input: it does not have a valid inclusion proof"
531    )]
532    InputNoteNotAuthenticated(NoteId),
533    #[error("note {0} has already been consumed")]
534    InputNoteAlreadyConsumed(NoteId),
535    #[error(
536        "output note declares sender {actual} but the transaction is executed by account {expected}"
537    )]
538    OutputNoteSenderMismatch { expected: AccountId, actual: AccountId },
539    #[error(
540        "the request declares fee conversion info but the account's auth component {0} does not read it"
541    )]
542    FeeConversionInfoUnsupported(String),
543    #[error("invalid transaction script")]
544    InvalidTransactionScript(#[from] TransactionScriptError),
545    #[error("merkle proof error")]
546    MerkleError(#[from] MerkleError),
547    #[error("empty transaction: the request has no input notes and no account state changes")]
548    NoInputNotesNorAccountChange,
549    #[error("note not found: {0}")]
550    NoteNotFound(String),
551    #[error("failed to create note")]
552    NoteCreationError(#[from] NoteError),
553    #[error("note failed validation")]
554    NoteValidationError(#[source] NoteError),
555    #[error("note execution failed")]
556    NoteExecutionError(#[source] NoteError),
557    #[error("failed to build note args")]
558    NoteArgError(#[source] NoteError),
559    #[error("pay-to-ID note must contain at least one asset to transfer")]
560    P2IDNoteWithoutAsset,
561    #[error(
562        "non-fungible asset issued by faucet {0} is not available in the account vault or incoming notes"
563    )]
564    MissingNonFungibleAsset(AccountId),
565    #[error("PSWAP note can only be cancelled by its creator: expected {expected}, got {actual}")]
566    PswapCancelCreatorMismatch { expected: AccountId, actual: AccountId },
567    #[error("error building script")]
568    CodeBuilderError(#[from] CodeBuilderError),
569    #[error("transaction script template error: {0}")]
570    ScriptTemplateError(String),
571    #[error("storage slot {0} not found in account ID {1}")]
572    StorageSlotNotFound(u8, AccountId),
573    #[error("error while building the input notes")]
574    TransactionInputError(#[from] TransactionInputError),
575    #[error("account storage map error")]
576    StorageMapError(#[from] StorageMapError),
577    #[error("asset vault error")]
578    AssetVaultError(#[from] AssetVaultError),
579    #[error(
580        "unsupported authentication scheme ID {0}; supported schemes are: RpoFalcon512 (0) and EcdsaK256Keccak (1)"
581    )]
582    UnsupportedAuthSchemeId(u8),
583}
584
585// TESTS
586// ================================================================================================
587
588#[cfg(test)]
589mod tests {
590    use std::vec::Vec;
591
592    use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
593    use miden_protocol::account::{
594        AccountBuilder,
595        AccountComponent,
596        AccountId,
597        AccountType,
598        StorageMapKey,
599        StorageSlotName,
600    };
601    use miden_protocol::asset::FungibleAsset;
602    use miden_protocol::crypto::rand::{FeltRng, RandomCoin};
603    use miden_protocol::note::{NoteTag, NoteType};
604    use miden_protocol::testing::account_id::{
605        ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
606        ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
607        ACCOUNT_ID_SENDER,
608    };
609    use miden_protocol::{EMPTY_WORD, Felt, Word};
610    use miden_standards::account::auth::{Approver, AuthSingleSig};
611    use miden_standards::note::P2idNote;
612    use miden_standards::testing::account_component::MockAccountComponent;
613    use miden_tx::utils::serde::{Deserializable, Serializable};
614
615    use super::{TransactionRequest, TransactionRequestBuilder};
616    use crate::rpc::domain::account::AccountStorageRequirements;
617    use crate::transaction::ForeignAccount;
618
619    #[test]
620    fn transaction_request_serialization() {
621        assert_transaction_request_serialization_with(|| {
622            AuthSingleSig::new(Approver::new(
623                PublicKeyCommitment::from(EMPTY_WORD),
624                AuthScheme::Falcon512Poseidon2,
625            ))
626            .into()
627        });
628    }
629
630    #[test]
631    fn transaction_request_serialization_ecdsa() {
632        assert_transaction_request_serialization_with(|| {
633            AuthSingleSig::new(Approver::new(
634                PublicKeyCommitment::from(EMPTY_WORD),
635                AuthScheme::EcdsaK256Keccak,
636            ))
637            .into()
638        });
639    }
640
641    fn assert_transaction_request_serialization_with<F>(auth_component: F)
642    where
643        F: FnOnce() -> AccountComponent,
644    {
645        let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap();
646        let target_id =
647            AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
648        let faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
649        let mut rng = RandomCoin::new(Word::default());
650
651        let mut notes = vec![];
652        for i in 0..6 {
653            let note = P2idNote::builder()
654                .sender(sender_id)
655                .target(target_id)
656                .assets(vec![FungibleAsset::new(faucet_id, 100 + i).unwrap()])
657                .note_type(NoteType::Private)
658                .generate_serial_number(&mut rng)
659                .build()
660                .expect("note creation failed");
661            notes.push(note.into());
662        }
663
664        let mut advice_vec: Vec<(Word, Vec<Felt>)> = vec![];
665        for i in 0u32..10 {
666            advice_vec.push((rng.draw_word(), vec![Felt::from(i)]));
667        }
668
669        let account = AccountBuilder::new(Default::default())
670            .with_component(MockAccountComponent::with_empty_slots())
671            .with_component(auth_component())
672            .account_type(AccountType::Private)
673            .build_existing()
674            .unwrap();
675
676        // This transaction request wouldn't be valid in a real scenario, it's intended for testing
677        let tx_request = TransactionRequestBuilder::new()
678            .input_notes(vec![(notes.pop().unwrap(), None)])
679            .expected_output_recipients(vec![notes.pop().unwrap().recipient().clone()])
680            .expected_future_notes(vec![(
681                notes.pop().unwrap().into(),
682                NoteTag::with_account_target(sender_id),
683            )])
684            .extend_advice_map(advice_vec)
685            .foreign_accounts([
686                ForeignAccount::public(
687                    target_id,
688                    AccountStorageRequirements::new([(
689                        StorageSlotName::new("demo::storage_slot").unwrap(),
690                        &[StorageMapKey::new(Word::default())],
691                    )]),
692                )
693                .unwrap(),
694                ForeignAccount::private(&account).unwrap(),
695            ])
696            .own_output_notes(vec![notes.pop().unwrap(), notes.pop().unwrap()])
697            .script_arg(rng.draw_word())
698            .auth_arg(rng.draw_word())
699            .expected_ntx_scripts(vec![notes.first().unwrap().recipient().script().clone()])
700            .build()
701            .unwrap();
702
703        let mut buffer = Vec::new();
704        tx_request.write_into(&mut buffer);
705
706        let deserialized_tx_request = TransactionRequest::read_from_bytes(&buffer).unwrap();
707        assert_eq!(tx_request, deserialized_tx_request);
708    }
709}