substrate_stellar_sdk/xdr/impls/
transaction.rs1use crate::{
4 types::{
5 FeeBumpTransactionEnvelope, FeeBumpTransactionExt, FeeBumpTransactionInnerTx, Memo, MuxedAccount, Operation,
6 Preconditions, TimeBounds, Transaction, TransactionEnvelope, TransactionExt, TransactionV0, TransactionV0Ext,
7 TransactionV1Envelope,
8 },
9 xdr::compound_types::LimitedVarArray,
10 FeeBumpTransaction, IntoAmount, IntoMuxedAccountId, StellarSdkError, BASE_FEE_STROOPS,
11};
12
13impl Transaction {
16 pub fn new<T: IntoMuxedAccountId>(
17 source_account: T,
18 sequence_number: i64,
19 fee_per_operation: Option<u32>,
20 preconditions: Preconditions,
21 memo: Option<Memo>,
22 ) -> Result<Self, StellarSdkError> {
23 let transaction = Self {
24 source_account: source_account.into_muxed_account_id()?,
25 fee: fee_per_operation.unwrap_or(BASE_FEE_STROOPS),
26 seq_num: sequence_number,
27 cond: preconditions,
28 memo: memo.unwrap_or(Memo::MemoNone),
29 operations: LimitedVarArray::new_empty(),
30 ext: TransactionExt::V0,
31 };
32
33 Ok(transaction)
34 }
35
36 pub fn append_operation(&mut self, operation: Operation) -> Result<(), StellarSdkError> {
37 self.operations.push(operation)
38 }
39
40 pub fn into_transaction_envelope(mut self) -> TransactionEnvelope {
42 self.fee = self.fee.checked_mul(self.operations.len() as u32).unwrap_or(self.fee);
43
44 TransactionEnvelope::EnvelopeTypeTx(TransactionV1Envelope {
45 tx: self,
46 signatures: LimitedVarArray::new_empty(),
47 })
48 }
49}
50
51impl From<TransactionV0> for Transaction {
52 fn from(transaction: TransactionV0) -> Self {
53 let time_bounds = transaction.time_bounds.unwrap_or(TimeBounds::from_time_points((), ()));
54
55 Self {
56 source_account: MuxedAccount::KeyTypeEd25519(transaction.source_account_ed25519),
57 fee: transaction.fee,
58 seq_num: transaction.seq_num,
59 cond: Preconditions::PrecondTime(time_bounds),
60 memo: transaction.memo,
61 operations: transaction.operations,
62 ext: match transaction.ext {
63 TransactionV0Ext::V0 => TransactionExt::V0,
64 TransactionV0Ext::Default(default) => TransactionExt::Default(default),
65 },
66 }
67 }
68}
69
70impl FeeBumpTransaction {
71 pub fn new<T: IntoMuxedAccountId, S: IntoAmount>(
72 fee_source: T,
73 new_total_fee: S,
74 inner_transaction_envelope: TransactionEnvelope,
75 ) -> Result<Self, StellarSdkError> {
76 let v1_envelope = match inner_transaction_envelope {
77 TransactionEnvelope::EnvelopeTypeTxV0(envelope) =>
78 TransactionV1Envelope { tx: envelope.tx.into(), signatures: envelope.signatures },
79 TransactionEnvelope::EnvelopeTypeTx(envelope) => envelope,
80 TransactionEnvelope::EnvelopeTypeTxFeeBump(_) => return Err(StellarSdkError::CantWrapFeeBumpTransaction),
81 TransactionEnvelope::Default(_) => unreachable!(),
82 };
83
84 let transaction = Self {
85 fee_source: fee_source.into_muxed_account_id()?,
86 fee: new_total_fee.into_stroop_amount(false)?,
87 inner_tx: FeeBumpTransactionInnerTx::EnvelopeTypeTx(v1_envelope),
88 ext: FeeBumpTransactionExt::V0,
89 };
90
91 Ok(transaction)
92 }
93
94 pub fn into_transaction_envelope(self) -> TransactionEnvelope {
95 TransactionEnvelope::EnvelopeTypeTxFeeBump(FeeBumpTransactionEnvelope {
96 tx: self,
97 signatures: LimitedVarArray::new_empty(),
98 })
99 }
100}
101
102#[cfg(test)]
103mod test {
104 use crate::{
105 network::TEST_NETWORK, types::Preconditions, Asset, IntoSecretKey, Memo, MilliSecondEpochTime, Operation,
106 Price, SecondEpochTime, StroopAmount, TimeBounds, Transaction, TransactionEnvelope, XdrCodec,
107 };
108
109 const ACCOUNT_ID1: &str = "GDGRDTRINPF66FNC47H22NY6BNWMCD5Q4XZTVA2KG7PFZ64WHRIU62TQ";
110 const ACCOUNT_ID2: &str = "GBNKQVTFRP25TIQRODMU5GJGSXDKHCEUDN7LNMOS5PNM427LMR77NV4M";
111 const ACCOUNT_ID3: &str = "GCACWDM2VEYTXGUI3CUYLBJ453IBEPQ3XEJKA772ARAP5XDQ4NMGFZGJ";
112
113 const SIGNER1: &str = "SCVKZEONBSU3XD6OTHXGAP6BTEWHOU4RPZQZJJ5AVAGPXUZ5A4D7MU6S";
114 const SIGNER3: &str = "SDOKV37I4TI655LMEMDQFOWESJ3LK6DDFKIVTYKN4YYTSAYFIBPP7MYI";
115
116 #[test]
117 fn build_transaction1() {
118 let mut transaction = Transaction::new(
119 ACCOUNT_ID1,
120 1980190376853505,
121 Some(100),
122 Preconditions::PrecondTime(TimeBounds::from_time_points(SecondEpochTime(0), SecondEpochTime(1626258131))),
123 None,
124 )
125 .unwrap();
126
127 transaction
128 .append_operation(Operation::new_payment(ACCOUNT_ID2, Asset::native(), "123.456").unwrap())
129 .unwrap();
130
131 let expexted_base64 = b"AAAAAgAAAADNEc4oa8vvFaL\
132 nz603HgtswQ+w5fM6g0o33lz7ljxRTwAAAGQABwj5AAAAAQAAAAEA\
133 AAAAAAAAAAAAAABg7rrTAAAAAAAAAAEAAAAAAAAAAQAAAABaqFZli\
134 /XZohFw2U6ZJpXGo4iUG362sdLr2s5r62R/9gAAAAAAAAAASZXkAA\
135 AAAAAAAAAA";
136 let envelope = transaction.into_transaction_envelope();
137
138 assert_eq!(envelope, TransactionEnvelope::from_base64_xdr(expexted_base64).unwrap());
139 assert_eq!(envelope.to_base64_xdr(), expexted_base64);
140 }
141
142 #[test]
143 fn build_transaction2() {
144 let mut transaction = Transaction::new(
145 ACCOUNT_ID1,
146 1980190376853505,
147 Some(321),
148 Preconditions::PrecondTime(TimeBounds::from_time_points(SecondEpochTime(0), SecondEpochTime(0))),
149 Some(Memo::from_text_memo("Hello World!").unwrap()),
150 )
151 .unwrap();
152
153 transaction
154 .append_operation(
155 Operation::new_payment(
156 ACCOUNT_ID2,
157 Asset::from_asset_code("USD", ACCOUNT_ID3).unwrap(),
158 StroopAmount(1234560000),
159 )
160 .unwrap(),
161 )
162 .unwrap();
163
164 let expexted_base64 = b"AAAAAgAAAADNEc4oa8vvFaLnz603HgtswQ+w5fM6g0o33lz7ljxRTwAAAUEABwj5AAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAxIZWxsbyBXb3JsZCEAAAABAAAAAAAAAAEAAAAAWqhWZYv12aIRcNlOmSaVxqOIlBt+trHS69rOa+tkf/YAAAABVVNEAAAAAACAKw2aqTE7mojYqYWFPO7QEj4buRKgf/oEQP7ccONYYgAAAABJleQAAAAAAAAAAAA=";
165 let envelope = transaction.into_transaction_envelope();
166
167 assert_eq!(envelope, TransactionEnvelope::from_base64_xdr(expexted_base64).unwrap());
168 assert_eq!(envelope.to_base64_xdr(), expexted_base64);
169 }
170
171 #[test]
172 fn build_transaction3() {
173 let mut transaction = Transaction::new(
174 ACCOUNT_ID1,
175 1980190376853505,
176 Some(321),
177 Preconditions::PrecondTime(TimeBounds::from_time_points(
178 SecondEpochTime(162620000),
179 MilliSecondEpochTime(1626263454_000),
180 )),
181 Some(Memo::from_text_memo("Hello World!").unwrap()),
182 )
183 .unwrap();
184
185 transaction
186 .append_operation(
187 Operation::new_payment(
188 ACCOUNT_ID2,
189 Asset::from_asset_code("USD", ACCOUNT_ID3).unwrap(),
190 StroopAmount(1234560000),
191 )
192 .unwrap()
193 .set_source_account(ACCOUNT_ID3)
194 .unwrap(),
195 )
196 .unwrap();
197
198 transaction
199 .append_operation(
200 Operation::new_manage_sell_offer(
201 Asset::from_asset_code("DOMINATION", ACCOUNT_ID2).unwrap(),
202 Asset::native(),
203 "152.103",
204 Price::from_float(4.58).unwrap(),
205 Some(123456789),
206 )
207 .unwrap(),
208 )
209 .unwrap();
210
211 let expexted_base64 = b"AAAAAgAAAADNEc4oa8vvFaLnz603HgtswQ+w5fM6g0o33lz7ljxRTwAAAoIABwj5AAAAAQAAAAEAAAAACbFiYAAAAABg7s+eAAAAAQAAAAxIZWxsbyBXb3JsZCEAAAACAAAAAQAAAACAKw2aqTE7mojYqYWFPO7QEj4buRKgf/oEQP7ccONYYgAAAAEAAAAAWqhWZYv12aIRcNlOmSaVxqOIlBt+trHS69rOa+tkf/YAAAABVVNEAAAAAACAKw2aqTE7mojYqYWFPO7QEj4buRKgf/oEQP7ccONYYgAAAABJleQAAAAAAAAAAAMAAAACRE9NSU5BVElPTgAAAAAAAFqoVmWL9dmiEXDZTpkmlcajiJQbfrax0uvazmvrZH/2AAAAAAAAAABaqRNwAAAA5QAAADIAAAAAB1vNFQAAAAAAAAAA";
212 let mut envelope = transaction.into_transaction_envelope();
213
214 assert_eq!(envelope, TransactionEnvelope::from_base64_xdr(expexted_base64).unwrap());
215 assert_eq!(envelope.to_base64_xdr(), expexted_base64);
216
217 envelope
218 .sign(&TEST_NETWORK, vec![&SIGNER1.into_secret_key().unwrap(), &SIGNER3.into_secret_key().unwrap()])
219 .unwrap();
220
221 let expexted_singed_base64 = b"AAAAAgAAAADNEc4oa8vvFaLnz603HgtswQ+w5fM6g0o33lz7ljxRTwAAAoIABwj5AAAAAQAAAAEAAAAACbFiYAAAAABg7s+eAAAAAQAAAAxIZWxsbyBXb3JsZCEAAAACAAAAAQAAAACAKw2aqTE7mojYqYWFPO7QEj4buRKgf/oEQP7ccONYYgAAAAEAAAAAWqhWZYv12aIRcNlOmSaVxqOIlBt+trHS69rOa+tkf/YAAAABVVNEAAAAAACAKw2aqTE7mojYqYWFPO7QEj4buRKgf/oEQP7ccONYYgAAAABJleQAAAAAAAAAAAMAAAACRE9NSU5BVElPTgAAAAAAAFqoVmWL9dmiEXDZTpkmlcajiJQbfrax0uvazmvrZH/2AAAAAAAAAABaqRNwAAAA5QAAADIAAAAAB1vNFQAAAAAAAAACljxRTwAAAEB0B8vODxIESpa9H9f4QkPtFHVg4Xjx2A9aTncJOkW6BW0i1AxZFgMvrzEb7nO5UnXRvCKnBmuhpvA76YivAXYGcONYYgAAAECesooI2hhhuoOcLcXB76L58vMOrFvPFqpIeG+/zzLZXz0XYU6aELdtDxLAhK8GZCIZwlXdJ/RyZF9/2YzqbPMC";
222 assert_eq!(envelope, TransactionEnvelope::from_base64_xdr(expexted_singed_base64).unwrap());
223 assert_eq!(envelope.to_base64_xdr(), expexted_singed_base64);
224 }
225}