pallet_revive_eth_rpc/
example.rs1use crate::{EthRpcClient, ReceiptInfo};
19use anyhow::Context;
20use pallet_revive::evm::*;
21use std::sync::Arc;
22
23pub struct TransactionBuilder<Client: EthRpcClient + Sync + Send> {
25 client: Arc<Client>,
26 signer: Account,
27 value: U256,
28 input: Bytes,
29 to: Option<H160>,
30 nonce: Option<U256>,
31 mutate: Box<dyn FnOnce(&mut TransactionLegacyUnsigned)>,
32}
33
34#[derive(Debug)]
35pub struct SubmittedTransaction<Client: EthRpcClient + Sync + Send> {
36 tx: GenericTransaction,
37 hash: H256,
38 client: Arc<Client>,
39}
40
41impl<Client: EthRpcClient + Sync + Send> SubmittedTransaction<Client> {
42 pub fn hash(&self) -> H256 {
44 self.hash
45 }
46
47 pub fn gas(&self) -> U256 {
49 self.tx.gas.unwrap()
50 }
51
52 pub fn generic_transaction(&self) -> GenericTransaction {
53 self.tx.clone()
54 }
55
56 pub async fn wait_for_receipt(&self) -> anyhow::Result<ReceiptInfo> {
58 let hash = self.hash();
59 for _ in 0..30 {
60 tokio::time::sleep(std::time::Duration::from_secs(2)).await;
61 let receipt = self.client.get_transaction_receipt(hash).await?;
62 if let Some(receipt) = receipt {
63 if receipt.is_success() {
64 assert!(
65 self.gas() > receipt.gas_used,
66 "Gas used should be less than gas estimated."
67 );
68 return Ok(receipt);
69 } else {
70 anyhow::bail!("Transaction failed receipt: {receipt:?}")
71 }
72 }
73 }
74
75 anyhow::bail!("Timeout, failed to get receipt")
76 }
77}
78
79impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
80 pub fn new(client: &Arc<Client>) -> Self {
81 Self {
82 client: Arc::clone(client),
83 signer: Account::default(),
84 value: U256::zero(),
85 input: Bytes::default(),
86 to: None,
87 nonce: None,
88 mutate: Box::new(|_| {}),
89 }
90 }
91 pub fn signer(mut self, signer: Account) -> Self {
93 self.signer = signer;
94 self
95 }
96
97 pub fn value(mut self, value: U256) -> Self {
99 self.value = value;
100 self
101 }
102
103 pub fn input(mut self, input: Vec<u8>) -> Self {
105 self.input = Bytes(input);
106 self
107 }
108
109 pub fn to(mut self, to: H160) -> Self {
111 self.to = Some(to);
112 self
113 }
114
115 pub fn nonce(mut self, nonce: U256) -> Self {
117 self.nonce = Some(nonce);
118 self
119 }
120
121 pub fn mutate(mut self, mutate: impl FnOnce(&mut TransactionLegacyUnsigned) + 'static) -> Self {
123 self.mutate = Box::new(mutate);
124 self
125 }
126
127 pub async fn eth_call(self) -> anyhow::Result<Vec<u8>> {
129 let TransactionBuilder { client, signer, value, input, to, .. } = self;
130
131 let from = signer.address();
132 let result = client
133 .call(
134 GenericTransaction {
135 from: Some(from),
136 input: input.into(),
137 value: Some(value),
138 to,
139 ..Default::default()
140 },
141 None,
142 )
143 .await
144 .with_context(|| "eth_call failed")?;
145 Ok(result.0)
146 }
147
148 pub async fn send(self) -> anyhow::Result<SubmittedTransaction<Client>> {
150 let TransactionBuilder { client, signer, value, input, to, nonce, mutate } = self;
151
152 let from = signer.address();
153 let chain_id = Some(client.chain_id().await?);
154 let gas_price = client.gas_price().await?;
155 let nonce = if let Some(nonce) = nonce {
156 nonce
157 } else {
158 client
159 .get_transaction_count(from, BlockTag::Latest.into())
160 .await
161 .with_context(|| "Failed to fetch account nonce")?
162 };
163
164 let gas = client
165 .estimate_gas(
166 GenericTransaction {
167 from: Some(from),
168 input: input.clone().into(),
169 value: Some(value),
170 gas_price: Some(gas_price),
171 to,
172 ..Default::default()
173 },
174 None,
175 )
176 .await
177 .with_context(|| "Failed to fetch gas estimate")?;
178
179 let mut unsigned_tx = TransactionLegacyUnsigned {
180 gas,
181 nonce,
182 to,
183 value,
184 input,
185 gas_price,
186 chain_id,
187 ..Default::default()
188 };
189
190 mutate(&mut unsigned_tx);
191
192 let signed_tx = signer.sign_transaction(unsigned_tx.into());
193 let bytes = signed_tx.signed_payload();
194
195 let hash = client
196 .send_raw_transaction(bytes.into())
197 .await
198 .with_context(|| "send_raw_transaction failed")?;
199
200 Ok(SubmittedTransaction {
201 tx: GenericTransaction::from_signed(signed_tx, gas_price, Some(from)),
202 hash,
203 client,
204 })
205 }
206}
207
208#[test]
209fn test_dummy_payload_has_correct_len() {
210 let signer = Account::from(subxt_signer::eth::dev::ethan());
211 let unsigned_tx: TransactionUnsigned =
212 TransactionLegacyUnsigned { input: vec![42u8; 100].into(), ..Default::default() }.into();
213
214 let signed_tx = signer.sign_transaction(unsigned_tx.clone());
215 let signed_payload = signed_tx.signed_payload();
216 let unsigned_tx = signed_tx.unsigned();
217
218 let dummy_payload = unsigned_tx.dummy_signed_payload();
219 assert_eq!(dummy_payload.len(), signed_payload.len());
220}