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