Skip to main content

fedimint_mintv2_client/
ecash.rs

1use fedimint_core::config::FederationId;
2use fedimint_core::encoding::{Decodable, Encodable};
3use fedimint_core::invite_code::InviteCode;
4use fedimint_core::util::SafeUrl;
5use fedimint_core::{Amount, PeerId};
6
7use crate::SpendableNote;
8
9#[derive(Clone, Debug, Encodable, Decodable)]
10pub struct ECash(Vec<ECashField>);
11
12#[derive(Clone, Debug, Decodable, Encodable)]
13enum ECashField {
14    Mint(FederationId),
15    Note(SpendableNote),
16    /// Invite code to join the federation by which the e-cash was issued. This
17    /// allows a recipient that has not yet joined the federation to do so
18    /// directly from the received ecash.
19    Invite {
20        peer_apis: Vec<(PeerId, SafeUrl)>,
21        federation_id: FederationId,
22    },
23    ApiSecret(String),
24    #[encodable_default]
25    Default {
26        variant: u64,
27        bytes: Vec<u8>,
28    },
29}
30
31impl ECash {
32    pub fn new(mint: FederationId, notes: Vec<SpendableNote>) -> Self {
33        Self(
34            std::iter::once(ECashField::Mint(mint))
35                .chain(notes.into_iter().map(ECashField::Note))
36                .collect(),
37        )
38    }
39
40    pub fn new_with_invite(notes: Vec<SpendableNote>, invite: &InviteCode) -> Self {
41        let mut fields = vec![ECashField::Mint(invite.federation_id())];
42
43        fields.extend(notes.into_iter().map(ECashField::Note));
44
45        fields.push(ECashField::Invite {
46            peer_apis: vec![(invite.peer(), invite.url())],
47            federation_id: invite.federation_id(),
48        });
49
50        if let Some(api_secret) = invite.api_secret() {
51            fields.push(ECashField::ApiSecret(api_secret));
52        }
53
54        Self(fields)
55    }
56
57    pub fn amount(&self) -> Amount {
58        self.0
59            .iter()
60            .filter_map(|field| match field {
61                ECashField::Note(note) => Some(note.amount()),
62                _ => None,
63            })
64            .sum()
65    }
66
67    pub fn mint(&self) -> Option<FederationId> {
68        self.0.iter().find_map(|field| match field {
69            ECashField::Mint(mint) => Some(*mint),
70            _ => None,
71        })
72    }
73
74    pub fn notes(&self) -> Vec<SpendableNote> {
75        self.0
76            .iter()
77            .filter_map(|field| match field {
78                ECashField::Note(note) => Some(note.clone()),
79                _ => None,
80            })
81            .collect()
82    }
83
84    /// The invite code of the federation by which this ecash was issued, if it
85    /// was included by the sender.
86    pub fn federation_invite(&self) -> Option<InviteCode> {
87        let api_secret = self.api_secret();
88
89        self.0.iter().find_map(|field| {
90            let ECashField::Invite {
91                peer_apis,
92                federation_id,
93            } = field
94            else {
95                return None;
96            };
97
98            let (peer_id, api) = peer_apis.first().cloned()?;
99
100            Some(InviteCode::new(
101                api,
102                peer_id,
103                *federation_id,
104                api_secret.clone(),
105            ))
106        })
107    }
108
109    fn api_secret(&self) -> Option<String> {
110        self.0.iter().find_map(|field| match field {
111            ECashField::ApiSecret(api_secret) => Some(api_secret.clone()),
112            _ => None,
113        })
114    }
115}