fedimint_mintv2_client/
ecash.rs1use fedimint_core::config::FederationId;
2use fedimint_core::encoding::{Decodable, Encodable};
3use fedimint_core::invite_code::InviteCode;
4use fedimint_core::module::AmountUnit;
5use fedimint_core::util::SafeUrl;
6use fedimint_core::{Amount, PeerId};
7
8use crate::SpendableNote;
9
10#[derive(Clone, Debug, Encodable, Decodable)]
11pub struct ECash(Vec<ECashField>);
12
13#[derive(Clone, Debug, Decodable, Encodable)]
14enum ECashField {
15 Mint(FederationId),
16 Note(SpendableNote),
17 Invite {
21 peer_apis: Vec<(PeerId, SafeUrl)>,
22 federation_id: FederationId,
23 },
24 ApiSecret(String),
25 Unit(AmountUnit),
32 #[encodable_default]
33 Default {
34 variant: u64,
35 bytes: Vec<u8>,
36 },
37}
38
39impl ECash {
40 pub fn new(mint: FederationId, notes: Vec<SpendableNote>) -> Self {
41 Self(
42 std::iter::once(ECashField::Mint(mint))
43 .chain(notes.into_iter().map(ECashField::Note))
44 .collect(),
45 )
46 }
47
48 pub fn new_with_invite(notes: Vec<SpendableNote>, invite: &InviteCode) -> Self {
49 let mut fields = vec![ECashField::Mint(invite.federation_id())];
50
51 fields.extend(notes.into_iter().map(ECashField::Note));
52
53 fields.push(ECashField::Invite {
54 peer_apis: vec![(invite.peer(), invite.url())],
55 federation_id: invite.federation_id(),
56 });
57
58 if let Some(api_secret) = invite.api_secret() {
59 fields.push(ECashField::ApiSecret(api_secret));
60 }
61
62 Self(fields)
63 }
64
65 #[must_use]
68 pub fn with_unit(mut self, unit: AmountUnit) -> Self {
69 if !unit.is_bitcoin() {
70 self.0.push(ECashField::Unit(unit));
71 }
72
73 self
74 }
75
76 pub fn unit(&self) -> AmountUnit {
79 self.0
80 .iter()
81 .find_map(|field| match field {
82 ECashField::Unit(unit) => Some(*unit),
83 _ => None,
84 })
85 .unwrap_or(AmountUnit::BITCOIN)
86 }
87
88 pub fn amount(&self) -> Amount {
89 self.0
90 .iter()
91 .filter_map(|field| match field {
92 ECashField::Note(note) => Some(note.amount()),
93 _ => None,
94 })
95 .sum()
96 }
97
98 pub fn mint(&self) -> Option<FederationId> {
99 self.0.iter().find_map(|field| match field {
100 ECashField::Mint(mint) => Some(*mint),
101 _ => None,
102 })
103 }
104
105 pub fn notes(&self) -> Vec<SpendableNote> {
106 self.0
107 .iter()
108 .filter_map(|field| match field {
109 ECashField::Note(note) => Some(note.clone()),
110 _ => None,
111 })
112 .collect()
113 }
114
115 pub fn federation_invite(&self) -> Option<InviteCode> {
118 let api_secret = self.api_secret();
119
120 self.0.iter().find_map(|field| {
121 let ECashField::Invite {
122 peer_apis,
123 federation_id,
124 } = field
125 else {
126 return None;
127 };
128
129 let (peer_id, api) = peer_apis.first().cloned()?;
130
131 Some(InviteCode::new(
132 api,
133 peer_id,
134 *federation_id,
135 api_secret.clone(),
136 ))
137 })
138 }
139
140 fn api_secret(&self) -> Option<String> {
141 self.0.iter().find_map(|field| match field {
142 ECashField::ApiSecret(api_secret) => Some(api_secret.clone()),
143 _ => None,
144 })
145 }
146}
147
148#[cfg(test)]
149mod tests;