ic_web3_rs/api/
accounts.rs

1//! Partial implementation of the `Accounts` namespace.
2
3use crate::ic::{ic_raw_sign, recover_address, KeyInfo};
4use crate::{api::Namespace, signing, types::H256, Transport};
5
6/// `Accounts` namespace
7#[derive(Debug, Clone)]
8pub struct Accounts<T> {
9    transport: T,
10}
11
12impl<T: Transport> Namespace<T> for Accounts<T> {
13    fn new(transport: T) -> Self
14    where
15        Self: Sized,
16    {
17        Accounts { transport }
18    }
19
20    fn transport(&self) -> &T {
21        &self.transport
22    }
23}
24
25impl<T: Transport> Accounts<T> {
26    /// Hash a message according to EIP-191.
27    ///
28    /// The data is a UTF-8 encoded string and will enveloped as follows:
29    /// `"\x19Ethereum Signed Message:\n" + message.length + message` and hashed
30    /// using keccak256.
31    pub fn hash_message<S>(&self, message: S) -> H256
32    where
33        S: AsRef<[u8]>,
34    {
35        signing::hash_message(message)
36    }
37}
38
39// #[cfg(feature = "signing")]
40mod accounts_signing {
41    use super::*;
42    use crate::{
43        api::Web3,
44        error,
45        signing::Signature,
46        types::{
47            AccessList, Address, Bytes, Recovery, RecoveryMessage, SignedData, SignedTransaction,
48            TransactionParameters, U256, U64,
49        },
50    };
51    use rlp::RlpStream;
52    // use std::convert::TryInto;
53
54    const LEGACY_TX_ID: u64 = 0;
55    const ACCESSLISTS_TX_ID: u64 = 1;
56    const EIP1559_TX_ID: u64 = 2;
57
58    impl<T: Transport> Accounts<T> {
59        /// Gets the parent `web3` namespace
60        fn web3(&self) -> Web3<T> {
61            Web3::new(self.transport.clone())
62        }
63
64        // Signs an Ethereum transaction with a given private key.
65        //
66        // Transaction signing can perform RPC requests in order to fill missing
67        // parameters required for signing `nonce`, `gas_price` and `chain_id`. Note
68        // that if all transaction parameters were provided, this future will resolve
69        // immediately.
70        // pub async fn sign_transaction<K: signing::Key>(
71        //     &self,
72        //     tx: TransactionParameters,
73        //     key: K,
74        // ) -> error::Result<SignedTransaction> {
75        //     macro_rules! maybe {
76        //         ($o: expr, $f: expr) => {
77        //             async {
78        //                 match $o {
79        //                     Some(value) => Ok(value),
80        //                     None => $f.await,
81        //                 }
82        //             }
83        //         };
84        //     }
85        //     let from = key.address();
86
87        //     let gas_price = match tx.transaction_type {
88        //         Some(tx_type) if tx_type == U64::from(EIP1559_TX_ID) && tx.max_fee_per_gas.is_some() => {
89        //             tx.max_fee_per_gas
90        //         }
91        //         _ => tx.gas_price,
92        //     };
93
94        //     let (nonce, gas_price, chain_id) = futures::future::try_join3(
95        //         maybe!(tx.nonce, self.web3().eth().transaction_count(from, None)),
96        //         maybe!(gas_price, self.web3().eth().gas_price()),
97        //         maybe!(tx.chain_id.map(U256::from), self.web3().eth().chain_id()),
98        //     )
99        //     .await?;
100        //     let chain_id = chain_id.as_u64();
101
102        //     let max_priority_fee_per_gas = match tx.transaction_type {
103        //         Some(tx_type) if tx_type == U64::from(EIP1559_TX_ID) => {
104        //             tx.max_priority_fee_per_gas.unwrap_or(gas_price)
105        //         }
106        //         _ => gas_price,
107        //     };
108
109        //     let tx = Transaction {
110        //         to: tx.to,
111        //         nonce,
112        //         gas: tx.gas,
113        //         gas_price,
114        //         value: tx.value,
115        //         data: tx.data.0,
116        //         transaction_type: tx.transaction_type,
117        //         access_list: tx.access_list.unwrap_or_default(),
118        //         max_priority_fee_per_gas,
119        //     };
120
121        //     let signed = tx.sign(key, chain_id);
122        //     Ok(signed)
123        // }
124        pub async fn sign_transaction(
125            &self,
126            tx: TransactionParameters,
127            from: String,
128            key_info: KeyInfo,
129            chain_id: u64,
130        ) -> error::Result<SignedTransaction> {
131            let gas_price = match tx.transaction_type {
132                Some(tx_type) if tx_type == U64::from(EIP1559_TX_ID) && tx.max_fee_per_gas.is_some() => {
133                    tx.max_fee_per_gas.unwrap()
134                }
135                _ => tx.gas_price.unwrap(),
136            };
137
138            let max_priority_fee_per_gas = match tx.transaction_type {
139                Some(tx_type) if tx_type == U64::from(EIP1559_TX_ID) => {
140                    tx.max_priority_fee_per_gas.unwrap_or(gas_price)
141                }
142                _ => gas_price,
143            };
144
145            let tx = Transaction {
146                to: tx.to,
147                nonce: tx.nonce.unwrap(),
148                gas: tx.gas,
149                gas_price,
150                value: tx.value,
151                data: tx.data.0,
152                transaction_type: tx.transaction_type,
153                access_list: tx.access_list.unwrap_or_default(),
154                max_priority_fee_per_gas,
155            };
156
157            let signed = tx.sign(from, key_info, chain_id).await;
158            Ok(signed)
159        }
160
161        // Sign arbitrary string data.
162        //
163        // The data is UTF-8 encoded and enveloped the same way as with
164        // `hash_message`. The returned signed data's signature is in 'Electrum'
165        // notation, that is the recovery value `v` is either `27` or `28` (as
166        // opposed to the standard notation where `v` is either `0` or `1`). This
167        // is important to consider when using this signature with other crates.
168        // pub fn sign<S>(&self, message: S, key: impl signing::Key) -> SignedData
169        // where
170        //     S: AsRef<[u8]>,
171        // {
172        //     let message = message.as_ref();
173        //     let message_hash = self.hash_message(message);
174
175        //     let signature = key
176        //         .sign(message_hash.as_bytes(), None)
177        //         .expect("hash is non-zero 32-bytes; qed");
178        //     let v = signature
179        //         .v
180        //         .try_into()
181        //         .expect("signature recovery in electrum notation always fits in a u8");
182
183        //     let signature_bytes = Bytes({
184        //         let mut bytes = Vec::with_capacity(65);
185        //         bytes.extend_from_slice(signature.r.as_bytes());
186        //         bytes.extend_from_slice(signature.s.as_bytes());
187        //         bytes.push(v);
188        //         bytes
189        //     });
190
191        //     // We perform this allocation only after all previous fallible actions have completed successfully.
192        //     let message = message.to_owned();
193
194        //     SignedData {
195        //         message,
196        //         message_hash,
197        //         v,
198        //         r: signature.r,
199        //         s: signature.s,
200        //         signature: signature_bytes,
201        //     }
202        // }
203
204        // Recovers the Ethereum address which was used to sign the given data.
205        //
206        // Recovery signature data uses 'Electrum' notation, this means the `v`
207        // value is expected to be either `27` or `28`.
208        // pub fn recover<R>(&self, recovery: R) -> error::Result<Address>
209        // where
210        //     R: Into<Recovery>,
211        // {
212        //     let recovery = recovery.into();
213        //     let message_hash = match recovery.message {
214        //         RecoveryMessage::Data(ref message) => self.hash_message(message),
215        //         RecoveryMessage::Hash(hash) => hash,
216        //     };
217        //     let (signature, recovery_id) = recovery
218        //         .as_signature()
219        //         .ok_or(error::Error::Recovery(signing::RecoveryError::InvalidSignature))?;
220        //     let address = signing::recover(message_hash.as_bytes(), &signature, recovery_id)?;
221        //     Ok(address)
222        // }
223    }
224    /// A transaction used for RLP encoding, hashing and signing.
225    #[derive(Debug)]
226    pub struct Transaction {
227        pub to: Option<Address>,
228        pub nonce: U256,
229        pub gas: U256,
230        pub gas_price: U256,
231        pub value: U256,
232        pub data: Vec<u8>,
233        pub transaction_type: Option<U64>,
234        pub access_list: AccessList,
235        pub max_priority_fee_per_gas: U256,
236    }
237
238    impl Transaction {
239        fn rlp_append_legacy(&self, stream: &mut RlpStream) {
240            stream.append(&self.nonce);
241            stream.append(&self.gas_price);
242            stream.append(&self.gas);
243            if let Some(to) = self.to {
244                stream.append(&to);
245            } else {
246                stream.append(&"");
247            }
248            stream.append(&self.value);
249            stream.append(&self.data);
250        }
251
252        fn encode_legacy(&self, chain_id: u64, signature: Option<&Signature>) -> RlpStream {
253            let mut stream = RlpStream::new();
254            stream.begin_list(9);
255
256            self.rlp_append_legacy(&mut stream);
257
258            if let Some(signature) = signature {
259                self.rlp_append_signature(&mut stream, signature);
260            } else {
261                stream.append(&chain_id);
262                stream.append(&0u8);
263                stream.append(&0u8);
264            }
265
266            stream
267        }
268
269        fn encode_access_list_payload(&self, chain_id: u64, signature: Option<&Signature>) -> RlpStream {
270            let mut stream = RlpStream::new();
271
272            let list_size = if signature.is_some() { 11 } else { 8 };
273            stream.begin_list(list_size);
274
275            // append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size.
276            stream.append(&chain_id);
277
278            self.rlp_append_legacy(&mut stream);
279            self.rlp_append_access_list(&mut stream);
280
281            if let Some(signature) = signature {
282                self.rlp_append_signature(&mut stream, signature);
283            }
284
285            stream
286        }
287
288        fn encode_eip1559_payload(&self, chain_id: u64, signature: Option<&Signature>) -> RlpStream {
289            let mut stream = RlpStream::new();
290
291            let list_size = if signature.is_some() { 12 } else { 9 };
292            stream.begin_list(list_size);
293
294            // append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size.
295            stream.append(&chain_id);
296
297            stream.append(&self.nonce);
298            stream.append(&self.max_priority_fee_per_gas);
299            stream.append(&self.gas_price);
300            stream.append(&self.gas);
301            if let Some(to) = self.to {
302                stream.append(&to);
303            } else {
304                stream.append(&"");
305            }
306            stream.append(&self.value);
307            stream.append(&self.data);
308
309            self.rlp_append_access_list(&mut stream);
310
311            if let Some(signature) = signature {
312                self.rlp_append_signature(&mut stream, signature);
313            }
314
315            stream
316        }
317
318        fn rlp_append_signature(&self, stream: &mut RlpStream, signature: &Signature) {
319            stream.append(&signature.v);
320            stream.append(&U256::from_big_endian(signature.r.as_bytes()));
321            stream.append(&U256::from_big_endian(signature.s.as_bytes()));
322        }
323
324        fn rlp_append_access_list(&self, stream: &mut RlpStream) {
325            stream.begin_list(self.access_list.len());
326            for access in self.access_list.iter() {
327                stream.begin_list(2);
328                stream.append(&access.address);
329                stream.begin_list(access.storage_keys.len());
330                for storage_key in access.storage_keys.iter() {
331                    stream.append(storage_key);
332                }
333            }
334        }
335
336        fn encode(&self, chain_id: u64, signature: Option<&Signature>) -> Vec<u8> {
337            match self.transaction_type.map(|t| t.as_u64()) {
338                Some(LEGACY_TX_ID) | None => {
339                    let stream = self.encode_legacy(chain_id, signature);
340                    stream.out().to_vec()
341                }
342
343                Some(ACCESSLISTS_TX_ID) => {
344                    let tx_id: u8 = ACCESSLISTS_TX_ID as u8;
345                    let stream = self.encode_access_list_payload(chain_id, signature);
346                    [&[tx_id], stream.as_raw()].concat()
347                }
348
349                Some(EIP1559_TX_ID) => {
350                    let tx_id: u8 = EIP1559_TX_ID as u8;
351                    let stream = self.encode_eip1559_payload(chain_id, signature);
352                    [&[tx_id], stream.as_raw()].concat()
353                }
354
355                _ => {
356                    panic!("Unsupported transaction type");
357                }
358            }
359        }
360
361        /// Sign and return a raw signed transaction.
362        // pub fn sign(self, sign: impl signing::Key, chain_id: u64) -> SignedTransaction {
363        //     let adjust_v_value = matches!(self.transaction_type.map(|t| t.as_u64()), Some(LEGACY_TX_ID) | None);
364
365        //     let encoded = self.encode(chain_id, None);
366
367        //     let hash = signing::keccak256(encoded.as_ref());
368
369        //     let signature = if adjust_v_value {
370        //         sign.sign(&hash, Some(chain_id))
371        //             .expect("hash is non-zero 32-bytes; qed")
372        //     } else {
373        //         sign.sign_message(&hash).expect("hash is non-zero 32-bytes; qed")
374        //     };
375
376        //     let signed = self.encode(chain_id, Some(&signature));
377        //     let transaction_hash = signing::keccak256(signed.as_ref()).into();
378
379        //     SignedTransaction {
380        //         message_hash: hash.into(),
381        //         v: signature.v,
382        //         r: signature.r,
383        //         s: signature.s,
384        //         raw_transaction: signed.into(),
385        //         transaction_hash,
386        //     }
387        // }
388
389        pub async fn sign(self, from: String, key_info: KeyInfo, chain_id: u64) -> SignedTransaction {
390            let adjust_v_value = matches!(self.transaction_type.map(|t| t.as_u64()), Some(LEGACY_TX_ID) | None);
391
392            let encoded = self.encode(chain_id, None);
393
394            let hash = signing::keccak256(encoded.as_ref());
395
396            let res = match ic_raw_sign(hash.to_vec(), key_info).await {
397                Ok(v) => v,
398                Err(e) => {
399                    panic!("{}", e);
400                }
401            };
402
403            let v = if recover_address(hash.clone().to_vec(), res.clone(), 0) == from {
404                if adjust_v_value {
405                    2 * chain_id + 35 + 0
406                } else {
407                    0
408                }
409            } else {
410                if adjust_v_value {
411                    2 * chain_id + 35 + 1
412                } else {
413                    1
414                }
415            };
416
417            let r_arr = H256::from_slice(&res[0..32]);
418            let s_arr = H256::from_slice(&res[32..64]);
419            let sig = Signature {
420                v: v.clone(),
421                r: r_arr.clone().into(),
422                s: s_arr.clone().into(),
423            };
424
425            let signed = self.encode(chain_id, Some(&sig));
426            let transaction_hash = signing::keccak256(signed.as_ref()).into();
427
428            SignedTransaction {
429                message_hash: hash.into(),
430                v,
431                r: r_arr.into(),
432                s: s_arr.into(),
433                raw_transaction: signed.into(),
434                transaction_hash,
435            }
436        }
437    }
438}
439
440#[cfg(all(test, not(target_arch = "wasm32")))]
441mod tests {
442    //use super::*;
443    //use crate::{
444    //    signing::{SecretKey, SecretKeyRef},
445    //    transports::test::TestTransport,
446    //    types::{Address, Recovery, SignedTransaction, TransactionParameters, U256},
447    //};
448    //use accounts_signing::*;
449    //use hex_literal::hex;
450    //use serde_json::json;
451    //
452    //#[test]
453    //fn accounts_sign_transaction() {
454    //    // retrieved test vector from:
455    //    // https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
456    //
457    //    let tx = TransactionParameters {
458    //        to: Some(hex!("F0109fC8DF283027b6285cc889F5aA624EaC1F55").into()),
459    //        value: 1_000_000_000.into(),
460    //        gas: 2_000_000.into(),
461    //        ..Default::default()
462    //    };
463    //    let key = SecretKey::from_slice(&hex!(
464    //        "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
465    //    ))
466    //    .unwrap();
467    //    let nonce = U256::zero();
468    //    let gas_price = U256::from(21_000_000_000u128);
469    //    let chain_id = "0x1";
470    //    let from: Address = signing::secret_key_address(&key);
471    //
472    //    let mut transport = TestTransport::default();
473    //    transport.add_response(json!(nonce));
474    //    transport.add_response(json!(gas_price));
475    //    transport.add_response(json!(chain_id));
476    //
477    //    let signed = {
478    //        let accounts = Accounts::new(&transport);
479    //        futures::executor::block_on(accounts.sign_transaction(tx, &key))
480    //    };
481    //
482    //    transport.assert_request(
483    //        "eth_getTransactionCount",
484    //        &[json!(from).to_string(), json!("latest").to_string()],
485    //    );
486    //    transport.assert_request("eth_gasPrice", &[]);
487    //    transport.assert_request("eth_chainId", &[]);
488    //    transport.assert_no_more_requests();
489    //
490    //    let expected = SignedTransaction {
491    //        message_hash: hex!("88cfbd7e51c7a40540b233cf68b62ad1df3e92462f1c6018d6d67eae0f3b08f5").into(),
492    //        v: 0x25,
493    //        r: hex!("c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895").into(),
494    //        s: hex!("727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68").into(),
495    //        raw_transaction: hex!("f869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68").into(),
496    //        transaction_hash: hex!("de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593").into(),
497    //    };
498    //
499    //    assert_eq!(signed, Ok(expected));
500    //}
501    //
502    //#[test]
503    //fn accounts_sign_transaction_with_all_parameters() {
504    //    let key = SecretKey::from_slice(&hex!(
505    //        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
506    //    ))
507    //    .unwrap();
508    //
509    //    let accounts = Accounts::new(TestTransport::default());
510    //    futures::executor::block_on(accounts.sign_transaction(
511    //        TransactionParameters {
512    //            nonce: Some(0.into()),
513    //            gas_price: Some(1.into()),
514    //            chain_id: Some(42),
515    //            ..Default::default()
516    //        },
517    //        &key,
518    //    ))
519    //    .unwrap();
520    //
521    //    // sign_transaction makes no requests when all parameters are specified
522    //    accounts.transport().assert_no_more_requests();
523    //}
524    //
525    //#[test]
526    //fn accounts_hash_message() {
527    //    // test vector taken from:
528    //    // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#hashmessage
529    //
530    //    let accounts = Accounts::new(TestTransport::default());
531    //    let hash = accounts.hash_message("Hello World");
532    //
533    //    assert_eq!(
534    //        hash,
535    //        hex!("a1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2").into()
536    //    );
537    //
538    //    // this method does not actually make any requests.
539    //    accounts.transport().assert_no_more_requests();
540    //}
541    //
542    //#[test]
543    //fn accounts_sign() {
544    //    // test vector taken from:
545    //    // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#sign
546    //
547    //    let accounts = Accounts::new(TestTransport::default());
548    //
549    //    let key = SecretKey::from_slice(&hex!(
550    //        "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
551    //    ))
552    //    .unwrap();
553    //    let signed = accounts.sign("Some data", SecretKeyRef::new(&key));
554    //
555    //    assert_eq!(
556    //        signed.message_hash,
557    //        hex!("1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655").into()
558    //    );
559    //    assert_eq!(
560    //        signed.signature.0,
561    //        hex!("b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c")
562    //    );
563    //
564    //    // this method does not actually make any requests.
565    //    accounts.transport().assert_no_more_requests();
566    //}
567    //
568    //#[test]
569    //fn accounts_recover() {
570    //    // test vector taken from:
571    //    // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#recover
572    //
573    //    let accounts = Accounts::new(TestTransport::default());
574    //
575    //    let v = 0x1cu64;
576    //    let r = hex!("b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd").into();
577    //    let s = hex!("6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029").into();
578    //
579    //    let recovery = Recovery::new("Some data", v, r, s);
580    //    assert_eq!(
581    //        accounts.recover(recovery).unwrap(),
582    //        hex!("2c7536E3605D9C16a7a3D7b1898e529396a65c23").into()
583    //    );
584    //
585    //    // this method does not actually make any requests.
586    //    accounts.transport().assert_no_more_requests();
587    //}
588    //
589    //#[test]
590    //fn accounts_recover_signed() {
591    //    let key = SecretKey::from_slice(&hex!(
592    //        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
593    //    ))
594    //    .unwrap();
595    //    let address: Address = signing::secret_key_address(&key);
596    //
597    //    let accounts = Accounts::new(TestTransport::default());
598    //
599    //    let signed = accounts.sign("rust-web3 rocks!", &key);
600    //    let recovered = accounts.recover(&signed).unwrap();
601    //    assert_eq!(recovered, address);
602    //
603    //    let signed = futures::executor::block_on(accounts.sign_transaction(
604    //        TransactionParameters {
605    //            nonce: Some(0.into()),
606    //            gas_price: Some(1.into()),
607    //            chain_id: Some(42),
608    //            ..Default::default()
609    //        },
610    //        &key,
611    //        KeyInfo {
612    //            derivation_path: vec![],
613    //            key_name: "".to_string(),
614    //            ecdsa_sign_cycles: None,
615    //        },
616    //        0,
617    //    ))
618    //    .unwrap();
619    //    let recovered = accounts.recover(&signed).unwrap();
620    //    assert_eq!(recovered, address);
621    //
622    //    // these methods make no requests
623    //    accounts.transport().assert_no_more_requests();
624    //}
625    //
626    //#[test]
627    //fn sign_transaction_data() {
628    //    // retrieved test vector from:
629    //    // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#eth-accounts-signtransaction
630    //
631    //    let tx = Transaction {
632    //        nonce: 0.into(),
633    //        gas: 2_000_000.into(),
634    //        gas_price: 234_567_897_654_321u64.into(),
635    //        to: Some(hex!("F0109fC8DF283027b6285cc889F5aA624EaC1F55").into()),
636    //        value: 1_000_000_000.into(),
637    //        data: Vec::new(),
638    //        transaction_type: None,
639    //        access_list: vec![],
640    //        max_priority_fee_per_gas: 0.into(),
641    //    };
642    //    let skey = SecretKey::from_slice(&hex!(
643    //        "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
644    //    ))
645    //    .unwrap();
646    //    let key = SecretKeyRef::new(&skey);
647    //
648    //    let signed = tx.sign(key, 1).await;
649    //
650    //    let expected = SignedTransaction {
651    //        message_hash: hex!("6893a6ee8df79b0f5d64a180cd1ef35d030f3e296a5361cf04d02ce720d32ec5").into(),
652    //        v: 0x25,
653    //        r: hex!("09ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9c").into(),
654    //        s: hex!("440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428").into(),
655    //        raw_transaction: hex!("f86a8086d55698372431831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a009ebb6ca057a0535d6186462bc0b465b561c94a295bdb0621fc19208ab149a9ca0440ffd775ce91a833ab410777204d5341a6f9fa91216a6f3ee2c051fea6a0428").into(),
656    //        transaction_hash: hex!("d8f64a42b57be0d565f385378db2f6bf324ce14a594afc05de90436e9ce01f60").into(),
657    //    };
658    //
659    //    assert_eq!(signed, expected);
660    //}
661}