stellar_base/operations/
create_claimable_balance.rs1use crate::amount::Stroops;
2use crate::asset::Asset;
3use crate::claim::Claimant;
4use crate::crypto::MuxedAccount;
5use crate::error::{Error, Result};
6use crate::operations::Operation;
7use crate::xdr;
8use std::convert::TryInto;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct CreateClaimableBalanceOperation {
12 source_account: Option<MuxedAccount>,
13 asset: Asset,
14 amount: Stroops,
15 claimants: Vec<Claimant>,
16}
17
18#[derive(Debug, Default)]
19pub struct CreateClaimableBalanceOperationBuilder {
20 source_account: Option<MuxedAccount>,
21 asset: Option<Asset>,
22 amount: Option<Stroops>,
23 claimants: Vec<Claimant>,
24}
25
26impl CreateClaimableBalanceOperation {
27 pub fn source_account(&self) -> &Option<MuxedAccount> {
29 &self.source_account
30 }
31
32 pub fn source_account_mut(&mut self) -> &mut Option<MuxedAccount> {
34 &mut self.source_account
35 }
36
37 pub fn asset(&self) -> &Asset {
39 &self.asset
40 }
41
42 pub fn asset_mut(&mut self) -> &mut Asset {
44 &mut self.asset
45 }
46
47 pub fn amount(&self) -> &Stroops {
49 &self.amount
50 }
51
52 pub fn amount_mut(&mut self) -> &mut Stroops {
54 &mut self.amount
55 }
56
57 pub fn claimants(&self) -> &Vec<Claimant> {
59 &self.claimants
60 }
61
62 pub fn claimants_mut(&mut self) -> &mut Vec<Claimant> {
64 &mut self.claimants
65 }
66
67 pub fn to_xdr_operation_body(&self) -> Result<xdr::OperationBody> {
69 let asset = self.asset.to_xdr()?;
70 let amount = self.amount.to_xdr_int64()?;
71 let claimants_res: Result<Vec<xdr::Claimant>> =
72 self.claimants.iter().map(|a| a.to_xdr()).collect();
73 let claimants = claimants_res?;
74 let inner = xdr::CreateClaimableBalanceOp {
75 asset,
76 amount,
77 claimants: claimants.try_into().map_err(|_| Error::XdrError)?,
78 };
79 Ok(xdr::OperationBody::CreateClaimableBalance(inner))
80 }
81
82 pub fn from_xdr_operation_body(
84 source_account: Option<MuxedAccount>,
85 x: &xdr::CreateClaimableBalanceOp,
86 ) -> Result<CreateClaimableBalanceOperation> {
87 let asset = Asset::from_xdr(&x.asset)?;
88 let amount = Stroops::from_xdr_int64(x.amount)?;
89 let claimants_res: Result<Vec<Claimant>> =
90 x.claimants.iter().map(Claimant::from_xdr).collect();
91 let claimants = claimants_res?;
92 Ok(CreateClaimableBalanceOperation {
93 source_account,
94 asset,
95 amount,
96 claimants,
97 })
98 }
99}
100
101impl CreateClaimableBalanceOperationBuilder {
102 pub fn new() -> CreateClaimableBalanceOperationBuilder {
103 Default::default()
104 }
105
106 pub fn with_source_account<S>(mut self, source: S) -> CreateClaimableBalanceOperationBuilder
107 where
108 S: Into<MuxedAccount>,
109 {
110 self.source_account = Some(source.into());
111 self
112 }
113
114 pub fn with_asset(mut self, asset: Asset) -> CreateClaimableBalanceOperationBuilder {
115 self.asset = Some(asset);
116 self
117 }
118
119 pub fn with_amount<A>(mut self, amount: A) -> Result<CreateClaimableBalanceOperationBuilder>
120 where
121 A: TryInto<Stroops>,
122 {
123 self.amount = Some(amount.try_into().map_err(|_| Error::InvalidStroopsAmount)?);
124 Ok(self)
125 }
126
127 pub fn with_claimants(
128 mut self,
129 claimants: Vec<Claimant>,
130 ) -> CreateClaimableBalanceOperationBuilder {
131 self.claimants = claimants;
132 self
133 }
134
135 pub fn add_claimant(mut self, claimant: Claimant) -> CreateClaimableBalanceOperationBuilder {
136 self.claimants.push(claimant);
137 self
138 }
139
140 pub fn build(self) -> Result<Operation> {
141 let asset = self.asset.ok_or_else(|| {
142 Error::InvalidOperation("missing create claimable balance asset".to_string())
143 })?;
144
145 let amount = self.amount.ok_or_else(|| {
146 Error::InvalidOperation("missing create claimable balance amount".to_string())
147 })?;
148
149 if self.claimants.is_empty() {
150 return Err(Error::InvalidOperation(
151 "missing create claimable balance claimants".to_string(),
152 ));
153 }
154
155 Ok(Operation::CreateClaimableBalance(
156 CreateClaimableBalanceOperation {
157 source_account: self.source_account,
158 asset,
159 amount,
160 claimants: self.claimants,
161 },
162 ))
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use crate::amount::Amount;
169 use crate::asset::Asset;
170 use crate::claim::{ClaimPredicate, Claimant};
171
172 use crate::network::Network;
173 use crate::operations::tests::*;
174 use crate::operations::Operation;
175 use crate::transaction::{Transaction, TransactionEnvelope, MIN_BASE_FEE};
176 use crate::xdr::{XDRDeserialize, XDRSerialize};
177 use chrono::Duration;
178 use std::str::FromStr;
179
180 #[test]
181 fn test_create_claimable_balance() {
182 let kp = keypair0();
183 let kp1 = keypair1();
184 let kp2 = keypair2();
185
186 let amount = Amount::from_str("12.0333").unwrap();
187 let asset = Asset::new_credit("ABCD", kp2.public_key()).unwrap();
188
189 let predicate =
190 ClaimPredicate::new_not(ClaimPredicate::new_before_relative_time(Duration::days(7)));
191
192 let claimant = Claimant::new(kp1.public_key(), predicate);
193
194 let op = Operation::new_create_claimable_balance()
195 .with_asset(asset)
196 .with_amount(amount)
197 .unwrap()
198 .add_claimant(claimant)
199 .build()
200 .unwrap();
201
202 let mut tx = Transaction::builder(kp.public_key(), 3556091187167235, MIN_BASE_FEE)
203 .add_operation(op)
204 .into_transaction()
205 .unwrap();
206 tx.sign(kp.as_ref(), &Network::new_test()).unwrap();
207 let envelope = tx.to_envelope();
208 let xdr = envelope.xdr_base64().unwrap();
209 let expected = "AAAAAgAAAADg3G3hclysZlFitS+s5zWyiiJD5B0STWy5LXCj6i5yxQAAAGQADKI/AAAAAwAAAAAAAAAAAAAAAQAAAAAAAAAOAAAAAUFCQ0QAAAAAfhHLNNY19eGrAtSgLD3VpaRm2AjNjxIBWQg9zS4VWZgAAAAABywiyAAAAAEAAAAAAAAAACXK8doPx27P6IReQlRRuweSSUiUfjqgyswxiu3Sh2R+AAAAAwAAAAEAAAAFAAAAAAAJOoAAAAAAAAAAAeoucsUAAABAUA3iWSLubKZc6r4CL2s9WTr/xMS5zuWgzxvm2hBs9use/2ejCagSPlRBeRCe3Ky4R+tKMk8Qpa2LATvgUQS2BQ==";
210 assert_eq!(expected, xdr);
211 let back = TransactionEnvelope::from_xdr_base64(&xdr).unwrap();
212 assert_eq!(envelope, back);
213 }
214
215 #[test]
216 fn test_create_claimable_balance_with_source_account() {
217 let kp = keypair0();
218 let kp1 = keypair1();
219 let kp2 = keypair2();
220
221 let amount = Amount::from_str("12.0333").unwrap();
222 let asset = Asset::new_credit("ABCD", kp2.public_key()).unwrap();
223
224 let predicate =
225 ClaimPredicate::new_not(ClaimPredicate::new_before_relative_time(Duration::days(7)));
226
227 let claimant = Claimant::new(kp1.public_key(), predicate);
228
229 let op = Operation::new_create_claimable_balance()
230 .with_source_account(kp.public_key())
231 .with_asset(asset)
232 .with_amount(amount)
233 .unwrap()
234 .add_claimant(claimant)
235 .build()
236 .unwrap();
237
238 let mut tx = Transaction::builder(kp.public_key(), 3556091187167235, MIN_BASE_FEE)
239 .add_operation(op)
240 .into_transaction()
241 .unwrap();
242 tx.sign(kp.as_ref(), &Network::new_test()).unwrap();
243 let envelope = tx.to_envelope();
244 let xdr = envelope.xdr_base64().unwrap();
245 let expected = "AAAAAgAAAADg3G3hclysZlFitS+s5zWyiiJD5B0STWy5LXCj6i5yxQAAAGQADKI/AAAAAwAAAAAAAAAAAAAAAQAAAAEAAAAA4Nxt4XJcrGZRYrUvrOc1sooiQ+QdEk1suS1wo+oucsUAAAAOAAAAAUFCQ0QAAAAAfhHLNNY19eGrAtSgLD3VpaRm2AjNjxIBWQg9zS4VWZgAAAAABywiyAAAAAEAAAAAAAAAACXK8doPx27P6IReQlRRuweSSUiUfjqgyswxiu3Sh2R+AAAAAwAAAAEAAAAFAAAAAAAJOoAAAAAAAAAAAeoucsUAAABAcaaQuqZMwpwVMS9814lZPhjt43B3xwlGNfeyx2wU2EJSDJ0h0d2a7dxngMzq4/abNVCjBKspCU7XroelAhSNCw==";
246 assert_eq!(expected, xdr);
247 let back = TransactionEnvelope::from_xdr_base64(&xdr).unwrap();
248 assert_eq!(envelope, back);
249 }
250}