1use crate::Provider;
3use alloy_eips::BlockNumberOrTag;
4use alloy_network::Network;
5use alloy_primitives::{Address, Bytes, TxHash, B256};
6use alloy_rpc_types_eth::{state::StateOverride, BlockOverrides};
7use alloy_rpc_types_tenderly::{
8 TenderlyDecodeInputResult, TenderlyEstimateGasResult, TenderlyFunctionSignature,
9 TenderlyGasPriceResult, TenderlySimulationResult, TenderlyStorageChange,
10 TenderlyStorageQueryParams, TenderlyTransactionRangeParams,
11};
12use alloy_transport::TransportResult;
13
14#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
16#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
17pub trait TenderlyApi<N: Network>: Send + Sync {
18 async fn tenderly_simulate_transaction(
21 &self,
22 tx: N::TransactionRequest,
23 block: BlockNumberOrTag,
24 state_overrides: Option<StateOverride>,
25 block_overrides: Option<BlockOverrides>,
26 ) -> TransportResult<TenderlySimulationResult>;
27
28 async fn tenderly_simulate_bundle(
31 &self,
32 txs: &[N::TransactionRequest],
33 block: BlockNumberOrTag,
34 state_overrides: Option<StateOverride>,
35 block_overrides: Option<BlockOverrides>,
36 ) -> TransportResult<Vec<TenderlySimulationResult>>;
37
38 async fn tenderly_trace_transaction(
40 &self,
41 txs: &[TxHash],
42 ) -> TransportResult<TenderlySimulationResult>;
43
44 async fn tenderly_estimate_gas(
46 &self,
47 tx: N::TransactionRequest,
48 block: BlockNumberOrTag,
49 ) -> TransportResult<TenderlyEstimateGasResult>;
50
51 async fn tenderly_gas_price(&self) -> TransportResult<TenderlyGasPriceResult>;
53
54 async fn tenderly_suggest_gas_fee(&self) -> TransportResult<TenderlyGasPriceResult>;
56
57 async fn tenderly_estimate_gas_bundle(
59 &self,
60 txs: &[N::TransactionRequest],
61 block: BlockNumberOrTag,
62 ) -> TransportResult<Vec<TenderlyEstimateGasResult>>;
63
64 async fn tenderly_decode_input(
66 &self,
67 call_data: Bytes,
68 ) -> TransportResult<TenderlyDecodeInputResult>;
69
70 async fn tenderly_decode_error(
72 &self,
73 error_data: Bytes,
74 ) -> TransportResult<TenderlyDecodeInputResult>;
75
76 async fn tenderly_function_signatures(
78 &self,
79 selector: Bytes,
80 ) -> TransportResult<Vec<TenderlyFunctionSignature>>;
81
82 async fn tenderly_decode_event(
84 &self,
85 topics: Vec<B256>,
86 data: Bytes,
87 ) -> TransportResult<TenderlyDecodeInputResult>;
88
89 async fn tenderly_error_signatures(
91 &self,
92 selector: Bytes,
93 ) -> TransportResult<Vec<TenderlyFunctionSignature>>;
94
95 async fn tenderly_event_signature(
97 &self,
98 signature: B256,
99 ) -> TransportResult<TenderlyFunctionSignature>;
100
101 async fn tenderly_get_transactions_range(
103 &self,
104 params: TenderlyTransactionRangeParams,
105 ) -> TransportResult<Vec<N::TransactionResponse>>;
106
107 async fn tenderly_get_contract_abi(
112 &self,
113 address: Address,
114 ) -> TransportResult<Vec<serde_json::Value>>;
115
116 async fn tenderly_get_storage_changes(
123 &self,
124 params: TenderlyStorageQueryParams,
125 ) -> TransportResult<Vec<TenderlyStorageChange>>;
126}
127
128#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
129#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
130impl<N, P> TenderlyApi<N> for P
131where
132 N: Network,
133 P: Provider<N>,
134{
135 async fn tenderly_simulate_transaction(
136 &self,
137 tx: N::TransactionRequest,
138 block: BlockNumberOrTag,
139 state_overrides: Option<StateOverride>,
140 block_overrides: Option<BlockOverrides>,
141 ) -> TransportResult<TenderlySimulationResult> {
142 self.client()
143 .request("tenderly_simulateTransaction", (tx, block, state_overrides, block_overrides))
144 .await
145 }
146
147 async fn tenderly_simulate_bundle(
148 &self,
149 txs: &[N::TransactionRequest],
150 block: BlockNumberOrTag,
151 state_overrides: Option<StateOverride>,
152 block_overrides: Option<BlockOverrides>,
153 ) -> TransportResult<Vec<TenderlySimulationResult>> {
154 self.client()
155 .request("tenderly_simulateBundle", (txs, block, state_overrides, block_overrides))
156 .await
157 }
158
159 async fn tenderly_trace_transaction(
160 &self,
161 txs: &[TxHash],
162 ) -> TransportResult<TenderlySimulationResult> {
163 self.client().request("tenderly_traceTransaction", txs).await
164 }
165
166 async fn tenderly_estimate_gas(
167 &self,
168 tx: N::TransactionRequest,
169 block: BlockNumberOrTag,
170 ) -> TransportResult<TenderlyEstimateGasResult> {
171 self.client().request("tenderly_estimateGas", (tx, block)).await
172 }
173
174 async fn tenderly_gas_price(&self) -> TransportResult<TenderlyGasPriceResult> {
175 self.client().request_noparams("tenderly_gasPrice").await
176 }
177
178 async fn tenderly_suggest_gas_fee(&self) -> TransportResult<TenderlyGasPriceResult> {
179 self.client().request_noparams("tenderly_suggestGasFee").await
180 }
181
182 async fn tenderly_estimate_gas_bundle(
183 &self,
184 txs: &[N::TransactionRequest],
185 block: BlockNumberOrTag,
186 ) -> TransportResult<Vec<TenderlyEstimateGasResult>> {
187 self.client().request("tenderly_estimateGasBundle", (txs, block)).await
188 }
189
190 async fn tenderly_decode_input(
191 &self,
192 call_data: Bytes,
193 ) -> TransportResult<TenderlyDecodeInputResult> {
194 self.client().request("tenderly_decodeInput", (call_data,)).await
195 }
196
197 async fn tenderly_decode_error(
198 &self,
199 error_data: Bytes,
200 ) -> TransportResult<TenderlyDecodeInputResult> {
201 self.client().request("tenderly_decodeError", (error_data,)).await
202 }
203
204 async fn tenderly_function_signatures(
205 &self,
206 selector: Bytes,
207 ) -> TransportResult<Vec<TenderlyFunctionSignature>> {
208 self.client().request("tenderly_functionSignatures", (selector,)).await
209 }
210
211 async fn tenderly_decode_event(
212 &self,
213 topics: Vec<B256>,
214 data: Bytes,
215 ) -> TransportResult<TenderlyDecodeInputResult> {
216 self.client().request("tenderly_decodeEvent", (topics, data)).await
217 }
218
219 async fn tenderly_error_signatures(
220 &self,
221 selector: Bytes,
222 ) -> TransportResult<Vec<TenderlyFunctionSignature>> {
223 self.client().request("tenderly_errorSignatures", (selector,)).await
224 }
225
226 async fn tenderly_event_signature(
227 &self,
228 signature: B256,
229 ) -> TransportResult<TenderlyFunctionSignature> {
230 self.client().request("tenderly_eventSignature", (signature,)).await
231 }
232
233 async fn tenderly_get_transactions_range(
234 &self,
235 params: TenderlyTransactionRangeParams,
236 ) -> TransportResult<Vec<N::TransactionResponse>> {
237 self.client().request("tenderly_getTransactionsRange", (params,)).await
238 }
239
240 async fn tenderly_get_contract_abi(
241 &self,
242 address: Address,
243 ) -> TransportResult<Vec<serde_json::Value>> {
244 self.client().request("tenderly_getContractAbi", (address,)).await
245 }
246
247 async fn tenderly_get_storage_changes(
248 &self,
249 params: TenderlyStorageQueryParams,
250 ) -> TransportResult<Vec<TenderlyStorageChange>> {
251 self.client().request("tenderly_getStorageChanges", (params,)).await
252 }
253}
254
255#[cfg(test)]
256mod test {
257 use std::{env, str::FromStr};
258
259 use alloy_primitives::{address, bytes, utils::parse_ether, Address, U256};
260 use alloy_rpc_types_eth::{
261 state::{AccountOverride, StateOverridesBuilder},
262 TransactionRequest,
263 };
264
265 use crate::ProviderBuilder;
266
267 use super::*;
268
269 #[tokio::test]
270 #[ignore]
271 async fn test_tenderly_simulate_transaction_erc20() {
272 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
273 let provider = ProviderBuilder::new().connect_http(url);
274
275 let gas_price = provider.get_gas_price().await.unwrap();
276 let block = BlockNumberOrTag::Latest;
277 let value = parse_ether("1").unwrap();
278
279 let tx = TransactionRequest::default()
281 .from(Address::ZERO)
282 .to(address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"))
283 .value(value)
284 .max_fee_per_gas(gas_price + 1)
285 .max_priority_fee_per_gas(gas_price + 1);
286
287 let account_override = AccountOverride::default().with_balance(U256::MAX);
288 let state_override =
289 StateOverridesBuilder::default().append(Address::ZERO, account_override).build();
290
291 let _res = provider
292 .tenderly_simulate_transaction(tx, block, Some(state_override), None)
293 .await
294 .unwrap();
295 }
296
297 #[tokio::test]
298 #[ignore]
299 async fn test_tenderly_simulate_transaction_native() {
300 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
301 let provider = ProviderBuilder::new().connect_http(url);
302
303 let gas_price = provider.get_gas_price().await.unwrap();
304 let block = BlockNumberOrTag::Latest;
305 let value = parse_ether("1").unwrap();
306
307 let tx = TransactionRequest::default()
308 .from(Address::ZERO)
309 .to(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"))
310 .value(value)
311 .max_fee_per_gas(gas_price + 1)
312 .max_priority_fee_per_gas(gas_price + 1);
313
314 let account_override = AccountOverride::default().with_balance(U256::MAX);
315 let state_override =
316 StateOverridesBuilder::default().append(Address::ZERO, account_override).build();
317
318 let _res = provider
319 .tenderly_simulate_transaction(tx, block, Some(state_override), None)
320 .await
321 .unwrap();
322 }
323
324 #[tokio::test]
325 #[ignore]
326 async fn test_tenderly_simulate_batch() {
327 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
328 let provider = ProviderBuilder::new().connect_http(url);
329
330 let gas_price = provider.get_gas_price().await.unwrap();
331 let block = BlockNumberOrTag::Latest;
332 let value = parse_ether("1").unwrap();
333
334 let tx = TransactionRequest::default()
335 .from(Address::ZERO)
336 .to(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"))
337 .value(value)
338 .max_fee_per_gas(gas_price + 1)
339 .max_priority_fee_per_gas(gas_price + 1);
340
341 let account_override = AccountOverride::default().with_balance(U256::MAX);
342 let state_override =
343 StateOverridesBuilder::default().append(Address::ZERO, account_override).build();
344
345 let _res = provider
346 .tenderly_simulate_bundle(&[tx.clone(), tx], block, Some(state_override), None)
347 .await
348 .unwrap();
349 }
350
351 #[tokio::test]
352 #[ignore]
353 async fn test_tenderly_trace_transaction() {
354 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
355 let provider = ProviderBuilder::new().connect_http(url);
356
357 let hash =
358 TxHash::from_str("0x6b2264fa8e28a641d834482d250080b39cbbf39251344573c7504d6137c4b793")
359 .unwrap();
360
361 let _res = provider.tenderly_trace_transaction(&[hash]).await.unwrap();
362 }
363
364 #[tokio::test]
365 #[ignore]
366 async fn test_tenderly_estimate_gas() {
367 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
368 let provider = ProviderBuilder::new().connect_http(url);
369
370 let tx = TransactionRequest::default()
371 .from(address!("8516feaea147ea0db64d1c5b97bb651ca5435155"))
372 .to(address!("6b175474e89094c44da98b954eedeac495271d0f"))
373 .input(bytes!("a9059cbb0000000000000000000000003fc3c4c84bdd2db5ab2cc62f93b2a9a347de25fb00000000000000000000000000000000000000000000001869c36187f3430000").into());
374
375 let block = BlockNumberOrTag::Number(21285787);
376
377 let res = provider.tenderly_estimate_gas(tx, block).await.unwrap();
378
379 assert!(res.gas > 0);
380 assert!(res.gas_used > 0);
381 }
382
383 #[tokio::test]
384 #[ignore]
385 async fn test_tenderly_gas_price() {
386 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
387 let provider = ProviderBuilder::new().connect_http(url);
388
389 let res = provider.tenderly_gas_price().await.unwrap();
390
391 assert!(res.current_block_number > 0);
392 assert!(res.base_fee_per_gas > 0);
393 assert!(res.price.low.max_priority_fee_per_gas > 0);
394 assert!(res.price.low.max_fee_per_gas > 0);
395 assert!(res.price.medium.max_priority_fee_per_gas > 0);
396 assert!(res.price.medium.max_fee_per_gas > 0);
397 assert!(res.price.high.max_priority_fee_per_gas > 0);
398 assert!(res.price.high.max_fee_per_gas > 0);
399 }
400
401 #[tokio::test]
402 #[ignore]
403 async fn test_tenderly_suggest_gas_fee() {
404 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
405 let provider = ProviderBuilder::new().connect_http(url);
406
407 let res = provider.tenderly_suggest_gas_fee().await.unwrap();
408
409 assert!(res.current_block_number > 0);
410 assert!(res.base_fee_per_gas > 0);
411 assert!(res.price.low.max_priority_fee_per_gas > 0);
412 assert!(res.price.low.max_fee_per_gas > 0);
413 assert!(res.price.medium.max_priority_fee_per_gas > 0);
414 assert!(res.price.medium.max_fee_per_gas > 0);
415 assert!(res.price.high.max_priority_fee_per_gas > 0);
416 assert!(res.price.high.max_fee_per_gas > 0);
417 }
418
419 #[tokio::test]
420 #[ignore]
421 async fn test_tenderly_estimate_gas_bundle() {
422 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
423 let provider = ProviderBuilder::new().connect_http(url);
424
425 let tx1 = TransactionRequest::default()
426 .from(address!("7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"))
427 .to(address!("ae7ab96520DE3A18E5e111B5EaAb095312D7fE84"))
428 .input(bytes!("095ea7b30000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab410000000000000000000000000000000000000000000000000c1291a92f17a100").into());
429
430 let tx2 = TransactionRequest::default()
431 .from(address!("9008D19f58AAbD9eD0D60971565AA8510560ab41"))
432 .to(address!("ae7ab96520DE3A18E5e111B5EaAb095312D7fE84"))
433 .input(bytes!("23b872dd0000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca00000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab410000000000000000000000000000000000000000000000000c1291a92f17a100").into());
434
435 let block = BlockNumberOrTag::Latest;
436
437 let res = provider.tenderly_estimate_gas_bundle(&[tx1, tx2], block).await.unwrap();
438
439 assert!(!res.is_empty());
440 assert_eq!(res.len(), 2);
441 assert!(res[0].gas > 0);
442 assert!(res[0].gas_used > 0);
443 assert!(res[1].gas > 0);
444 assert!(res[1].gas_used > 0);
445 }
446
447 #[tokio::test]
448 #[ignore]
449 async fn test_tenderly_decode_input() {
450 let url = env::var("TENDERLY_URL").unwrap().parse().unwrap();
451 let provider = ProviderBuilder::new().connect_http(url);
452
453 let call_data = bytes!("a9059cbb00000000000000000000000011223344551122334455112233445511223344550000000000000000000000000000000000000000000000000000000000000999");
454
455 let res = provider.tenderly_decode_input(call_data).await.unwrap();
456
457 assert!(!res.name.is_empty());
458 assert!(!res.decoded_arguments.is_empty());
459 }
460}