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}