Skip to main content

flashbots_sdk/
transaction.rs

1use ethers::prelude::*;
2use ethers::types::transaction::eip2718::TypedTransaction;
3use evm_client::{EvmClient, EvmType};
4use std::sync::Arc;
5
6use crate::types::{FlashbotsError, FlashbotsResult};
7/// Builder for creating and signing Ethereum transactions
8pub struct TransactionBuilder {
9    evm_client: Arc<EvmClient>,
10}
11
12/// Creates a new TransactionBuilder with the given provider
13impl TransactionBuilder {
14    pub async fn new(evm_type: EvmType) -> FlashbotsResult<Self> {
15        Ok(Self {
16            evm_client: Arc::new(
17                EvmClient::from_type(evm_type)
18                    .await
19                    .map_err(|e| FlashbotsError::Error(format!("{:?}", e)))?,
20            ),
21        })
22    }
23
24    /// Creates a basic ETH transfer transaction
25    ///
26    /// # Example
27    /// ```
28    /// use ethers::types::{Address, U256};
29    /// use ethers::providers::Provider;
30    /// use std::str::FromStr;
31    ///
32    /// let builder = TransactionBuilder::new(EvmType::ETHEREUM_MAINNET).await?;
33    /// let wallet = create_random_wallet();
34    /// let to_address = Address::from_str("0x742d35Cc6634C0532925a3b8Dc9F5a5f6b6b6b6b")?;
35    /// let value = U256::from(1000000000000000000u64); // 1 ETH
36    /// let tx = builder.create_eth_transfer(wallet, to_address, value).await?;
37    /// ```
38    pub async fn create_eth_transfer(
39        &self,
40        from: LocalWallet,
41        to: Address,
42        value: U256,
43    ) -> Result<TransactionRequest, Box<dyn std::error::Error>> {
44        let nonce = self
45            .evm_client
46            .provider
47            .get_transaction_count(from.address(), None)
48            .await?;
49        let gas_price = self.evm_client.provider.get_gas_price().await?;
50        Ok(TransactionRequest::new()
51            .from(from.address())
52            .to(to)
53            .value(value)
54            .nonce(nonce)
55            .gas_price(gas_price)
56            .gas(21000))
57    }
58
59    /// Creates a contract call transaction
60    pub async fn create_contract_call(
61        &self,
62        from: Address,
63        to: Address,
64        data: Bytes,
65        value: Option<U256>,
66    ) -> Result<TransactionRequest, Box<dyn std::error::Error>> {
67        let nonce = self
68            .evm_client
69            .provider
70            .get_transaction_count(from, None)
71            .await?;
72        let gas_price = self.evm_client.provider.get_gas_price().await?;
73        let mut tx = TransactionRequest::new()
74            .from(from)
75            .to(to)
76            .data(data)
77            .nonce(nonce)
78            .gas_price(gas_price);
79        if let Some(val) = value {
80            tx = tx.value(val);
81        }
82        Ok(tx)
83    }
84
85    /// Signs a transaction with the given wallet
86    ///
87    /// # Example
88    /// ```
89    /// let builder = TransactionBuilder::new(EvmType::ETHEREUM_MAINNET).await?;
90    /// let wallet = create_random_wallet();
91    /// let tx_request = TransactionRequest::new()
92    ///     .to("0x742d35Cc6634C0532925a3b8Dc9F5a5f6b6b6b6b".parse()?)
93    ///     .value(1000000000000000000u64);
94    /// let signed_tx = builder.sign_transaction(wallet, tx_request).await?;
95    /// println!("Signed transaction: {}", signed_tx);
96    /// ```
97    pub async fn sign_transaction(
98        &self,
99        wallet: LocalWallet,
100        tx: TransactionRequest,
101    ) -> Result<String, Box<dyn std::error::Error>> {
102        let chain_id = self.evm_client.provider.get_chainid().await?.as_u64();
103        let mut typed_tx: TypedTransaction = tx.into();
104        typed_tx.set_chain_id(chain_id);
105        let signature = wallet.sign_transaction(&typed_tx).await?;
106        let signed_tx = typed_tx.rlp_signed(&signature);
107        let signed_tx_hex = format!("0x{}", hex::encode(signed_tx));
108        Ok(signed_tx_hex)
109    }
110
111    /// Estimates gas for a transaction
112    pub async fn estimate_gas(
113        &self,
114        tx: &TransactionRequest,
115    ) -> Result<U256, Box<dyn std::error::Error>> {
116        let typed_tx: TypedTransaction = tx.clone().into();
117        let gas_estimate = self
118            .evm_client
119            .provider
120            .estimate_gas(&typed_tx, None)
121            .await?;
122        Ok(gas_estimate)
123    }
124
125    /// Gets current gas price from the network
126    pub async fn get_current_gas_price(&self) -> Result<U256, Box<dyn std::error::Error>> {
127        let gas_price = self.evm_client.provider.get_gas_price().await?;
128        Ok(gas_price)
129    }
130
131    /// Gets nonce for an address
132    pub async fn get_nonce(&self, address: Address) -> Result<U256, Box<dyn std::error::Error>> {
133        let nonce = self
134            .evm_client
135            .provider
136            .get_transaction_count(address, None)
137            .await?;
138        Ok(nonce)
139    }
140
141    /// Creates and signs an ETH transfer in one operation
142    pub async fn create_and_sign_eth_transfer(
143        &self,
144        wallet: LocalWallet,
145        to: Address,
146        value: U256,
147    ) -> Result<String, Box<dyn std::error::Error>> {
148        let tx_request = self.create_eth_transfer(wallet.clone(), to, value).await?;
149        self.sign_transaction(wallet, tx_request).await
150    }
151
152    /// Creates and signs a contract call in one operation
153    pub async fn create_and_sign_contract_call(
154        &self,
155        wallet: LocalWallet,
156        to: Address,
157        data: Bytes,
158        value: Option<U256>,
159    ) -> Result<String, Box<dyn std::error::Error>> {
160        let tx_request = self
161            .create_contract_call(wallet.address(), to, data, value)
162            .await?;
163        self.sign_transaction(wallet, tx_request).await
164    }
165
166    /// Creates an EIP-1559 type transaction
167    ///
168    /// # Example
169    /// ```
170    /// let builder = TransactionBuilder::new(EvmType::ETHEREUM_MAINNET).await?;
171    /// let wallet = create_random_wallet();
172    /// let to_address = "0x742d35Cc6634C0532925a3b8Dc9F5a5f6b6b6b6b".parse()?;
173    /// let value = U256::from(1000000000000000000u64);
174    /// let tx = builder.create_eip1559_transaction(wallet, to_address, value).await?;
175    /// ```
176    pub async fn create_eip1559_transaction(
177        &self,
178        from: LocalWallet,
179        to: Address,
180        value: U256,
181    ) -> Result<Eip1559TransactionRequest, Box<dyn std::error::Error>> {
182        let nonce = self
183            .evm_client
184            .provider
185            .get_transaction_count(from.address(), None)
186            .await?;
187        let block = self
188            .evm_client
189            .provider
190            .get_block(BlockNumber::Latest)
191            .await?;
192        let base_fee = block
193            .and_then(|b| b.base_fee_per_gas)
194            .unwrap_or_else(|| U256::from(1000000000)); // 默认 1 gwei
195        let max_priority_fee_per_gas = U256::from(1500000000); // 1.5 gwei
196        Ok(Eip1559TransactionRequest::new()
197            .from(from.address())
198            .to(to)
199            .value(value)
200            .nonce(nonce)
201            .max_fee_per_gas(base_fee + max_priority_fee_per_gas)
202            .max_priority_fee_per_gas(max_priority_fee_per_gas)
203            .gas(21000))
204    }
205
206    /// Signs an EIP-1559 transaction
207    pub async fn sign_eip1559_transaction(
208        &self,
209        wallet: LocalWallet,
210        tx: Eip1559TransactionRequest,
211    ) -> Result<String, Box<dyn std::error::Error>> {
212        let chain_id = self.evm_client.provider.get_chainid().await?.as_u64();
213        let mut typed_tx: TypedTransaction = tx.into();
214        typed_tx.set_chain_id(chain_id);
215        let signature = wallet.sign_transaction(&typed_tx).await?;
216        let signed_tx = typed_tx.rlp_signed(&signature);
217        let signed_tx_hex = format!("0x{}", hex::encode(signed_tx));
218        Ok(signed_tx_hex)
219    }
220
221    /// Signs raw transaction data
222    pub async fn sign_raw_transaction(
223        &self,
224        wallet: LocalWallet,
225        tx_data: Bytes,
226    ) -> Result<String, Box<dyn std::error::Error>> {
227        let chain_id = self.evm_client.provider.get_chainid().await?.as_u64();
228        let tx_request = TransactionRequest::new().data(tx_data);
229        let mut typed_tx: TypedTransaction = tx_request.into();
230        typed_tx.set_chain_id(chain_id);
231        let signature = wallet.sign_transaction(&typed_tx).await?;
232        let signed_tx = typed_tx.rlp_signed(&signature);
233        let signed_tx_hex = format!("0x{}", hex::encode(signed_tx));
234        Ok(signed_tx_hex)
235    }
236}
237
238/// Builder for creating multiple transactions in batch
239pub struct BatchTransactionBuilder {
240    evm_client: Arc<EvmClient>,
241    transactions: Vec<TransactionRequest>,
242}
243
244impl BatchTransactionBuilder {
245    /// Creates a new BatchTransactionBuilder
246    pub async fn new(evm_type: EvmType) -> FlashbotsResult<Self> {
247        Ok(Self {
248            evm_client: Arc::new(
249                EvmClient::from_type(evm_type)
250                    .await
251                    .map_err(|e| FlashbotsError::Error(format!("{:?}", e)))?,
252            ),
253            transactions: Vec::new(),
254        })
255    }
256    /// Adds a transaction to the batch
257    pub fn add_transaction(mut self, tx: TransactionRequest) -> Self {
258        self.transactions.push(tx);
259        self
260    }
261
262    /// Estimates gas for all transactions in the batch
263    pub async fn estimate_all_gas(&self) -> Result<Vec<U256>, Box<dyn std::error::Error>> {
264        let mut estimates = Vec::new();
265        for tx in &self.transactions {
266            let typed_tx: TypedTransaction = tx.clone().into();
267            let estimate = self
268                .evm_client
269                .provider
270                .estimate_gas(&typed_tx, None)
271                .await?;
272            estimates.push(estimate);
273        }
274        Ok(estimates)
275    }
276
277    /// Signs all transactions in the batch with the same wallet
278    ///
279    /// # Example
280    /// ```
281    /// let builder = BatchTransactionBuilder::new(EvmType::ETHEREUM_MAINNET).await?
282    /// let wallet = create_random_wallet();
283    /// let batch = builder
284    ///     .add_transaction(TransactionRequest::new().value(1000000000000000000u64))
285    ///     .add_transaction(TransactionRequest::new().value(2000000000000000000u64));
286    /// let signed_txs = batch.sign_all(wallet).await?;
287    /// println!("Signed {} transactions", signed_txs.len());
288    /// ```
289    pub async fn sign_all(&self, wallet: LocalWallet) -> FlashbotsResult<Vec<String>> {
290        let mut signed_txs = Vec::new();
291        // Create a TransactionBuilder with the same evm_client
292        let tx_builder = TransactionBuilder {
293            evm_client: Arc::clone(&self.evm_client),
294        };
295        for tx in &self.transactions {
296            let signed_tx = tx_builder
297                .sign_transaction(wallet.clone(), tx.clone())
298                .await
299                .map_err(|e| FlashbotsError::Error(format!("{:?}", e)))?;
300            signed_txs.push(signed_tx);
301        }
302        Ok(signed_txs)
303    }
304}