substrate_stellar_sdk/xdr/impls/
transaction_envelope.rs

1//! Transaction envelopes and signatures
2
3use core::convert::TryInto;
4use sodalite::SIGN_LEN;
5use sp_std::{prelude::*, vec::Vec};
6
7use crate::{
8    network::Network,
9    secret_key::SecretKey,
10    types::{
11        DecoratedSignature, PublicKey, TransactionEnvelope, TransactionSignaturePayload,
12        TransactionSignaturePayloadTaggedTransaction,
13    },
14    utils::{
15        base64,
16        sha256::{sha256, BinarySha256Hash},
17    },
18    xdr::{
19        compound_types::{LimitedVarArray, LimitedVarOpaque},
20        xdr_codec::XdrCodec,
21    },
22    StellarSdkError,
23};
24
25impl TransactionEnvelope {
26    fn get_signatures(&mut self) -> &mut LimitedVarArray<DecoratedSignature, 20> {
27        match self {
28            TransactionEnvelope::EnvelopeTypeTxV0(envelope) => &mut envelope.signatures,
29            TransactionEnvelope::EnvelopeTypeTx(envelope) => &mut envelope.signatures,
30            TransactionEnvelope::EnvelopeTypeTxFeeBump(envelope) => &mut envelope.signatures,
31            _ => unreachable!("Invalid transaction envelope type"),
32        }
33    }
34
35    /// Generate a base64 encoded signature
36    ///
37    /// Generate a signature for the `transaction_envelope`. Generate the signature
38    /// for a network having the passphrase contained in `network`. The secret key for
39    /// signing the envelope is provided by `keypair`.
40    /// The signature is not appended to the transaction envelope.
41    pub fn create_base64_signature(&self, network: &Network, keypair: &SecretKey) -> Vec<u8> {
42        let transaction_hash = self.get_hash(network);
43        let signature = keypair.create_signature(transaction_hash);
44        base64::encode(signature)
45    }
46
47    /// Generate and add signatures to a transaction envelope
48    ///
49    /// Generate and add signatures to the `transaction_envelope`. The signature
50    /// is generated for a network having the passphrase contained in `network`. Generate and add
51    /// one signature for each keypair in `keypairs`.
52    pub fn sign(&mut self, network: &Network, keypairs: Vec<&SecretKey>) -> Result<(), StellarSdkError> {
53        let transaction_hash = self.get_hash(network);
54
55        let signatures = self.get_signatures();
56
57        for keypair in keypairs.iter() {
58            let signature = keypair.create_signature(transaction_hash);
59            let hint = keypair.get_public().get_signature_hint();
60
61            signatures
62                .push(DecoratedSignature { hint, signature: LimitedVarOpaque::new(Vec::from(signature)).unwrap() })
63                .map_err(|_| StellarSdkError::TooManySignatures)?;
64        }
65
66        Ok(())
67    }
68
69    /// Add a base64 encoded signature to a transaction envelope
70    ///
71    /// Add a previously generated base64 encoded signature to the `transaction_envelope`.
72    /// This function verifies whether the signature is valid given the passphrase contained in `network`
73    /// and the `public_key`.
74    pub fn add_base64_signature<T: AsRef<[u8]>>(
75        &mut self,
76        network: &Network,
77        base64_signature: T,
78        public_key: &PublicKey,
79    ) -> Result<(), StellarSdkError> {
80        let signature = match base64::decode(base64_signature) {
81            Err(err) => Err(StellarSdkError::InvalidBase64Encoding(err))?,
82            Ok(signature) => {
83                if signature.len() != SIGN_LEN {
84                    return Err(StellarSdkError::InvalidSignatureLength {
85                        found_length: signature.len(),
86                        expected_length: SIGN_LEN,
87                    })
88                };
89                signature
90            },
91        };
92
93        let transaction_hash = self.get_hash(network);
94        if !public_key.verify_signature(transaction_hash, signature[..].try_into().unwrap()) {
95            return Err(StellarSdkError::PublicKeyCantVerify)
96        }
97
98        let signatures = self.get_signatures();
99
100        signatures
101            .push(DecoratedSignature {
102                hint: public_key.get_signature_hint(),
103                signature: LimitedVarOpaque::new(signature).unwrap(),
104            })
105            .map_err(|_| StellarSdkError::TooManySignatures)?;
106
107        Ok(())
108    }
109
110    pub fn get_hash(&self, network: &Network) -> BinarySha256Hash {
111        let network_id = network.get_id().clone();
112
113        let tagged_transaction = match self {
114            TransactionEnvelope::EnvelopeTypeTxV0(envelope) => {
115                let transaction = envelope.tx.clone().into();
116                TransactionSignaturePayloadTaggedTransaction::EnvelopeTypeTx(transaction)
117            },
118
119            TransactionEnvelope::EnvelopeTypeTx(envelope) =>
120                TransactionSignaturePayloadTaggedTransaction::EnvelopeTypeTx(envelope.tx.clone()),
121
122            TransactionEnvelope::EnvelopeTypeTxFeeBump(envelope) =>
123                TransactionSignaturePayloadTaggedTransaction::EnvelopeTypeTxFeeBump(envelope.tx.clone()),
124
125            _ => unimplemented!("This type of transaction envelope is not supported"),
126        };
127
128        let signature_payload = TransactionSignaturePayload { network_id, tagged_transaction };
129
130        sha256(signature_payload.to_xdr())
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use sp_std::{prelude::*, vec::Vec};
137
138    use crate::{
139        types::{
140            AlphaNum4, Asset, ManageSellOfferOp, Memo, MuxedAccount, Operation, OperationBody, PaymentOp,
141            Preconditions, Price, PublicKey, TimeBounds, Transaction, TransactionEnvelope, TransactionExt,
142            TransactionMeta, TransactionV1Envelope, Uint256,
143        },
144        xdr::compound_types::LimitedVarArray,
145        XdrCodec,
146    };
147
148    use crate::{network::TEST_NETWORK, secret_key::SecretKey};
149
150    const ENVELOPE: &[u8; 408] = b"AAAAAgAAAAC9xFYU1gQJeH4apEfzJkMCsW5DL4GEWRpyVjQHOlWVzgAAAZA\
151        CGsQoAAQytgAAAAAAAAAAAAAAAgAAAAAAAAADAAAAAVhMUEcAAAAAxxJMrxQQOx9raxDm3\
152        lINsLvksi7tj1BCQXzWTtqigbgAAAAAAAAAAAbK5N8CprKDAExLQAAAAAAAAAAAAAAAAAA\
153        AAAMAAAAAAAAAAVhMUEcAAAAAxxJMrxQQOx9raxDm3lINsLvksi7tj1BCQXzWTtqigbgAA\
154        AAAlV2+xQAEaBMAJiWgAAAAAAAAAAAAAAAAAAAAATpVlc4AAABAaX11e1dGcDkXrFT5s3Q\
155        N6x3v4kQqJ/1VIjqO00y6OStd70/aYiXR35e4289RvmBTudJ5Q05PaRsD8p1qa17VDQ==";
156
157    const META: &[u8; 2060] = b"AAAAAgAAAAIAAAADAiOf2gAAAAAAAAAAvcRWFNYECXh+GqRH8yZDArFuQy+Bh\
158        FkaclY0BzpVlc4AAAABMLFdwgIaxCgABDK1AAAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAA\
159        AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECI5/aAAAAAAAAAAC9xFYU1gQJeH4apEf\
160        zJkMCsW5DL4GEWRpyVjQHOlWVzgAAAAEwsV3CAhrEKAAEMrYAAAABAAAAAAAAAAAAAAAAAQ\
161        AAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAUAAAADAiOf2gAAA\
162        AAAAAAAvcRWFNYECXh+GqRH8yZDArFuQy+BhFkaclY0BzpVlc4AAAABMLFdwgIaxCgABDK2\
163        AAAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
164        AAAECI5/aAAAAAAAAAAC9xFYU1gQJeH4apEfzJkMCsW5DL4GEWRpyVjQHOlWVzgAAAAEwsV\
165        3CAhrEKAAEMrYAAAACAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAADxs5AUAAAAAAAAAA\
166        AAAAAAAAAAAAAAAAAIjn9oAAAACAAAAAL3EVhTWBAl4fhqkR/MmQwKxbkMvgYRZGnJWNAc6\
167        VZXOAAAAACWxxV0AAAABWExQRwAAAADHEkyvFBA7H2trEObeUg2wu+SyLu2PUEJBfNZO2qK\
168        BuAAAAAAAAAAABsrk3wKmsoMATEtAAAAAAAAAAAAAAAAAAAAAAwIjn9gAAAABAAAAAL3EVh\
169        TWBAl4fhqkR/MmQwKxbkMvgYRZGnJWNAc6VZXOAAAAAVhMUEcAAAAAxxJMrxQQOx9raxDm3\
170        lINsLvksi7tj1BCQXzWTtqigbgAAAAADaUL/n//////////AAAAAQAAAAEAAAAAAAAAAAAA\
171        AAAAAAAAAAAAAAAAAAAAAAABAiOf2gAAAAEAAAAAvcRWFNYECXh+GqRH8yZDArFuQy+BhFk\
172        aclY0BzpVlc4AAAABWExQRwAAAADHEkyvFBA7H2trEObeUg2wu+SyLu2PUEJBfNZO2qKBuA\
173        AAAAANpQv+f/////////8AAAABAAAAAQAAAAAAAAAAAAAAAAbK5N8AAAAAAAAAAAAAAAUAA\
174        AADAiOf2gAAAAAAAAAAvcRWFNYECXh+GqRH8yZDArFuQy+BhFkaclY0BzpVlc4AAAABMLFd\
175        wgIaxCgABDK2AAAAAgAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAA8bOQFAAAAAAAAAAA\
176        AAAAAAAAAAAAAAAECI5/aAAAAAAAAAAC9xFYU1gQJeH4apEfzJkMCsW5DL4GEWRpyVjQHOl\
177        WVzgAAAAEwsV3CAhrEKAAEMrYAAAADAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAADxs5\
178        AUAAAAAlV2+wgAAAAAAAAAAAAAAAAIjn9oAAAACAAAAAL3EVhTWBAl4fhqkR/MmQwKxbkMv\
179        gYRZGnJWNAc6VZXOAAAAACWxxV4AAAAAAAAAAVhMUEcAAAAAxxJMrxQQOx9raxDm3lINsLv\
180        ksi7tj1BCQXzWTtqigbgAAAAAlV2+wgAEaBMAJiWgAAAAAAAAAAAAAAAAAAAAAwIjn9oAAA\
181        ABAAAAAL3EVhTWBAl4fhqkR/MmQwKxbkMvgYRZGnJWNAc6VZXOAAAAAVhMUEcAAAAAxxJMr\
182        xQQOx9raxDm3lINsLvksi7tj1BCQXzWTtqigbgAAAAADaUL/n//////////AAAAAQAAAAEA\
183        AAAAAAAAAAAAAAAGyuTfAAAAAAAAAAAAAAABAiOf2gAAAAEAAAAAvcRWFNYECXh+GqRH8yZ\
184        DArFuQy+BhFkaclY0BzpVlc4AAAABWExQRwAAAADHEkyvFBA7H2trEObeUg2wu+SyLu2PUE\
185        JBfNZO2qKBuAAAAAANpQv+f/////////8AAAABAAAAAQAAAAARQQaGAAAAAAbK5N8AAAAAA\
186        AAAAAAAAAA=";
187
188    fn binary_public(public: &str) -> Uint256 {
189        PublicKey::from_encoding(public).unwrap().into_binary()
190    }
191
192    #[test]
193    fn xdr_encode_decode_transaction_envelope() {
194        let envelope = TransactionEnvelope::from_base64_xdr(ENVELOPE).unwrap();
195        assert_eq!(ENVELOPE, &envelope.to_base64_xdr()[..]);
196
197        let meta = TransactionMeta::from_base64_xdr(META).unwrap();
198        assert_eq!(META, &meta.to_base64_xdr()[..]);
199    }
200
201    #[test]
202    fn decode_complex_transaction() {
203        let envelope = "AAAAAgAAAAAH0lW2BMK5GhjjJ6rrG4xbz7f80vEjTkNnIN8\
204        9rLn0sgAABdwCIrMOAAEgvAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAA\
205        AAwAAAAFVU0RUAAAAAKEzZhDpuPmzRl/ENBVUCh5Bd0GLgQQB9u7uGKat9lNZAAAAAUx\
206        UQwAAAAAAurdHYVeUhyvmUCg+kMeAmFcY6Zi+jZFh958xlu6ItVYAAAAAdRFqtAAAMc8\
207        AHoSAAAAAACZVXrUAAAAAAAAAAwAAAAFVU0RUAAAAAKEzZhDpuPmzRl/ENBVUCh5Bd0G\
208        LgQQB9u7uGKat9lNZAAAAAUxUQwAAAAAAurdHYVeUhyvmUCg+kMeAmFcY6Zi+jZFh958\
209        xlu6ItVYAAAAAjDnTwAAA+Q8AmJaAAAAAACZcaYkAAAAAAAAAAwAAAAFVU0RUAAAAAKE\
210        zZhDpuPmzRl/ENBVUCh5Bd0GLgQQB9u7uGKat9lNZAAAAAUxUQwAAAAAAurdHYVeUhyv\
211        mUCg+kMeAmFcY6Zi+jZFh958xlu6ItVYAAAAAjDeJ0AAA+RMAmJaAAAAAACZeXm4AAAA\
212        AAAAAAwAAAAFVU0RUAAAAAKEzZhDpuPmzRl/ENBVUCh5Bd0GLgQQB9u7uGKat9lNZAAA\
213        AAUxUQwAAAAAAurdHYVeUhyvmUCg+kMeAmFcY6Zi+jZFh958xlu6ItVYAAAAAjDU/4AA\
214        A+RcAmJaAAAAAACZmh6EAAAAAAAAAAwAAAAFVU0RUAAAAAKEzZhDpuPmzRl/ENBVUCh5\
215        Bd0GLgQQB9u7uGKat9lNZAAAAAUxUQwAAAAAAurdHYVeUhyvmUCg+kMeAmFcY6Zi+jZF\
216        h958xlu6ItVYAAAAAjDL18AAA+RsAmJaAAAAAACZxT/UAAAAAAAAAAwAAAAFMVEMAAAA\
217        AALq3R2FXlIcr5lAoPpDHgJhXGOmYvo2RYfefMZbuiLVWAAAAAVVTRFQAAAAAoTNmEOm\
218        4+bNGX8Q0FVQKHkF3QYuBBAH27u4Ypq32U1kAAAAAABd0sAAAPjkAAABkAAAAACZbMjU\
219        AAAAAAAAAAwAAAAFMVEMAAAAAALq3R2FXlIcr5lAoPpDHgJhXGOmYvo2RYfefMZbuiLV\
220        WAAAAAVVTRFQAAAAAoTNmEOm4+bNGX8Q0FVQKHkF3QYuBBAH27u4Ypq32U1kAAAAAAHA\
221        x4AAAPjsAAABkAAAAACZbrG4AAAAAAAAAAwAAAAFMVEMAAAAAALq3R2FXlIcr5lAoPpD\
222        HgJhXGOmYvo2RYfefMZbuiLVWAAAAAVVTRFQAAAAAoTNmEOm4+bNGX8Q0FVQKHkF3QYu\
223        BBAH27u4Ypq32U1kAAAAAAOThwAAAD48AAAAZAAAAACZbrG8AAAAAAAAAAwAAAAFMVEM\
224        AAAAAALq3R2FXlIcr5lAoPpDHgJhXGOmYvo2RYfefMZbuiLVWAAAAAVVTRFQAAAAAoTN\
225        mEOm4+bNGX8Q0FVQKHkF3QYuBBAH27u4Ypq32U1kAAAAAAOThwAAAPj0AAABkAAAAACZ\
226        j93QAAAAAAAAAAwAAAAFMVEMAAAAAALq3R2FXlIcr5lAoPpDHgJhXGOmYvo2RYfefMZb\
227        uiLVWAAAAAVVTRFQAAAAAoTNmEOm4+bNGX8Q0FVQKHkF3QYuBBAH27u4Ypq32U1kAAAA\
228        AAOThwAAAHx8AAAAyAAAAACZj93UAAAAAAAAAAwAAAAFMVEMAAAAAALq3R2FXlIcr5lA\
229        oPpDHgJhXGOmYvo2RYfefMZbuiLVWAAAAAVVTRFQAAAAAoTNmEOm4+bNGX8Q0FVQKHkF\
230        3QYuBBAH27u4Ypq32U1kAAAAAAOThXAAADHMAAAAUAAAAACZj93YAAAAAAAAAAwAAAAF\
231        MVEMAAAAAALq3R2FXlIcr5lAoPpDHgJhXGOmYvo2RYfefMZbuiLVWAAAAAVVTRFQAAAA\
232        AoTNmEOm4+bNGX8Q0FVQKHkF3QYuBBAH27u4Ypq32U1kAAAAAAOThXAAAD5AAAAAZAAA\
233        AACZqElgAAAAAAAAAAwAAAAFMVEMAAAAAALq3R2FXlIcr5lAoPpDHgJhXGOmYvo2RYfe\
234        fMZbuiLVWAAAAAVVTRFQAAAAAoTNmEOm4+bNGX8Q0FVQKHkF3QYuBBAH27u4Ypq32U1k\
235        AAAAAAOThwAAAPkEAAABkAAAAACZqao4AAAAAAAAAAwAAAAFMVEMAAAAAALq3R2FXlIc\
236        r5lAoPpDHgJhXGOmYvo2RYfefMZbuiLVWAAAAAVVTRFQAAAAAoTNmEOm4+bNGX8Q0FVQ\
237        KHkF3QYuBBAH27u4Ypq32U1kAAAAAAOThwAAAHyEAAAAyAAAAACZqbFIAAAAAAAAAAwA\
238        AAAFMVEMAAAAAALq3R2FXlIcr5lAoPpDHgJhXGOmYvo2RYfefMZbuiLVWAAAAAVVTRFQ\
239        AAAAAoTNmEOm4+bNGX8Q0FVQKHkF3QYuBBAH27u4Ypq32U1kAAAAAAOThXAAAPkMAAAB\
240        kAAAAACZw6OsAAAAAAAAAAay59LIAAABAdMmVEkeQO1ygJEUCpGk5qUzfhHWUD3qikrA\
241        7ZXRpe2n5JsRoJot88+Fc+ayFPJoIsKsP457TwyzTorPwuUGxBQ==";
242
243        let envelope = TransactionEnvelope::from_base64_xdr(envelope);
244        assert!(envelope.is_ok());
245        let envelope = match envelope.unwrap() {
246            TransactionEnvelope::EnvelopeTypeTx(envelope) => envelope,
247            _ => unreachable!(),
248        };
249
250        let transaction = envelope.tx;
251
252        let expected_transaction = Transaction {
253            source_account: MuxedAccount::KeyTypeEd25519(binary_public(
254                "GAD5EVNWATBLSGQY4MT2V2Y3RRN47N742LYSGTSDM4QN6PNMXH2LF7WV",
255            )),
256            fee: 1500,
257            seq_num: 153882209995006140,
258            cond: Preconditions::PrecondTime(TimeBounds { min_time: 0, max_time: 0 }),
259            memo: Memo::MemoNone,
260            operations: LimitedVarArray::new(vec![
261                Operation {
262                    source_account: None,
263                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
264                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
265                            asset_code: b"USDT".clone(),
266                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
267                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
268                            )),
269                        }),
270                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
271                            asset_code: b"LTC\0".clone(),
272                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
273                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
274                            )),
275                        }),
276                        amount: 1964075700,
277                        price: Price { n: 12751, d: 2000000 },
278                        offer_id: 643129013,
279                    }),
280                },
281                Operation {
282                    source_account: None,
283                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
284                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
285                            asset_code: b"USDT".clone(),
286                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
287                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
288                            )),
289                        }),
290                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
291                            asset_code: b"LTC\0".clone(),
292                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
293                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
294                            )),
295                        }),
296                        amount: 2352600000,
297                        price: Price { n: 63759, d: 10000000 },
298                        offer_id: 643590537,
299                    }),
300                },
301                Operation {
302                    source_account: None,
303                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
304                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
305                            asset_code: b"USDT".clone(),
306                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
307                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
308                            )),
309                        }),
310                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
311                            asset_code: b"LTC\0".clone(),
312                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
313                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
314                            )),
315                        }),
316                        amount: 2352450000,
317                        price: Price { n: 63763, d: 10000000 },
318                        offer_id: 643718766,
319                    }),
320                },
321                Operation {
322                    source_account: None,
323                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
324                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
325                            asset_code: b"USDT".clone(),
326                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
327                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
328                            )),
329                        }),
330                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
331                            asset_code: b"LTC\0".clone(),
332                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
333                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
334                            )),
335                        }),
336                        amount: 2352300000,
337                        price: Price { n: 63767, d: 10000000 },
338                        offer_id: 644253601,
339                    }),
340                },
341                Operation {
342                    source_account: None,
343                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
344                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
345                            asset_code: b"USDT".clone(),
346                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
347                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
348                            )),
349                        }),
350                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
351                            asset_code: b"LTC\0".clone(),
352                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
353                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
354                            )),
355                        }),
356                        amount: 2352150000,
357                        price: Price { n: 63771, d: 10000000 },
358                        offer_id: 644960245,
359                    }),
360                },
361                Operation {
362                    source_account: None,
363                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
364                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
365                            asset_code: b"LTC\0".clone(),
366                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
367                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
368                            )),
369                        }),
370                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
371                            asset_code: b"USDT".clone(),
372                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
373                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
374                            )),
375                        }),
376                        amount: 1537200,
377                        price: Price { n: 15929, d: 100 },
378                        offer_id: 643510837,
379                    }),
380                },
381                Operation {
382                    source_account: None,
383                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
384                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
385                            asset_code: b"LTC\0".clone(),
386                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
387                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
388                            )),
389                        }),
390                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
391                            asset_code: b"USDT".clone(),
392                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
393                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
394                            )),
395                        }),
396                        amount: 7352800,
397                        price: Price { n: 15931, d: 100 },
398                        offer_id: 643542126,
399                    }),
400                },
401                Operation {
402                    source_account: None,
403                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
404                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
405                            asset_code: b"LTC\0".clone(),
406                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
407                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
408                            )),
409                        }),
410                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
411                            asset_code: b"USDT".clone(),
412                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
413                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
414                            )),
415                        }),
416                        amount: 15000000,
417                        price: Price { n: 3983, d: 25 },
418                        offer_id: 643542127,
419                    }),
420                },
421                Operation {
422                    source_account: None,
423                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
424                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
425                            asset_code: b"LTC\0".clone(),
426                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
427                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
428                            )),
429                        }),
430                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
431                            asset_code: b"USDT".clone(),
432                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
433                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
434                            )),
435                        }),
436                        amount: 15000000,
437                        price: Price { n: 15933, d: 100 },
438                        offer_id: 644085620,
439                    }),
440                },
441                Operation {
442                    source_account: None,
443                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
444                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
445                            asset_code: b"LTC\0".clone(),
446                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
447                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
448                            )),
449                        }),
450                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
451                            asset_code: b"USDT".clone(),
452                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
453                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
454                            )),
455                        }),
456                        amount: 15000000,
457                        price: Price { n: 7967, d: 50 },
458                        offer_id: 644085621,
459                    }),
460                },
461                Operation {
462                    source_account: None,
463                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
464                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
465                            asset_code: b"LTC\0".clone(),
466                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
467                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
468                            )),
469                        }),
470                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
471                            asset_code: b"USDT".clone(),
472                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
473                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
474                            )),
475                        }),
476                        amount: 14999900,
477                        price: Price { n: 3187, d: 20 },
478                        offer_id: 644085622,
479                    }),
480                },
481                Operation {
482                    source_account: None,
483                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
484                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
485                            asset_code: b"LTC\0".clone(),
486                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
487                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
488                            )),
489                        }),
490                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
491                            asset_code: b"USDT".clone(),
492                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
493                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
494                            )),
495                        }),
496                        amount: 14999900,
497                        price: Price { n: 3984, d: 25 },
498                        offer_id: 644485720,
499                    }),
500                },
501                Operation {
502                    source_account: None,
503                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
504                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
505                            asset_code: b"LTC\0".clone(),
506                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
507                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
508                            )),
509                        }),
510                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
511                            asset_code: b"USDT".clone(),
512                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
513                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
514                            )),
515                        }),
516                        amount: 15000000,
517                        price: Price { n: 15937, d: 100 },
518                        offer_id: 644508302,
519                    }),
520                },
521                Operation {
522                    source_account: None,
523                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
524                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
525                            asset_code: b"LTC\0".clone(),
526                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
527                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
528                            )),
529                        }),
530                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
531                            asset_code: b"USDT".clone(),
532                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
533                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
534                            )),
535                        }),
536                        amount: 15000000,
537                        price: Price { n: 7969, d: 50 },
538                        offer_id: 644508754,
539                    }),
540                },
541                Operation {
542                    source_account: None,
543                    body: OperationBody::ManageSellOffer(ManageSellOfferOp {
544                        selling: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
545                            asset_code: b"LTC\0".clone(),
546                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
547                                "GC5LOR3BK6KIOK7GKAUD5EGHQCMFOGHJTC7I3ELB66PTDFXORC2VM5LP",
548                            )),
549                        }),
550                        buying: Asset::AssetTypeCreditAlphanum4(AlphaNum4 {
551                            asset_code: b"USDT".clone(),
552                            issuer: PublicKey::PublicKeyTypeEd25519(binary_public(
553                                "GCQTGZQQ5G4PTM2GL7CDIFKUBIPEC52BROAQIAPW53XBRJVN6ZJVTG6V",
554                            )),
555                        }),
556                        amount: 14999900,
557                        price: Price { n: 15939, d: 100 },
558                        offer_id: 644933867,
559                    }),
560                },
561            ])
562            .unwrap(),
563            ext: TransactionExt::V0,
564        };
565
566        assert_eq!(transaction, expected_transaction);
567    }
568
569    #[test]
570    fn sign_simple_transaction() {
571        let secret = "SCDSVACTNFNSD5LQZ5LWUWEY3UIAML2J7ALPFCD6ZX4D3TVJV7X243N3";
572        let keypair = SecretKey::from_encoding(secret);
573        assert!(keypair.is_ok());
574        let keypair = keypair.unwrap();
575
576        let dest_public = PublicKey::from_encoding("GDMTKCJQ322RDTGOBLIPVEUCO3EIEJLXDV4JTWLXU6AFOYTMSJ45WZY5").unwrap();
577
578        let mut transaction_envelope = TransactionEnvelope::EnvelopeTypeTx(TransactionV1Envelope {
579            tx: Transaction {
580                source_account: MuxedAccount::KeyTypeEd25519(keypair.get_public().clone().into_binary()),
581                fee: 10000,
582                seq_num: 59481002082305,
583                cond: Preconditions::PrecondTime(TimeBounds { min_time: 0, max_time: 0 }),
584                memo: Memo::MemoNone,
585                operations: LimitedVarArray::new(vec![Operation {
586                    source_account: None,
587                    body: OperationBody::Payment(PaymentOp {
588                        destination: MuxedAccount::KeyTypeEd25519(dest_public.into_binary()),
589                        asset: Asset::AssetTypeNative,
590                        amount: 10_000_000,
591                    }),
592                }])
593                .unwrap(),
594                ext: TransactionExt::V0,
595            },
596            signatures: LimitedVarArray::new(Vec::new()).unwrap(),
597        });
598
599        let expected_xdr = b"AAAAAgAAAABRVWJF9F/Kd+p+e65fn2mnDGH\
600        5BnlL9yXAMBTaJbnUcQAAJxAAADYZAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAA\
601        AAAAAAAAAAAEAAAAAAAAAAQAAAADZNQkw3rURzM4K0PqSgnbIgiV3HXiZ2Xe\
602        ngFdibJJ52wAAAAAAAAAAAJiWgAAAAAAAAAAA";
603        assert_eq!(transaction_envelope.to_base64_xdr().as_slice(), &expected_xdr[..]);
604
605        let signing_result = transaction_envelope.sign(&TEST_NETWORK, vec![&keypair]);
606        assert!(signing_result.is_ok());
607
608        let expected_signed_xdr = b"AAAAAgAAAABRVWJF9F/Kd+p+e65\
609        fn2mnDGH5BnlL9yXAMBTaJbnUcQAAJxAAADYZAAAAAQAAAAEAAAAAAAAAAA\
610        AAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADZNQkw3rURzM4K0PqSgnbIg\
611        iV3HXiZ2XengFdibJJ52wAAAAAAAAAAAJiWgAAAAAAAAAABJbnUcQAAAEAv\
612        CLQxbuE/zeBYq5Q/17d1hvcQME5uHUJ9SE8L8E/PQHa00jfGpFrtsG+XQV0\
613        DI0AnnqQhBhHKl1l5LNpIoxIA";
614
615        assert_eq!(transaction_envelope.to_base64_xdr().as_slice(), &expected_signed_xdr[..]);
616    }
617}