Skip to main content

miden_client/transaction/request/
builder.rs

1//! Contains structures and functions related to transaction creation.
2use alloc::collections::{BTreeMap, BTreeSet};
3use alloc::string::ToString;
4use alloc::vec::Vec;
5
6use miden_protocol::account::AccountId;
7use miden_protocol::asset::{Asset, FungibleAsset};
8use miden_protocol::block::BlockNumber;
9use miden_protocol::crypto::merkle::InnerNodeInfo;
10use miden_protocol::crypto::merkle::store::MerkleStore;
11use miden_protocol::crypto::rand::FeltRng;
12use miden_protocol::errors::NoteError;
13use miden_protocol::note::{
14    Note,
15    NoteAssets,
16    NoteAttachment,
17    NoteAttachments,
18    NoteDetails,
19    NoteDetailsCommitment,
20    NoteId,
21    NoteRecipient,
22    NoteScript,
23    NoteStorage,
24    NoteTag,
25    NoteType,
26    PartialNote,
27    PartialNoteMetadata,
28};
29use miden_protocol::transaction::TransactionScript;
30use miden_protocol::vm::AdviceMap;
31use miden_protocol::{Felt, Word};
32use miden_standards::note::{
33    P2idNote,
34    P2ideNote,
35    P2ideNoteStorage,
36    PswapNote,
37    PswapNoteStorage,
38    SwapNote,
39};
40
41use super::{
42    ForeignAccount,
43    NoteArgs,
44    TransactionRequest,
45    TransactionRequestError,
46    TransactionScriptTemplate,
47};
48use crate::ClientRng;
49
50// TRANSACTION REQUEST BUILDER
51// ================================================================================================
52
53/// A builder for a [`TransactionRequest`].
54///
55/// Use this builder to construct a [`TransactionRequest`] by adding input notes, specifying
56/// scripts, and setting other transaction parameters.
57#[derive(Clone, Debug)]
58pub struct TransactionRequestBuilder {
59    /// Notes to be consumed by the transaction.
60    /// Notes whose inclusion proof is present in the store are will be consumed as authenticated;
61    /// the ones that do not have proofs will be consumed as unauthenticated.
62    input_notes: Vec<Note>,
63    /// Optional arguments of the Notes to be consumed by the transaction. This
64    /// includes both authenticated and unauthenticated notes.
65    input_notes_args: Vec<(NoteId, Option<NoteArgs>)>,
66    /// Notes to be created by the transaction. The full note data is needed internally
67    /// to build the transaction script template.
68    own_output_notes: Vec<Note>,
69    /// A map of recipients of the output notes expected to be generated by the transaction.
70    expected_output_recipients: BTreeMap<Word, NoteRecipient>,
71    /// A map of details and tags of notes we expect to be created as part of future transactions
72    /// with their respective tags.
73    ///
74    /// For example, after a swap note is consumed, a payback note is expected to be created.
75    expected_future_notes: BTreeMap<NoteDetailsCommitment, (NoteDetails, NoteTag)>,
76    /// Custom transaction script to be used.
77    custom_script: Option<TransactionScript>,
78    /// Initial state of the `AdviceMap` that provides data during runtime.
79    advice_map: AdviceMap,
80    /// Initial state of the `MerkleStore` that provides data during runtime.
81    merkle_store: MerkleStore,
82    /// Foreign account data requirements. At execution time, account data will be retrieved from
83    /// the network, and injected as advice inputs. Additionally, the account's code will be
84    /// added to the executor and prover.
85    foreign_accounts: BTreeMap<AccountId, ForeignAccount>,
86    /// The number of blocks in relation to the transaction's reference block after which the
87    /// transaction will expire. If `None`, the transaction will not expire.
88    expiration_delta: Option<u16>,
89    /// Indicates whether to **silently** ignore invalid input notes when executing the
90    /// transaction. This will allow the transaction to be executed even if some input notes
91    /// are invalid.
92    ignore_invalid_input_notes: bool,
93    /// Optional [`Word`] that will be pushed to the operand stack before the transaction script
94    /// execution. If the advice map is extended with some user defined entries, this script
95    /// argument could be used as a key to access the corresponding value.
96    script_arg: Option<Word>,
97    /// Optional [`Word`] that will be pushed to the stack for the authentication procedure
98    /// during transaction execution.
99    auth_arg: Option<Word>,
100    /// Note scripts that the node's NTX builder will need in its script registry.
101    ///
102    /// See [`TransactionRequestBuilder::expected_ntx_scripts`] for details.
103    expected_ntx_scripts: Vec<NoteScript>,
104}
105
106impl TransactionRequestBuilder {
107    // CONSTRUCTORS
108    // --------------------------------------------------------------------------------------------
109
110    /// Creates a new, empty [`TransactionRequestBuilder`].
111    pub fn new() -> Self {
112        Self {
113            input_notes: vec![],
114            input_notes_args: vec![],
115            own_output_notes: Vec::new(),
116            expected_output_recipients: BTreeMap::new(),
117            expected_future_notes: BTreeMap::new(),
118            custom_script: None,
119            advice_map: AdviceMap::default(),
120            merkle_store: MerkleStore::default(),
121            expiration_delta: None,
122            foreign_accounts: BTreeMap::default(),
123            ignore_invalid_input_notes: false,
124            script_arg: None,
125            auth_arg: None,
126            expected_ntx_scripts: vec![],
127        }
128    }
129
130    /// Adds the specified notes as input notes to the transaction request.
131    #[must_use]
132    pub fn input_notes(
133        mut self,
134        notes: impl IntoIterator<Item = (Note, Option<NoteArgs>)>,
135    ) -> Self {
136        for (note, argument) in notes {
137            self.input_notes_args.push((note.id(), argument));
138            self.input_notes.push(note);
139        }
140        self
141    }
142
143    /// Specifies the output notes that should be created in the transaction script and will
144    /// be used as a transaction script template. These notes will also be added to the expected
145    /// output recipients of the transaction.
146    ///
147    /// If a transaction script template is already set (e.g. by calling `with_custom_script`), the
148    /// [`TransactionRequestBuilder::build`] method will return an error.
149    #[must_use]
150    pub fn own_output_notes(mut self, notes: impl IntoIterator<Item = Note>) -> Self {
151        for note in notes {
152            self.expected_output_recipients
153                .insert(note.recipient().digest(), note.recipient().clone());
154            self.own_output_notes.push(note);
155        }
156
157        self
158    }
159
160    /// Specifies a custom transaction script to be used.
161    ///
162    /// If a script template is already set (e.g. by calling `with_own_output_notes`), the
163    /// [`TransactionRequestBuilder::build`] method will return an error.
164    #[must_use]
165    pub fn custom_script(mut self, script: TransactionScript) -> Self {
166        self.custom_script = Some(script);
167        self
168    }
169
170    /// Specifies one or more foreign accounts (public or private) that contain data
171    /// utilized by the transaction.
172    ///
173    /// At execution, the client queries the node and retrieves the appropriate data,
174    /// depending on whether each foreign account is public or private:
175    ///
176    /// - **Public accounts**: the node retrieves the state and code for the account and injects
177    ///   them as advice inputs. Public accounts can be omitted here, as they will be lazily loaded
178    ///   through RPC calls. Undeclared accounts may trigger additional RPC calls for storage map
179    ///   accesses during execution.
180    /// - **Private accounts**: the node retrieves a proof of the account's existence and injects
181    ///   that as advice inputs. Private accounts must always be declared here with their
182    ///   [`PartialAccount`](miden_protocol::account::PartialAccount) state.
183    #[must_use]
184    pub fn foreign_accounts(
185        mut self,
186        foreign_accounts: impl IntoIterator<Item = impl Into<ForeignAccount>>,
187    ) -> Self {
188        for account in foreign_accounts {
189            let foreign_account: ForeignAccount = account.into();
190            self.foreign_accounts.insert(foreign_account.account_id(), foreign_account);
191        }
192
193        self
194    }
195
196    /// Specifies a transaction's expected output note recipients.
197    ///
198    /// The set of specified recipients is treated as a subset of the recipients for notes that may
199    /// be created by a transaction. That is, the transaction must create notes for all the
200    /// specified expected recipients, but it may also create notes for other recipients not
201    /// included in this set.
202    #[must_use]
203    pub fn expected_output_recipients(mut self, recipients: Vec<NoteRecipient>) -> Self {
204        self.expected_output_recipients = recipients
205            .into_iter()
206            .map(|recipient| (recipient.digest(), recipient))
207            .collect::<BTreeMap<_, _>>();
208        self
209    }
210
211    /// Specifies a set of notes which may be created when a transaction's output notes are
212    /// consumed.
213    ///
214    /// For example, after a SWAP note is consumed, a payback note is expected to be created. This
215    /// allows the client to track this note accordingly.
216    #[must_use]
217    pub fn expected_future_notes(mut self, notes: Vec<(NoteDetails, NoteTag)>) -> Self {
218        self.expected_future_notes = notes
219            .into_iter()
220            .map(|note| (note.0.commitment(), note))
221            .collect::<BTreeMap<_, _>>();
222        self
223    }
224
225    /// Extends the advice map with the specified `([Word], Vec<[Felt]>)` pairs.
226    #[must_use]
227    pub fn extend_advice_map<I, V>(mut self, iter: I) -> Self
228    where
229        I: IntoIterator<Item = (Word, V)>,
230        V: AsRef<[Felt]>,
231    {
232        self.advice_map.extend(iter.into_iter().map(|(w, v)| (w, v.as_ref().to_vec())));
233        self
234    }
235
236    /// Extends the merkle store with the specified [`InnerNodeInfo`] elements.
237    #[must_use]
238    pub fn extend_merkle_store<T: IntoIterator<Item = InnerNodeInfo>>(mut self, iter: T) -> Self {
239        self.merkle_store.extend(iter);
240        self
241    }
242
243    /// The number of blocks in relation to the transaction's reference block after which the
244    /// transaction will expire. By default, the transaction will not expire.
245    ///
246    /// Setting transaction expiration delta defines an upper bound for transaction expiration,
247    /// but other code executed during the transaction may impose an even smaller transaction
248    /// expiration delta.
249    #[must_use]
250    pub fn expiration_delta(mut self, expiration_delta: u16) -> Self {
251        self.expiration_delta = Some(expiration_delta);
252        self
253    }
254
255    /// The resulting transaction will **silently** ignore invalid input notes when being executed.
256    /// By default, this will not happen.
257    #[must_use]
258    pub fn ignore_invalid_input_notes(mut self) -> Self {
259        self.ignore_invalid_input_notes = true;
260        self
261    }
262
263    /// Sets an optional [`Word`] that will be pushed to the operand stack before the transaction
264    /// script execution. If the advice map is extended with some user defined entries, this script
265    /// argument could be used as a key to access the corresponding value.
266    #[must_use]
267    pub fn script_arg(mut self, script_arg: Word) -> Self {
268        self.script_arg = Some(script_arg);
269        self
270    }
271
272    /// Sets an optional [`Word`] that will be pushed to the stack for the authentication
273    /// procedure during transaction execution.
274    #[must_use]
275    pub fn auth_arg(mut self, auth_arg: Word) -> Self {
276        self.auth_arg = Some(auth_arg);
277        self
278    }
279
280    /// Specifies note scripts that the node's network transaction (NTX) builder will need in
281    /// its script registry.
282    ///
283    /// When a transaction creates notes destined for a network account, the node's NTX builder
284    /// must have the scripts of any public output notes in its registry. If a required script
285    /// is missing, the NTX will silently fail on the node side.
286    ///
287    /// When this field is set, the client will check each script against the node before
288    /// executing the main transaction. For any script not yet registered, the client
289    /// automatically creates and submits a separate registration transaction (a public note
290    /// carrying that script) so the node's registry is populated before the NTX executes.
291    ///
292    /// Standard note scripts are ignored here — the NTX builder resolves them directly.
293    #[must_use]
294    pub fn expected_ntx_scripts(mut self, scripts: Vec<NoteScript>) -> Self {
295        self.expected_ntx_scripts = scripts;
296        self
297    }
298
299    // STANDARDIZED REQUESTS
300    // --------------------------------------------------------------------------------------------
301
302    /// Consumes the builder and returns a [`TransactionRequest`] for a transaction to consume the
303    /// specified notes.
304    ///
305    /// - `notes` is a list of notes to be consumed.
306    pub fn build_consume_notes(
307        self,
308        notes: Vec<Note>,
309    ) -> Result<TransactionRequest, TransactionRequestError> {
310        let input_notes = notes.into_iter().map(|id| (id, None));
311        self.input_notes(input_notes).build()
312    }
313
314    /// Consumes the builder and returns a [`TransactionRequest`] for a transaction to mint fungible
315    /// assets. This request must be executed against a fungible faucet account.
316    ///
317    /// - `asset` is the fungible asset to be minted.
318    /// - `target_id` is the account ID of the account to receive the minted asset.
319    /// - `note_type` determines the visibility of the note to be created.
320    /// - `rng` is the random number generator used to generate the serial number for the created
321    ///   note.
322    ///
323    /// This function cannot be used with a previously set custom script.
324    pub fn build_mint_fungible_asset(
325        self,
326        asset: FungibleAsset,
327        target_id: AccountId,
328        note_type: NoteType,
329        rng: &mut ClientRng,
330    ) -> Result<TransactionRequest, TransactionRequestError> {
331        let created_note = P2idNote::create(
332            asset.faucet_id(),
333            target_id,
334            vec![asset.into()],
335            note_type,
336            NoteAttachments::empty(),
337            rng,
338        )?;
339
340        self.own_output_notes(vec![created_note]).build()
341    }
342
343    /// Consumes the builder and returns a [`TransactionRequest`] for a transaction to send a P2ID
344    /// or P2IDE note. This request must be executed against the wallet sender account.
345    ///
346    /// - `payment_data` is the data for the payment transaction that contains the asset to be
347    ///   transferred, the sender account ID, and the target account ID. If the recall or timelock
348    ///   heights are set, a P2IDE note will be created; otherwise, a P2ID note will be created.
349    /// - `note_type` determines the visibility of the note to be created.
350    /// - `rng` is the random number generator used to generate the serial number for the created
351    ///   note.
352    ///
353    /// This function cannot be used with a previously set custom script.
354    pub fn build_pay_to_id(
355        self,
356        payment_data: PaymentNoteDescription,
357        note_type: NoteType,
358        rng: &mut ClientRng,
359    ) -> Result<TransactionRequest, TransactionRequestError> {
360        if payment_data
361            .assets()
362            .iter()
363            .all(|asset| asset.is_fungible() && asset.unwrap_fungible().amount().as_u64() == 0)
364        {
365            return Err(TransactionRequestError::P2IDNoteWithoutAsset);
366        }
367
368        let created_note = payment_data.into_note(note_type, rng)?;
369
370        self.own_output_notes(vec![created_note]).build()
371    }
372
373    /// Consumes the builder and returns a [`TransactionRequest`] for a transaction to send a SWAP
374    /// note. This request must be executed against the wallet sender account.
375    ///
376    /// - `swap_data` is the data for the swap transaction that contains the sender account ID, the
377    ///   offered asset, and the requested asset.
378    /// - `note_type` determines the visibility of the note to be created.
379    /// - `payback_note_type` determines the visibility of the payback note.
380    /// - `rng` is the random number generator used to generate the serial number for the created
381    ///   note.
382    ///
383    /// This function cannot be used with a previously set custom script.
384    pub fn build_swap(
385        self,
386        swap_data: &SwapTransactionData,
387        note_type: NoteType,
388        payback_note_type: NoteType,
389        rng: &mut ClientRng,
390    ) -> Result<TransactionRequest, TransactionRequestError> {
391        // The created note is the one that we need as the output of the tx, the other one is the
392        // one that we expect to receive and consume eventually.
393        let (created_note, payback_note_details) = SwapNote::create(
394            swap_data.account_id(),
395            swap_data.offered_asset(),
396            swap_data.requested_asset(),
397            note_type,
398            NoteAttachments::empty(),
399            payback_note_type,
400            rng,
401        )?;
402
403        let payback_tag = NoteTag::with_account_target(swap_data.account_id());
404
405        self.expected_future_notes(vec![(payback_note_details, payback_tag)])
406            .own_output_notes(vec![created_note])
407            .build()
408    }
409
410    /// Consumes the builder and returns a [`TransactionRequest`] for a transaction that registers
411    /// note scripts in the node's script registry.
412    ///
413    /// This creates one public output note per script, each with empty assets and storage. The
414    /// node indexes the script of every public note it processes, so submitting this transaction
415    /// makes the scripts available for future network transactions (NTX).
416    ///
417    /// - `sender_account_id` is the account executing the transaction.
418    /// - `scripts` is the list of note scripts to register.
419    /// - `rng` is used to generate serial numbers for the registration notes.
420    ///
421    /// This function cannot be used with a previously set custom script.
422    pub fn build_register_note_scripts(
423        self,
424        sender_account_id: AccountId,
425        scripts: Vec<NoteScript>,
426        rng: &mut ClientRng,
427    ) -> Result<TransactionRequest, TransactionRequestError> {
428        let registration_notes: Vec<Note> = scripts
429            .into_iter()
430            .map(|script| {
431                let serial_num = rng.draw_word();
432                let note_storage = NoteStorage::new(vec![])?;
433                let recipient = NoteRecipient::new(serial_num, script, note_storage);
434                let note_assets = NoteAssets::new(vec![])?;
435                let metadata = PartialNoteMetadata::new(sender_account_id, NoteType::Public);
436                Ok(Note::new(note_assets, metadata, recipient))
437            })
438            .collect::<Result<_, NoteError>>()?;
439
440        self.own_output_notes(registration_notes).build()
441    }
442
443    /// Consumes the builder and returns a [`TransactionRequest`] for a transaction that creates a
444    /// partial swap (PSWAP) note. This request must be executed against the creator account.
445    ///
446    /// - `pswap_data` is the data for the partial swap that contains the creator account ID, the
447    ///   offered fungible asset, and the requested fungible asset.
448    /// - `note_type` determines the visibility of the PSWAP note itself.
449    /// - `payback_note_type` determines the visibility of the payback note that fillers emit back
450    ///   to the creator. Typically [`NoteType::Private`] (cheaper; the fill amount is already
451    ///   visible in the executing transaction).
452    /// - `note_attachment` is the optional attachment for the PSWAP note. Pass `None` when there is
453    ///   nothing to attach.
454    /// - `rng` is the random number generator used to generate the serial number for the created
455    ///   note.
456    ///
457    /// This function cannot be used with a previously set custom script.
458    pub fn build_pswap_create(
459        self,
460        pswap_data: &PswapTransactionData,
461        note_type: NoteType,
462        payback_note_type: NoteType,
463        note_attachment: Option<NoteAttachment>,
464        rng: &mut ClientRng,
465    ) -> Result<TransactionRequest, TransactionRequestError> {
466        let storage = PswapNoteStorage::builder()
467            .requested_asset(pswap_data.requested_asset())
468            .creator_account_id(pswap_data.creator_account_id())
469            .payback_note_type(payback_note_type)
470            .build();
471
472        let pswap_note = PswapNote::builder()
473            .sender(pswap_data.creator_account_id())
474            .storage(storage)
475            .serial_number(rng.draw_word())
476            .note_type(note_type)
477            .offered_asset(pswap_data.offered_asset())
478            .maybe_attachment(note_attachment)
479            .build()
480            .map_err(TransactionRequestError::NoteCreationError)?;
481
482        let note: Note = pswap_note.into();
483        self.own_output_notes(vec![note]).build()
484    }
485
486    /// Consumes the builder and returns a [`TransactionRequest`] for a transaction that consumes
487    /// (fills) a partial swap (PSWAP) note. This request must be executed against the consumer
488    /// account.
489    ///
490    /// - `pswap_note` is the PSWAP note being consumed.
491    /// - `consumer_account_id` is the account consuming the swap.
492    /// - `account_fill_amount` is the amount of the requested asset being provided by the consumer
493    ///   account.
494    /// - `note_fill_amount` is any additional amount being provided by other (in-flight) notes.
495    ///
496    /// This function cannot be used with a previously set custom script.
497    pub fn build_pswap_consume(
498        self,
499        pswap_note: &Note,
500        consumer_account_id: AccountId,
501        account_fill_amount: u64,
502        note_fill_amount: u64,
503    ) -> Result<TransactionRequest, TransactionRequestError> {
504        let pswap = PswapNote::try_from(pswap_note)
505            .map_err(TransactionRequestError::NoteValidationError)?;
506
507        let requested_faucet_id = pswap.storage().requested_asset().faucet_id();
508
509        let account_fill_asset = FungibleAsset::new(requested_faucet_id, account_fill_amount)?;
510        let note_fill_asset = FungibleAsset::new(requested_faucet_id, note_fill_amount)?;
511
512        let (payback_note, remainder_pswap) = pswap
513            .execute(consumer_account_id, Some(account_fill_asset), Some(note_fill_asset))
514            .map_err(TransactionRequestError::NoteExecutionError)?;
515
516        let note_args = PswapNote::create_args(account_fill_amount, note_fill_amount)
517            .map_err(TransactionRequestError::NoteArgError)?;
518
519        // Payback and remainder both settle to the creator, not the consumer. Declare them as
520        // expected recipients so the transaction is validated against them, but don't register
521        // them as expected future notes — that's the creator's concern, and doing so here would
522        // leave stale, un-consumable notes in the consumer's store.
523        let mut expected_recipients = vec![payback_note.recipient().clone()];
524
525        if let Some(remainder) = remainder_pswap {
526            let remainder_note: Note = remainder.into();
527            expected_recipients.push(remainder_note.recipient().clone());
528        }
529
530        self.input_notes(vec![(pswap_note.clone(), Some(note_args))])
531            .expected_output_recipients(expected_recipients)
532            .build()
533    }
534
535    /// Consumes the builder and returns a [`TransactionRequest`] for a transaction that cancels a
536    /// partial swap (PSWAP) note. This request must be executed against the creator account.
537    ///
538    /// - `pswap_note` is the PSWAP note to cancel.
539    /// - `creator_account_id` is the account that created the note. The note's stored creator must
540    ///   match this ID; this is the account the resulting transaction must be executed against.
541    ///
542    /// This function cannot be used with a previously set custom script.
543    pub fn build_pswap_cancel(
544        self,
545        pswap_note: Note,
546        creator_account_id: AccountId,
547    ) -> Result<TransactionRequest, TransactionRequestError> {
548        let pswap = PswapNote::try_from(&pswap_note)
549            .map_err(TransactionRequestError::NoteValidationError)?;
550
551        let note_creator = pswap.storage().creator_account_id();
552        if note_creator != creator_account_id {
553            return Err(TransactionRequestError::PswapCancelCreatorMismatch {
554                expected: note_creator,
555                actual: creator_account_id,
556            });
557        }
558
559        self.input_notes(vec![(pswap_note, None)]).build()
560    }
561
562    // FINALIZE BUILDER
563    // --------------------------------------------------------------------------------------------
564
565    /// Consumes the builder and returns a [`TransactionRequest`].
566    ///
567    /// # Errors
568    /// - If both a custom script and own output notes are set.
569    /// - If an expiration delta is set when a custom script is set.
570    /// - If an invalid note variant is encountered in the own output notes.
571    pub fn build(self) -> Result<TransactionRequest, TransactionRequestError> {
572        let mut seen_input_notes = BTreeSet::new();
573        for (note_id, _) in &self.input_notes_args {
574            if !seen_input_notes.insert(note_id) {
575                return Err(TransactionRequestError::DuplicateInputNote(*note_id));
576            }
577        }
578
579        let script_template = match (self.custom_script, self.own_output_notes.is_empty()) {
580            (Some(_), false) => {
581                return Err(TransactionRequestError::ScriptTemplateError(
582                    "Cannot set both a custom script and own output notes".to_string(),
583                ));
584            },
585            (Some(script), true) => {
586                if self.expiration_delta.is_some() {
587                    return Err(TransactionRequestError::ScriptTemplateError(
588                        "Cannot set expiration delta when a custom script is set".to_string(),
589                    ));
590                }
591
592                Some(TransactionScriptTemplate::CustomScript(script))
593            },
594            (None, false) => {
595                let partial_notes: Vec<PartialNote> =
596                    self.own_output_notes.into_iter().map(Into::into).collect();
597
598                Some(TransactionScriptTemplate::SendNotes(partial_notes))
599            },
600            (None, true) => None,
601        };
602
603        Ok(TransactionRequest {
604            input_notes: self.input_notes,
605            input_notes_args: self.input_notes_args,
606            script_template,
607            expected_output_recipients: self.expected_output_recipients,
608            expected_future_notes: self.expected_future_notes,
609            advice_map: self.advice_map,
610            merkle_store: self.merkle_store,
611            foreign_accounts: self.foreign_accounts,
612            expiration_delta: self.expiration_delta,
613            ignore_invalid_input_notes: self.ignore_invalid_input_notes,
614            script_arg: self.script_arg,
615            auth_arg: self.auth_arg,
616            expected_ntx_scripts: self.expected_ntx_scripts,
617        })
618    }
619}
620
621// PAYMENT NOTE DESCRIPTION
622// ================================================================================================
623
624/// Contains information needed to create a payment note.
625#[derive(Clone, Debug)]
626pub struct PaymentNoteDescription {
627    /// Assets that are meant to be sent to the target account.
628    assets: Vec<Asset>,
629    /// Account ID of the sender account.
630    sender_account_id: AccountId,
631    /// Account ID of the receiver account.
632    target_account_id: AccountId,
633    /// Optional reclaim height for the P2IDE note. It allows the possibility for the sender to
634    /// reclaim the assets if the note has not been consumed by the target before this height.
635    reclaim_height: Option<BlockNumber>,
636    /// Optional timelock height for the P2IDE note. It allows the possibility to add a timelock to
637    /// the asset transfer, meaning that the note can only be consumed after this height.
638    timelock_height: Option<BlockNumber>,
639}
640
641impl PaymentNoteDescription {
642    // CONSTRUCTORS
643    // --------------------------------------------------------------------------------------------
644
645    /// Creates a new [`PaymentNoteDescription`].
646    pub fn new(
647        assets: Vec<Asset>,
648        sender_account_id: AccountId,
649        target_account_id: AccountId,
650    ) -> PaymentNoteDescription {
651        PaymentNoteDescription {
652            assets,
653            sender_account_id,
654            target_account_id,
655            reclaim_height: None,
656            timelock_height: None,
657        }
658    }
659
660    /// Modifies the [`PaymentNoteDescription`] to set a reclaim height for payment note.
661    #[must_use]
662    pub fn with_reclaim_height(mut self, reclaim_height: BlockNumber) -> PaymentNoteDescription {
663        self.reclaim_height = Some(reclaim_height);
664        self
665    }
666
667    /// Modifies the [`PaymentNoteDescription`] to set a timelock height for payment note.
668    #[must_use]
669    pub fn with_timelock_height(mut self, timelock_height: BlockNumber) -> PaymentNoteDescription {
670        self.timelock_height = Some(timelock_height);
671        self
672    }
673
674    /// Returns the executor [`AccountId`].
675    pub fn account_id(&self) -> AccountId {
676        self.sender_account_id
677    }
678
679    /// Returns the target [`AccountId`].
680    pub fn target_account_id(&self) -> AccountId {
681        self.target_account_id
682    }
683
684    /// Returns the transaction's list of [`Asset`].
685    pub fn assets(&self) -> &Vec<Asset> {
686        &self.assets
687    }
688
689    /// Returns the reclaim height for the P2IDE note, if set.
690    pub fn reclaim_height(&self) -> Option<BlockNumber> {
691        self.reclaim_height
692    }
693
694    /// Returns the timelock height for the P2IDE note, if set.
695    pub fn timelock_height(&self) -> Option<BlockNumber> {
696        self.timelock_height
697    }
698
699    // CONVERSION
700    // --------------------------------------------------------------------------------------------
701
702    /// Converts the payment transaction data into a [`Note`] based on the specified fields. If the
703    /// reclaim and timelock heights are not set, a P2ID note is created; otherwise, a P2IDE note is
704    /// created.
705    pub(crate) fn into_note(
706        self,
707        note_type: NoteType,
708        rng: &mut ClientRng,
709    ) -> Result<Note, NoteError> {
710        if self.reclaim_height.is_none() && self.timelock_height.is_none() {
711            // Create a P2ID note
712            P2idNote::create(
713                self.sender_account_id,
714                self.target_account_id,
715                self.assets,
716                note_type,
717                NoteAttachments::empty(),
718                rng,
719            )
720        } else {
721            // Create a P2IDE note
722            P2ideNote::create(
723                self.sender_account_id,
724                P2ideNoteStorage::new(
725                    self.target_account_id,
726                    self.reclaim_height,
727                    self.timelock_height,
728                ),
729                self.assets,
730                note_type,
731                NoteAttachments::empty(),
732                rng,
733            )
734        }
735    }
736}
737
738// SWAP TRANSACTION DATA
739// ================================================================================================
740
741/// Contains information related to a swap transaction.
742///
743/// A swap transaction involves creating a SWAP note, which will carry the offered asset and which,
744/// when consumed, will create a payback note that carries the requested asset taken from the
745/// consumer account's vault.
746#[derive(Clone, Debug)]
747pub struct SwapTransactionData {
748    /// Account ID of the sender account.
749    sender_account_id: AccountId,
750    /// Asset that is offered in the swap.
751    offered_asset: Asset,
752    /// Asset that is expected in the payback note generated as a result of the swap.
753    requested_asset: Asset,
754}
755
756impl SwapTransactionData {
757    // CONSTRUCTORS
758    // --------------------------------------------------------------------------------------------
759
760    /// Creates a new [`SwapTransactionData`].
761    pub fn new(
762        sender_account_id: AccountId,
763        offered_asset: Asset,
764        requested_asset: Asset,
765    ) -> SwapTransactionData {
766        SwapTransactionData {
767            sender_account_id,
768            offered_asset,
769            requested_asset,
770        }
771    }
772
773    /// Returns the executor [`AccountId`].
774    pub fn account_id(&self) -> AccountId {
775        self.sender_account_id
776    }
777
778    /// Returns the transaction offered [`Asset`].
779    pub fn offered_asset(&self) -> Asset {
780        self.offered_asset
781    }
782
783    /// Returns the transaction requested [`Asset`].
784    pub fn requested_asset(&self) -> Asset {
785        self.requested_asset
786    }
787}
788
789// PSWAP TRANSACTION DATA
790// ================================================================================================
791
792/// Contains information related to a partial swap (PSWAP) transaction.
793///
794/// A PSWAP transaction involves creating a PSWAP note that carries the offered fungible asset
795/// and, when consumed (filled), produces a payback note carrying the requested fungible asset
796/// taken from the filler's vault. Both legs are restricted to fungible assets so that fills can
797/// be denominated in arbitrary amounts.
798#[derive(Clone, Debug)]
799pub struct PswapTransactionData {
800    /// Account ID of the creator account.
801    creator_account_id: AccountId,
802    /// Fungible asset offered in the swap.
803    offered_asset: FungibleAsset,
804    /// Fungible asset expected in the payback note generated when the PSWAP is filled.
805    requested_asset: FungibleAsset,
806}
807
808impl PswapTransactionData {
809    // CONSTRUCTORS
810    // --------------------------------------------------------------------------------------------
811
812    /// Creates a new [`PswapTransactionData`].
813    pub fn new(
814        creator_account_id: AccountId,
815        offered_asset: FungibleAsset,
816        requested_asset: FungibleAsset,
817    ) -> PswapTransactionData {
818        PswapTransactionData {
819            creator_account_id,
820            offered_asset,
821            requested_asset,
822        }
823    }
824
825    /// Returns the creator [`AccountId`].
826    pub fn creator_account_id(&self) -> AccountId {
827        self.creator_account_id
828    }
829
830    /// Returns the offered [`FungibleAsset`].
831    pub fn offered_asset(&self) -> FungibleAsset {
832        self.offered_asset
833    }
834
835    /// Returns the requested [`FungibleAsset`].
836    pub fn requested_asset(&self) -> FungibleAsset {
837        self.requested_asset
838    }
839}