Skip to main content

cdk_common/database/mint/
mod.rs

1//! CDK Database
2
3use std::collections::HashMap;
4use std::ops::{Deref, DerefMut};
5
6use async_trait::async_trait;
7use cashu::quote_id::QuoteId;
8use cashu::Amount;
9
10use super::{DbTransactionFinalizer, Error};
11use crate::mint::{
12    self, MeltQuote, MintKeySetInfo, MintQuote as MintMintQuote, Operation, ProofsWithState,
13};
14use crate::nuts::{
15    BlindSignature, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, Proof, Proofs, PublicKey,
16    State,
17};
18use crate::payment::PaymentIdentifier;
19
20mod auth;
21
22#[cfg(feature = "test")]
23pub mod test;
24
25pub use auth::{DynMintAuthDatabase, MintAuthDatabase, MintAuthTransaction};
26
27// Re-export KVStore types from shared module for backward compatibility
28pub use super::kvstore::{
29    validate_kvstore_params, validate_kvstore_string, KVStore, KVStoreDatabase, KVStoreTransaction,
30    KVSTORE_NAMESPACE_KEY_ALPHABET, KVSTORE_NAMESPACE_KEY_MAX_LEN,
31};
32
33/// A wrapper indicating that a resource has been acquired with a database lock.
34///
35/// This type is returned by database operations that lock rows for update
36/// (e.g., `SELECT ... FOR UPDATE`). It serves as a compile-time marker that
37/// the wrapped resource was properly locked before being returned, ensuring
38/// that subsequent modifications are safe from race conditions.
39///
40/// # Usage
41///
42/// When you need to modify a database record, first acquire it using a locking
43/// query method. The returned `Acquired<T>` guarantees the row is locked for
44/// the duration of the transaction.
45///
46/// ```ignore
47/// // Acquire a quote with a row lock
48/// let mut quote: Acquired<MintQuote> = tx.get_mint_quote_for_update(&quote_id).await?;
49///
50/// // Safely modify the quote (row is locked)
51/// quote.state = QuoteState::Paid;
52///
53/// // Persist the changes
54/// tx.update_mint_quote(&mut quote).await?;
55/// ```
56///
57/// # Deref Behavior
58///
59/// `Acquired<T>` implements `Deref` and `DerefMut`, allowing transparent access
60/// to the inner value's methods and fields.
61#[derive(Debug)]
62pub struct Acquired<T> {
63    inner: T,
64}
65
66impl<T> From<T> for Acquired<T> {
67    /// Wraps a value to indicate it has been acquired with a lock.
68    ///
69    /// This is typically called by database layer implementations after
70    /// executing a locking query.
71    fn from(value: T) -> Self {
72        Acquired { inner: value }
73    }
74}
75
76impl<T> Acquired<T> {
77    /// Consumes the wrapper and returns the inner resource.
78    ///
79    /// Use this when you need to take ownership of the inner value,
80    /// for example when passing it to a function that doesn't accept
81    /// `Acquired<T>`.
82    pub fn inner(self) -> T {
83        self.inner
84    }
85}
86
87impl<T> Deref for Acquired<T> {
88    type Target = T;
89
90    /// Returns a reference to the inner resource.
91    fn deref(&self) -> &Self::Target {
92        &self.inner
93    }
94}
95
96impl<T> DerefMut for Acquired<T> {
97    /// Returns a mutable reference to the inner resource.
98    fn deref_mut(&mut self) -> &mut Self::Target {
99        &mut self.inner
100    }
101}
102
103/// Information about a melt request stored in the database
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct MeltRequestInfo {
106    /// Total amount of all input proofs in the melt request
107    pub inputs_amount: Amount<CurrencyUnit>,
108    /// Fee amount associated with the input proofs
109    pub inputs_fee: Amount<CurrencyUnit>,
110    /// Blinded messages for change outputs
111    pub change_outputs: Vec<BlindedMessage>,
112}
113
114/// Result of locking a melt quote and all related quotes atomically.
115///
116/// This struct is returned by [`QuotesTransaction::lock_melt_quote_and_related`]
117/// and contains both the target quote and all quotes sharing the same `request_lookup_id`.
118#[derive(Debug)]
119pub struct LockedMeltQuotes {
120    /// The target quote that was requested, if found
121    pub target: Option<Acquired<MeltQuote>>,
122    /// All quotes sharing the same `request_lookup_id` (including the target)
123    pub all_related: Vec<Acquired<MeltQuote>>,
124}
125
126/// KeysDatabaseWriter
127#[async_trait]
128pub trait KeysDatabaseTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
129    /// Add Active Keyset
130    async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error>;
131
132    /// Add [`MintKeySetInfo`]
133    async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error>;
134}
135
136/// Mint Keys Database trait
137#[async_trait]
138pub trait KeysDatabase {
139    /// Mint Keys Database Error
140    type Err: Into<Error> + From<Error>;
141
142    /// Begins a transaction
143    async fn begin_transaction<'a>(
144        &'a self,
145    ) -> Result<Box<dyn KeysDatabaseTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
146
147    /// Get Active Keyset
148    async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err>;
149
150    /// Get all Active Keyset
151    async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>;
152
153    /// Get [`MintKeySetInfo`]
154    async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
155
156    /// Get [`MintKeySetInfo`]s
157    async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
158}
159
160/// Mint Quote Database writer trait
161#[async_trait]
162pub trait QuotesTransaction {
163    /// Mint Quotes Database Error
164    type Err: Into<Error> + From<Error>;
165
166    /// Add melt_request with quote_id, inputs_amount, and inputs_fee
167    async fn add_melt_request(
168        &mut self,
169        quote_id: &QuoteId,
170        inputs_amount: Amount<CurrencyUnit>,
171        inputs_fee: Amount<CurrencyUnit>,
172    ) -> Result<(), Self::Err>;
173
174    /// Add blinded_messages for a quote_id
175    async fn add_blinded_messages(
176        &mut self,
177        quote_id: Option<&QuoteId>,
178        blinded_messages: &[BlindedMessage],
179        operation: &Operation,
180    ) -> Result<(), Self::Err>;
181
182    /// Delete blinded_messages by their blinded secrets
183    async fn delete_blinded_messages(
184        &mut self,
185        blinded_secrets: &[PublicKey],
186    ) -> Result<(), Self::Err>;
187
188    /// Get melt_request and associated blinded_messages by quote_id
189    async fn get_melt_request_and_blinded_messages(
190        &mut self,
191        quote_id: &QuoteId,
192    ) -> Result<Option<MeltRequestInfo>, Self::Err>;
193
194    /// Delete melt_request and associated blinded_messages by quote_id
195    async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err>;
196
197    /// Get [`MintMintQuote`] and lock it for update in this transaction
198    async fn get_mint_quote(
199        &mut self,
200        quote_id: &QuoteId,
201    ) -> Result<Option<Acquired<MintMintQuote>>, Self::Err>;
202
203    /// Get multiple [`MintMintQuote`]s by their IDs and lock them for update in this transaction.
204    ///
205    /// Returns results in the same order as the input IDs, with `None` for any IDs not found.
206    /// This method locks all found quotes to prevent race conditions during concurrent modifications.
207    async fn get_mint_quotes_by_ids(
208        &mut self,
209        quote_ids: &[QuoteId],
210    ) -> Result<Vec<Option<Acquired<MintMintQuote>>>, Self::Err>;
211
212    /// Add [`MintMintQuote`]
213    async fn add_mint_quote(
214        &mut self,
215        quote: MintMintQuote,
216    ) -> Result<Acquired<MintMintQuote>, Self::Err>;
217
218    /// Persists any pending changes made to the mint quote.
219    ///
220    /// This method extracts changes accumulated in the quote (via [`mint::MintQuote::take_changes`])
221    /// and persists them to the database. Changes may include new payments received or new
222    /// issuances recorded against the quote.
223    ///
224    /// If no changes are pending, this method returns successfully without performing
225    /// any database operations.
226    ///
227    /// # Arguments
228    ///
229    /// * `quote` - A mutable reference to an acquired (row-locked) mint quote. The quote
230    ///   must be locked to ensure transactional consistency when persisting changes.
231    ///
232    /// # Implementation Notes
233    ///
234    /// Implementations should call [`mint::MintQuote::take_changes`] to retrieve pending
235    /// changes, then persist each payment and issuance record, and finally update the
236    /// quote's aggregate counters (`amount_paid`, `amount_issued`) in the database.
237    async fn update_mint_quote(
238        &mut self,
239        quote: &mut Acquired<mint::MintQuote>,
240    ) -> Result<(), Self::Err>;
241
242    /// Get [`mint::MeltQuote`] and lock it for update in this transaction
243    async fn get_melt_quote(
244        &mut self,
245        quote_id: &QuoteId,
246    ) -> Result<Option<Acquired<mint::MeltQuote>>, Self::Err>;
247
248    /// Add [`mint::MeltQuote`]
249    async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
250
251    /// Retrieves all melt quotes matching a payment lookup identifier and locks them for update.
252    ///
253    /// This method returns multiple quotes because certain payment methods (notably BOLT12 offers)
254    /// can generate multiple payment attempts that share the same lookup identifier. Locking all
255    /// related quotes prevents race conditions where concurrent melt operations could interfere
256    /// with each other, potentially leading to double-spending or state inconsistencies.
257    ///
258    /// The returned quotes are locked within the current transaction to ensure safe concurrent
259    /// modification. This is essential during melt saga initiation and finalization to guarantee
260    /// atomic state transitions across all related quotes.
261    ///
262    /// # Arguments
263    ///
264    /// * `request_lookup_id` - The payment identifier used by the Lightning backend to track
265    ///   payment state (e.g., payment hash, offer ID, or label).
266    async fn get_melt_quotes_by_request_lookup_id(
267        &mut self,
268        request_lookup_id: &PaymentIdentifier,
269    ) -> Result<Vec<Acquired<MeltQuote>>, Self::Err>;
270
271    /// Locks a melt quote and all related quotes sharing the same request_lookup_id atomically.
272    ///
273    /// This method prevents deadlocks by acquiring all locks in a single query with consistent
274    /// ordering, rather than locking the target quote first and then related quotes separately.
275    ///
276    /// # Deadlock Prevention
277    ///
278    /// When multiple transactions try to melt quotes sharing the same `request_lookup_id`,
279    /// acquiring locks in two steps (first the target quote, then all related quotes) can cause
280    /// circular wait deadlocks. This method avoids that by:
281    /// 1. Using a subquery to find the `request_lookup_id` for the target quote
282    /// 2. Locking ALL quotes with that `request_lookup_id` in one atomic operation
283    /// 3. Ordering locks consistently by quote ID
284    ///
285    /// # Arguments
286    ///
287    /// * `quote_id` - The ID of the target melt quote
288    ///
289    /// # Returns
290    ///
291    /// A [`LockedMeltQuotes`] containing:
292    /// - `target`: The target quote (if found)
293    /// - `all_related`: All quotes sharing the same `request_lookup_id` (including the target)
294    ///
295    /// If the quote has no `request_lookup_id`, only the target quote is returned and locked.
296    async fn lock_melt_quote_and_related(
297        &mut self,
298        quote_id: &QuoteId,
299    ) -> Result<LockedMeltQuotes, Self::Err>;
300
301    /// Updates the request lookup id for a melt quote.
302    ///
303    /// Requires an [`Acquired`] melt quote to ensure the row is locked before modification.
304    async fn update_melt_quote_request_lookup_id(
305        &mut self,
306        quote: &mut Acquired<mint::MeltQuote>,
307        new_request_lookup_id: &PaymentIdentifier,
308    ) -> Result<(), Self::Err>;
309
310    /// Update [`mint::MeltQuote`] state.
311    ///
312    /// Requires an [`Acquired`] melt quote to ensure the row is locked before modification.
313    /// Returns the previous state.
314    async fn update_melt_quote_state(
315        &mut self,
316        quote: &mut Acquired<mint::MeltQuote>,
317        new_state: MeltQuoteState,
318        payment_proof: Option<String>,
319    ) -> Result<MeltQuoteState, Self::Err>;
320
321    /// Get all [`MintMintQuote`]s and lock it for update in this transaction
322    async fn get_mint_quote_by_request(
323        &mut self,
324        request: &str,
325    ) -> Result<Option<Acquired<MintMintQuote>>, Self::Err>;
326
327    /// Get all [`MintMintQuote`]s
328    async fn get_mint_quote_by_request_lookup_id(
329        &mut self,
330        request_lookup_id: &PaymentIdentifier,
331    ) -> Result<Option<Acquired<MintMintQuote>>, Self::Err>;
332}
333
334/// Mint Quote Database trait
335#[async_trait]
336pub trait QuotesDatabase {
337    /// Mint Quotes Database Error
338    type Err: Into<Error> + From<Error>;
339
340    /// Get [`MintMintQuote`]
341    async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintMintQuote>, Self::Err>;
342
343    /// Record a payment backend status check for a mint quote.
344    ///
345    /// Returns `true` when `last_checked` was updated, or `false` when it was already updated
346    /// within `min_interval` seconds.
347    async fn try_update_mint_quote_last_checked(
348        &self,
349        quote_id: &QuoteId,
350        last_checked: u64,
351        min_interval: u64,
352    ) -> Result<bool, Self::Err>;
353
354    /// Get multiple [`MintMintQuote`]s by their IDs.
355    ///
356    /// Returns results in the same order as the input IDs, with `None` for any IDs not found.
357    async fn get_mint_quotes_by_ids(
358        &self,
359        quote_ids: &[QuoteId],
360    ) -> Result<Vec<Option<MintMintQuote>>, Self::Err>;
361
362    /// Get all [`MintMintQuote`]s
363    async fn get_mint_quote_by_request(
364        &self,
365        request: &str,
366    ) -> Result<Option<MintMintQuote>, Self::Err>;
367    /// Get all [`MintMintQuote`]s
368    async fn get_mint_quote_by_request_lookup_id(
369        &self,
370        request_lookup_id: &PaymentIdentifier,
371    ) -> Result<Option<MintMintQuote>, Self::Err>;
372    /// Get Mint Quotes
373    async fn get_mint_quotes(&self) -> Result<Vec<MintMintQuote>, Self::Err>;
374    /// Get [`mint::MeltQuote`]
375    async fn get_melt_quote(
376        &self,
377        quote_id: &QuoteId,
378    ) -> Result<Option<mint::MeltQuote>, Self::Err>;
379    /// Get all [`mint::MeltQuote`]s
380    async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
381}
382
383/// Mint Proof Transaction trait
384#[async_trait]
385pub trait ProofsTransaction {
386    /// Mint Proof Database Error
387    type Err: Into<Error> + From<Error>;
388
389    /// Add  [`Proofs`]
390    ///
391    /// Adds proofs to the database. The database should error if the proof already exits, with a
392    /// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise.
393    async fn add_proofs(
394        &mut self,
395        proof: Proofs,
396        quote_id: Option<QuoteId>,
397        operation: &Operation,
398    ) -> Result<Acquired<ProofsWithState>, Self::Err>;
399
400    /// Updates the proofs to the given state in the database.
401    ///
402    /// Also updates the `state` field on the [`ProofsWithState`] wrapper to reflect
403    /// the new state after the database update succeeds.
404    async fn update_proofs_state(
405        &mut self,
406        proofs: &mut Acquired<ProofsWithState>,
407        new_state: State,
408    ) -> Result<(), Self::Err>;
409
410    /// get proofs states
411    async fn get_proofs(
412        &mut self,
413        ys: &[PublicKey],
414    ) -> Result<Acquired<ProofsWithState>, Self::Err>;
415
416    /// Remove [`Proofs`]
417    async fn remove_proofs(
418        &mut self,
419        ys: &[PublicKey],
420        quote_id: Option<QuoteId>,
421    ) -> Result<(), Self::Err>;
422
423    /// Get ys by quote id
424    async fn get_proof_ys_by_quote_id(
425        &mut self,
426        quote_id: &QuoteId,
427    ) -> Result<Vec<PublicKey>, Self::Err>;
428
429    /// Get proof ys by operation id
430    async fn get_proof_ys_by_operation_id(
431        &mut self,
432        operation_id: &uuid::Uuid,
433    ) -> Result<Vec<PublicKey>, Self::Err>;
434}
435
436/// Mint Proof Database trait
437#[async_trait]
438pub trait ProofsDatabase {
439    /// Mint Proof Database Error
440    type Err: Into<Error> + From<Error>;
441
442    /// Get [`Proofs`] by ys
443    async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
444    /// Get ys by quote id
445    async fn get_proof_ys_by_quote_id(
446        &self,
447        quote_id: &QuoteId,
448    ) -> Result<Vec<PublicKey>, Self::Err>;
449    /// Get [`Proofs`] state
450    async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
451
452    /// Get [`Proofs`] by state
453    async fn get_proofs_by_keyset_id(
454        &self,
455        keyset_id: &Id,
456    ) -> Result<(Proofs, Vec<Option<State>>), Self::Err>;
457
458    /// Get total proofs redeemed by keyset id
459    async fn get_total_redeemed(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
460
461    /// Get proof ys by operation id
462    async fn get_proof_ys_by_operation_id(
463        &self,
464        operation_id: &uuid::Uuid,
465    ) -> Result<Vec<PublicKey>, Self::Err>;
466}
467
468#[async_trait]
469/// Mint Signatures Transaction trait
470pub trait SignaturesTransaction {
471    /// Mint Signature Database Error
472    type Err: Into<Error> + From<Error>;
473
474    /// Add [`BlindSignature`]
475    async fn add_blind_signatures(
476        &mut self,
477        blinded_messages: &[PublicKey],
478        blind_signatures: &[BlindSignature],
479        quote_id: Option<QuoteId>,
480    ) -> Result<(), Self::Err>;
481
482    /// Get [`BlindSignature`]s
483    async fn get_blind_signatures(
484        &mut self,
485        blinded_messages: &[PublicKey],
486    ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
487}
488
489#[async_trait]
490/// Mint Signatures Database trait
491pub trait SignaturesDatabase {
492    /// Mint Signature Database Error
493    type Err: Into<Error> + From<Error>;
494
495    /// Get [`BlindSignature`]s
496    async fn get_blind_signatures(
497        &self,
498        blinded_messages: &[PublicKey],
499    ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
500
501    /// Get [`BlindSignature`]s for keyset_id
502    async fn get_blind_signatures_for_keyset(
503        &self,
504        keyset_id: &Id,
505    ) -> Result<Vec<BlindSignature>, Self::Err>;
506
507    /// Get [`BlindSignature`]s for quote
508    async fn get_blind_signatures_for_quote(
509        &self,
510        quote_id: &QuoteId,
511    ) -> Result<Vec<BlindSignature>, Self::Err>;
512
513    /// Get total amount issued by keyset id
514    async fn get_total_issued(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
515
516    /// Get blinded secrets (B values) by operation id
517    async fn get_blinded_secrets_by_operation_id(
518        &self,
519        operation_id: &uuid::Uuid,
520    ) -> Result<Vec<PublicKey>, Self::Err>;
521}
522
523#[async_trait]
524/// Saga Transaction trait
525pub trait SagaTransaction {
526    /// Saga Database Error
527    type Err: Into<Error> + From<Error>;
528
529    /// Get saga by operation_id
530    async fn get_saga(
531        &mut self,
532        operation_id: &uuid::Uuid,
533    ) -> Result<Option<mint::Saga>, Self::Err>;
534
535    /// Add saga
536    async fn add_saga(&mut self, saga: &mint::Saga) -> Result<(), Self::Err>;
537
538    /// Update saga state (only updates state and updated_at fields)
539    async fn update_saga(
540        &mut self,
541        operation_id: &uuid::Uuid,
542        new_state: mint::SagaStateEnum,
543    ) -> Result<(), Self::Err>;
544
545    /// Update saga state and optional finalization metadata.
546    async fn update_saga_with_finalization_data(
547        &mut self,
548        operation_id: &uuid::Uuid,
549        new_state: mint::SagaStateEnum,
550        finalization_data: Option<&mint::MeltFinalizationData>,
551    ) -> Result<(), Self::Err>;
552
553    /// Delete saga
554    async fn delete_saga(&mut self, operation_id: &uuid::Uuid) -> Result<(), Self::Err>;
555}
556
557#[async_trait]
558/// Saga Database trait
559pub trait SagaDatabase {
560    /// Saga Database Error
561    type Err: Into<Error> + From<Error>;
562
563    /// Get the melt saga associated with a melt quote id
564    async fn get_melt_saga_by_quote_id(
565        &self,
566        quote_id: &QuoteId,
567    ) -> Result<Option<mint::Saga>, Self::Err>;
568
569    /// Get all incomplete sagas for a given operation kind
570    async fn get_incomplete_sagas(
571        &self,
572        operation_kind: mint::OperationKind,
573    ) -> Result<Vec<mint::Saga>, Self::Err>;
574}
575
576#[async_trait]
577/// Completed Operations Transaction trait
578pub trait CompletedOperationsTransaction {
579    /// Completed Operations Database Error
580    type Err: Into<Error> + From<Error>;
581
582    /// Add completed operation
583    async fn add_completed_operation(
584        &mut self,
585        operation: &mint::Operation,
586        fee_by_keyset: &std::collections::HashMap<crate::nuts::Id, crate::Amount>,
587    ) -> Result<(), Self::Err>;
588}
589
590#[async_trait]
591/// Completed Operations Database trait
592pub trait CompletedOperationsDatabase {
593    /// Completed Operations Database Error
594    type Err: Into<Error> + From<Error>;
595
596    /// Get completed operation by operation_id
597    async fn get_completed_operation(
598        &self,
599        operation_id: &uuid::Uuid,
600    ) -> Result<Option<mint::Operation>, Self::Err>;
601
602    /// Get completed operations by operation kind
603    async fn get_completed_operations_by_kind(
604        &self,
605        operation_kind: mint::OperationKind,
606    ) -> Result<Vec<mint::Operation>, Self::Err>;
607
608    /// Get all completed operations
609    async fn get_completed_operations(&self) -> Result<Vec<mint::Operation>, Self::Err>;
610}
611
612/// Base database writer
613pub trait Transaction<Error>:
614    DbTransactionFinalizer<Err = Error>
615    + QuotesTransaction<Err = Error>
616    + SignaturesTransaction<Err = Error>
617    + ProofsTransaction<Err = Error>
618    + KVStoreTransaction<Error>
619    + SagaTransaction<Err = Error>
620    + CompletedOperationsTransaction<Err = Error>
621{
622}
623
624/// Mint Database trait
625#[async_trait]
626pub trait Database<Error>:
627    KVStoreDatabase<Err = Error>
628    + QuotesDatabase<Err = Error>
629    + ProofsDatabase<Err = Error>
630    + SignaturesDatabase<Err = Error>
631    + SagaDatabase<Err = Error>
632    + CompletedOperationsDatabase<Err = Error>
633{
634    /// Begins a transaction
635    async fn begin_transaction(&self) -> Result<Box<dyn Transaction<Error> + Send + Sync>, Error>;
636}
637
638/// Type alias for Mint Database
639pub type DynMintDatabase = std::sync::Arc<dyn Database<Error> + Send + Sync>;
640
641/// Type alias for Mint Transaction
642pub type DynMintTransaction = Box<dyn Transaction<Error> + Send + Sync>;