Skip to main content

worgl_sdk/
payment.rs

1//! What a payee asks for and what a payer hands over.
2
3use serde::{Deserialize, Serialize};
4use worgl::{Output, PublicKey, TransferRequest};
5
6/// What a payee asks to be paid: one fresh key of its own for each note it
7/// expects, so the mint never sees two of its notes under one key.
8///
9/// Make one with [`Holder::request`](crate::Holder::request) and hand it to
10/// the payer, who answers with a [`Payment`].
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct PaymentRequest {
13    /// The mint whose notes the payee accepts.
14    pub mint: PublicKey,
15    /// The notes to be created for the payee: an amount and a fresh key
16    /// each.
17    pub outputs: Vec<Output>,
18}
19
20impl PaymentRequest {
21    /// The total asked for.
22    pub fn amount(&self) -> u128 {
23        self.outputs.iter().map(|o| u128::from(o.amount)).sum()
24    }
25
26    /// Checks that a payment pays exactly what this request asks: every
27    /// transfer spends one note of the requested mint, and every requested
28    /// output appears in exactly one transfer.
29    ///
30    /// It cannot tell whether the payer's notes are still live; submitting
31    /// the transfers does.
32    pub fn check(&self, payment: &Payment) -> Result<(), CheckError> {
33        if payment.mint != self.mint {
34            return Err(CheckError::WrongMint);
35        }
36        let mut wanted = self.outputs.clone();
37        for scheduled in &payment.transfers {
38            let request = &scheduled.request;
39            let [spend] = request.inputs.as_slice() else {
40                return Err(CheckError::NotOneInput);
41            };
42            if spend.note.issuer() != &self.mint {
43                return Err(CheckError::WrongMint);
44            }
45            for output in &request.outputs {
46                if let Some(i) = wanted.iter().position(|w| w == output) {
47                    wanted.swap_remove(i);
48                } else if self.outputs.iter().any(|w| w.holder == output.holder) {
49                    return Err(CheckError::Duplicate(*output));
50                }
51            }
52        }
53        match wanted.first() {
54            Some(missing) => Err(CheckError::Missing(*missing)),
55            None => Ok(()),
56        }
57    }
58}
59
60/// A payment does not match its request.
61#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
62pub enum CheckError {
63    /// It spends notes of another mint.
64    #[error("the payment spends notes of another mint")]
65    WrongMint,
66    /// A transfer does not spend exactly one note.
67    #[error("a transfer spends more or fewer than one note")]
68    NotOneInput,
69    /// A requested note is not paid.
70    #[error("the payment does not pay {} to {}", .0.amount, .0.holder)]
71    Missing(Output),
72    /// A requested key is paid more than once, or with the wrong amount.
73    #[error("the payment pays {} to {} more than asked", .0.amount, .0.holder)]
74    Duplicate(Output),
75}
76
77/// The payer's answer to a [`PaymentRequest`]: signed transfers that,
78/// submitted to the mint, create the payee's notes.
79///
80/// Either side may submit them. Each spends one of the payer's notes and
81/// pays any change to fresh keys of the payer.
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct Payment {
84    /// The mint the transfers go to.
85    pub mint: PublicKey,
86    /// The transfers, each with when to submit it.
87    pub transfers: Vec<ScheduledTransfer>,
88}
89
90/// A signed transfer and when to submit it.
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct ScheduledTransfer {
93    /// Seconds to wait before submitting, counted from when the transfers
94    /// were made. Transfers sent together tell the mint their notes had one
95    /// owner, so the SDK spreads them out.
96    pub after_secs: u64,
97    /// The transfer.
98    pub request: TransferRequest,
99}
100
101/// How far apart to submit the transfers of one payment when it takes more
102/// than one, in seconds. Each gap is drawn at random from this range.
103///
104/// Closer spacing pays faster but lets the mint guess, from timing alone,
105/// that the notes had one owner.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
107pub struct Spacing {
108    /// Shortest gap.
109    pub min_secs: u64,
110    /// Longest gap.
111    pub max_secs: u64,
112}
113
114impl Spacing {
115    /// One gap, drawn at random.
116    pub(crate) fn gap(self) -> u64 {
117        crate::random::between(self.min_secs, self.max_secs)
118    }
119}
120
121impl Default for Spacing {
122    /// Gaps of 2 to 60 seconds.
123    fn default() -> Self {
124        Self {
125            min_secs: 2,
126            max_secs: 60,
127        }
128    }
129}