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}