1use async_trait::async_trait;
8use eyre::{eyre, Result};
9use soroban_rs::stellar_rpc_client::Client;
10use soroban_rs::stellar_rpc_client::{
11 EventStart, EventType, GetEventsResponse, GetLatestLedgerResponse, GetLedgerEntriesResponse,
12 GetNetworkResponse, GetTransactionResponse, GetTransactionsRequest, GetTransactionsResponse,
13 SimulateTransactionResponse,
14};
15use soroban_rs::xdr::{AccountEntry, Hash, LedgerKey, TransactionEnvelope};
16#[cfg(test)]
17use soroban_rs::xdr::{AccountId, LedgerKeyAccount, PublicKey, Uint256};
18use soroban_rs::SorobanTransactionResponse;
19
20#[cfg(test)]
21use mockall::automock;
22
23use crate::models::RpcConfig;
24use crate::services::provider::ProviderError;
25
26#[derive(Debug, Clone)]
27pub struct GetEventsRequest {
28 pub start: EventStart,
29 pub event_type: Option<EventType>,
30 pub contract_ids: Vec<String>,
31 pub topics: Vec<String>,
32 pub limit: Option<usize>,
33}
34
35#[derive(Clone, Debug)]
36pub struct StellarProvider {
37 client: Client,
38}
39
40#[async_trait]
41#[cfg_attr(test, automock)]
42#[allow(dead_code)]
43pub trait StellarProviderTrait: Send + Sync {
44 async fn get_account(&self, account_id: &str) -> Result<AccountEntry>;
45 async fn simulate_transaction_envelope(
46 &self,
47 tx_envelope: &TransactionEnvelope,
48 ) -> Result<SimulateTransactionResponse>;
49 async fn send_transaction_polling(
50 &self,
51 tx_envelope: &TransactionEnvelope,
52 ) -> Result<SorobanTransactionResponse>;
53 async fn get_network(&self) -> Result<GetNetworkResponse>;
54 async fn get_latest_ledger(&self) -> Result<GetLatestLedgerResponse>;
55 async fn send_transaction(&self, tx_envelope: &TransactionEnvelope) -> Result<Hash>;
56 async fn get_transaction(&self, tx_id: &Hash) -> Result<GetTransactionResponse>;
57 async fn get_transactions(
58 &self,
59 request: GetTransactionsRequest,
60 ) -> Result<GetTransactionsResponse>;
61 async fn get_ledger_entries(&self, keys: &[LedgerKey]) -> Result<GetLedgerEntriesResponse>;
62 async fn get_events(&self, request: GetEventsRequest) -> Result<GetEventsResponse>;
63}
64
65impl StellarProvider {
66 pub fn new(mut rpc_configs: Vec<RpcConfig>, _timeout: u64) -> Result<Self, ProviderError> {
67 if rpc_configs.is_empty() {
68 return Err(ProviderError::NetworkConfiguration(
69 "No RPC configurations provided for StellarProvider".to_string(),
70 ));
71 }
72
73 RpcConfig::validate_list(&rpc_configs)
74 .map_err(|e| ProviderError::NetworkConfiguration(e.to_string()))?;
75
76 rpc_configs.retain(|config| config.get_weight() > 0);
77
78 if rpc_configs.is_empty() {
79 return Err(ProviderError::NetworkConfiguration(
80 "No active RPC configurations provided (all weights are 0 or list was empty after filtering)".to_string(),
81 ));
82 }
83
84 rpc_configs.sort_by_key(|config| std::cmp::Reverse(config.get_weight()));
85
86 let selected_config = &rpc_configs[0];
87 let url = &selected_config.url;
88
89 let client = Client::new(url).map_err(|e| {
90 ProviderError::NetworkConfiguration(format!(
91 "Failed to create Stellar RPC client: {} - URL: '{}'",
92 e, url
93 ))
94 })?;
95 Ok(Self { client })
96 }
97}
98
99impl AsRef<StellarProvider> for StellarProvider {
100 fn as_ref(&self) -> &StellarProvider {
101 self
102 }
103}
104
105#[async_trait]
106impl StellarProviderTrait for StellarProvider {
107 async fn get_account(&self, account_id: &str) -> Result<AccountEntry> {
108 self.client
109 .get_account(account_id)
110 .await
111 .map_err(|e| eyre!("Failed to get account: {}", e))
112 }
113
114 async fn simulate_transaction_envelope(
115 &self,
116 tx_envelope: &TransactionEnvelope,
117 ) -> Result<SimulateTransactionResponse> {
118 self.client
119 .simulate_transaction_envelope(tx_envelope)
120 .await
121 .map_err(|e| eyre!("Failed to simulate transaction: {}", e))
122 }
123
124 async fn send_transaction_polling(
125 &self,
126 tx_envelope: &TransactionEnvelope,
127 ) -> Result<SorobanTransactionResponse> {
128 self.client
129 .send_transaction_polling(tx_envelope)
130 .await
131 .map(SorobanTransactionResponse::from)
132 .map_err(|e| eyre!("Failed to send transaction (polling): {}", e))
133 }
134
135 async fn get_network(&self) -> Result<GetNetworkResponse> {
136 self.client
137 .get_network()
138 .await
139 .map_err(|e| eyre!("Failed to get network: {}", e))
140 }
141
142 async fn get_latest_ledger(&self) -> Result<GetLatestLedgerResponse> {
143 self.client
144 .get_latest_ledger()
145 .await
146 .map_err(|e| eyre!("Failed to get latest ledger: {}", e))
147 }
148
149 async fn send_transaction(&self, tx_envelope: &TransactionEnvelope) -> Result<Hash> {
150 self.client
151 .send_transaction(tx_envelope)
152 .await
153 .map_err(|e| eyre!("Failed to send transaction: {}", e))
154 }
155
156 async fn get_transaction(&self, tx_id: &Hash) -> Result<GetTransactionResponse> {
157 self.client
158 .get_transaction(tx_id)
159 .await
160 .map_err(|e| eyre!("Failed to get transaction: {}", e))
161 }
162
163 async fn get_transactions(
164 &self,
165 request: GetTransactionsRequest,
166 ) -> Result<GetTransactionsResponse> {
167 self.client
168 .get_transactions(request)
169 .await
170 .map_err(|e| eyre!("Failed to get transactions: {}", e))
171 }
172
173 async fn get_ledger_entries(&self, keys: &[LedgerKey]) -> Result<GetLedgerEntriesResponse> {
174 self.client
175 .get_ledger_entries(keys)
176 .await
177 .map_err(|e| eyre!("Failed to get ledger entries: {}", e))
178 }
179
180 async fn get_events(&self, request: GetEventsRequest) -> Result<GetEventsResponse> {
181 self.client
182 .get_events(
183 request.start,
184 request.event_type,
185 &request.contract_ids,
186 &request.topics,
187 request.limit,
188 )
189 .await
190 .map_err(|e| eyre!("Failed to get events: {}", e))
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::services::provider::stellar::{
198 GetEventsRequest, StellarProvider, StellarProviderTrait,
199 };
200 use eyre::eyre;
201 use futures::FutureExt;
202 use mockall::predicate as p;
203 use soroban_rs::stellar_rpc_client::{
204 EventStart, GetEventsResponse, GetLatestLedgerResponse, GetLedgerEntriesResponse,
205 GetNetworkResponse, GetTransactionResponse, GetTransactionsRequest,
206 GetTransactionsResponse, SimulateTransactionResponse,
207 };
208 use soroban_rs::xdr::{
209 AccountEntryExt, Hash, LedgerKey, OperationResult, String32, Thresholds,
210 TransactionEnvelope, TransactionResult, TransactionResultExt, TransactionResultResult,
211 VecM,
212 };
213 use soroban_rs::{create_mock_set_options_tx_envelope, SorobanTransactionResponse};
214 use std::str::FromStr;
215
216 fn dummy_hash() -> Hash {
217 Hash([0u8; 32])
218 }
219
220 fn dummy_get_network_response() -> GetNetworkResponse {
221 GetNetworkResponse {
222 friendbot_url: Some("https://friendbot.testnet.stellar.org/".into()),
223 passphrase: "Test SDF Network ; September 2015".into(),
224 protocol_version: 20,
225 }
226 }
227
228 fn dummy_get_latest_ledger_response() -> GetLatestLedgerResponse {
229 GetLatestLedgerResponse {
230 id: "c73c5eac58a441d4eb733c35253ae85f783e018f7be5ef974258fed067aabb36".into(),
231 protocol_version: 20,
232 sequence: 2_539_605,
233 }
234 }
235
236 fn dummy_simulate() -> SimulateTransactionResponse {
237 SimulateTransactionResponse {
238 min_resource_fee: 100,
239 transaction_data: "test".to_string(),
240 ..Default::default()
241 }
242 }
243
244 fn create_success_tx_result() -> TransactionResult {
245 let empty_vec: Vec<OperationResult> = Vec::new();
247 let op_results = empty_vec.try_into().unwrap_or_default();
248
249 TransactionResult {
250 fee_charged: 100,
251 result: TransactionResultResult::TxSuccess(op_results),
252 ext: TransactionResultExt::V0,
253 }
254 }
255
256 fn dummy_get_transaction_response() -> GetTransactionResponse {
257 GetTransactionResponse {
258 status: "SUCCESS".to_string(),
259 envelope: None,
260 result: Some(create_success_tx_result()),
261 result_meta: None,
262 }
263 }
264
265 fn dummy_soroban_tx() -> SorobanTransactionResponse {
266 SorobanTransactionResponse {
267 response: dummy_get_transaction_response(),
268 }
269 }
270
271 fn dummy_get_transactions_response() -> GetTransactionsResponse {
272 GetTransactionsResponse {
273 transactions: vec![],
274 latest_ledger: 0,
275 latest_ledger_close_time: 0,
276 oldest_ledger: 0,
277 oldest_ledger_close_time: 0,
278 cursor: 0,
279 }
280 }
281
282 fn dummy_get_ledger_entries_response() -> GetLedgerEntriesResponse {
283 GetLedgerEntriesResponse {
284 entries: None,
285 latest_ledger: 0,
286 }
287 }
288
289 fn dummy_get_events_response() -> GetEventsResponse {
290 GetEventsResponse {
291 events: vec![],
292 latest_ledger: 0,
293 }
294 }
295
296 fn dummy_transaction_envelope() -> TransactionEnvelope {
297 create_mock_set_options_tx_envelope()
298 }
299
300 fn dummy_ledger_key() -> LedgerKey {
301 LedgerKey::Account(LedgerKeyAccount {
302 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
303 })
304 }
305
306 pub fn mock_account_entry(account_id: &str) -> AccountEntry {
307 AccountEntry {
308 account_id: AccountId(PublicKey::from_str(account_id).unwrap()),
309 balance: 0,
310 ext: AccountEntryExt::V0,
311 flags: 0,
312 home_domain: String32::default(),
313 inflation_dest: None,
314 seq_num: 0.into(),
315 num_sub_entries: 0,
316 signers: VecM::default(),
317 thresholds: Thresholds([0, 0, 0, 0]),
318 }
319 }
320
321 fn dummy_account_entry() -> AccountEntry {
322 mock_account_entry("GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF")
323 }
324
325 #[test]
330 fn test_new_provider() {
331 let provider =
332 StellarProvider::new(vec![RpcConfig::new("http://localhost:8000".to_string())], 0);
333 assert!(provider.is_ok());
334
335 let provider_err = StellarProvider::new(vec![], 0);
336 assert!(provider_err.is_err());
337 match provider_err.unwrap_err() {
338 ProviderError::NetworkConfiguration(msg) => {
339 assert!(msg.contains("No RPC configurations provided"));
340 }
341 _ => panic!("Unexpected error type"),
342 }
343 }
344
345 #[test]
346 fn test_new_provider_selects_highest_weight() {
347 let configs = vec![
348 RpcConfig::with_weight("http://rpc1.example.com".to_string(), 10).unwrap(),
349 RpcConfig::with_weight("http://rpc2.example.com".to_string(), 100).unwrap(), RpcConfig::with_weight("http://rpc3.example.com".to_string(), 50).unwrap(),
351 ];
352 let provider = StellarProvider::new(configs, 0);
353 assert!(provider.is_ok());
354 }
358
359 #[test]
360 fn test_new_provider_ignores_weight_zero() {
361 let configs = vec![
362 RpcConfig::with_weight("http://rpc1.example.com".to_string(), 0).unwrap(), RpcConfig::with_weight("http://rpc2.example.com".to_string(), 100).unwrap(), ];
365 let provider = StellarProvider::new(configs, 0);
366 assert!(provider.is_ok());
367
368 let configs_only_zero =
369 vec![RpcConfig::with_weight("http://rpc1.example.com".to_string(), 0).unwrap()];
370 let provider_err = StellarProvider::new(configs_only_zero, 0);
371 assert!(provider_err.is_err());
372 match provider_err.unwrap_err() {
373 ProviderError::NetworkConfiguration(msg) => {
374 assert!(msg.contains("No active RPC configurations provided"));
375 }
376 _ => panic!("Unexpected error type"),
377 }
378 }
379
380 #[test]
381 fn test_new_provider_invalid_url_scheme() {
382 let configs = vec![RpcConfig::new("ftp://invalid.example.com".to_string())];
383 let provider_err = StellarProvider::new(configs, 0);
384 assert!(provider_err.is_err());
385 match provider_err.unwrap_err() {
386 ProviderError::NetworkConfiguration(msg) => {
387 assert!(msg.contains("Invalid URL scheme"));
388 }
389 _ => panic!("Unexpected error type"),
390 }
391 }
392
393 #[test]
394 fn test_new_provider_all_zero_weight_configs() {
395 let configs = vec![
396 RpcConfig::with_weight("http://rpc1.example.com".to_string(), 0).unwrap(),
397 RpcConfig::with_weight("http://rpc2.example.com".to_string(), 0).unwrap(),
398 ];
399 let provider_err = StellarProvider::new(configs, 0);
400 assert!(provider_err.is_err());
401 match provider_err.unwrap_err() {
402 ProviderError::NetworkConfiguration(msg) => {
403 assert!(msg.contains("No active RPC configurations provided"));
404 }
405 _ => panic!("Unexpected error type"),
406 }
407 }
408
409 #[tokio::test]
410 async fn test_mock_basic_methods() {
411 let mut mock = MockStellarProviderTrait::new();
412
413 mock.expect_get_network()
414 .times(1)
415 .returning(|| async { Ok(dummy_get_network_response()) }.boxed());
416
417 mock.expect_get_latest_ledger()
418 .times(1)
419 .returning(|| async { Ok(dummy_get_latest_ledger_response()) }.boxed());
420
421 assert!(mock.get_network().await.is_ok());
422 assert!(mock.get_latest_ledger().await.is_ok());
423 }
424
425 #[tokio::test]
426 async fn test_mock_transaction_flow() {
427 let mut mock = MockStellarProviderTrait::new();
428
429 let envelope: TransactionEnvelope = dummy_transaction_envelope();
430 let hash = dummy_hash();
431
432 mock.expect_simulate_transaction_envelope()
433 .withf(|_| true)
434 .times(1)
435 .returning(|_| async { Ok(dummy_simulate()) }.boxed());
436
437 mock.expect_send_transaction()
438 .withf(|_| true)
439 .times(1)
440 .returning(|_| async { Ok(dummy_hash()) }.boxed());
441
442 mock.expect_send_transaction_polling()
443 .withf(|_| true)
444 .times(1)
445 .returning(|_| async { Ok(dummy_soroban_tx()) }.boxed());
446
447 mock.expect_get_transaction()
448 .withf(|_| true)
449 .times(1)
450 .returning(|_| async { Ok(dummy_get_transaction_response()) }.boxed());
451
452 mock.simulate_transaction_envelope(&envelope).await.unwrap();
453 mock.send_transaction(&envelope).await.unwrap();
454 mock.send_transaction_polling(&envelope).await.unwrap();
455 mock.get_transaction(&hash).await.unwrap();
456 }
457
458 #[tokio::test]
459 async fn test_mock_events_and_entries() {
460 let mut mock = MockStellarProviderTrait::new();
461
462 mock.expect_get_events()
463 .times(1)
464 .returning(|_| async { Ok(dummy_get_events_response()) }.boxed());
465
466 mock.expect_get_ledger_entries()
467 .times(1)
468 .returning(|_| async { Ok(dummy_get_ledger_entries_response()) }.boxed());
469
470 let events_request = GetEventsRequest {
471 start: EventStart::Ledger(1),
472 event_type: None,
473 contract_ids: vec![],
474 topics: vec![],
475 limit: Some(10),
476 };
477
478 let dummy_key: LedgerKey = dummy_ledger_key();
479 mock.get_events(events_request).await.unwrap();
480 mock.get_ledger_entries(&[dummy_key]).await.unwrap();
481 }
482
483 #[tokio::test]
484 async fn test_mock_all_methods_ok() {
485 let mut mock = MockStellarProviderTrait::new();
486
487 mock.expect_get_account()
488 .with(p::eq("GTESTACCOUNTID"))
489 .times(1)
490 .returning(|_| async { Ok(dummy_account_entry()) }.boxed());
491
492 mock.expect_simulate_transaction_envelope()
493 .times(1)
494 .returning(|_| async { Ok(dummy_simulate()) }.boxed());
495
496 mock.expect_send_transaction_polling()
497 .times(1)
498 .returning(|_| async { Ok(dummy_soroban_tx()) }.boxed());
499
500 mock.expect_get_network()
501 .times(1)
502 .returning(|| async { Ok(dummy_get_network_response()) }.boxed());
503
504 mock.expect_get_latest_ledger()
505 .times(1)
506 .returning(|| async { Ok(dummy_get_latest_ledger_response()) }.boxed());
507
508 mock.expect_send_transaction()
509 .times(1)
510 .returning(|_| async { Ok(dummy_hash()) }.boxed());
511
512 mock.expect_get_transaction()
513 .times(1)
514 .returning(|_| async { Ok(dummy_get_transaction_response()) }.boxed());
515
516 mock.expect_get_transactions()
517 .times(1)
518 .returning(|_| async { Ok(dummy_get_transactions_response()) }.boxed());
519
520 mock.expect_get_ledger_entries()
521 .times(1)
522 .returning(|_| async { Ok(dummy_get_ledger_entries_response()) }.boxed());
523
524 mock.expect_get_events()
525 .times(1)
526 .returning(|_| async { Ok(dummy_get_events_response()) }.boxed());
527
528 let _ = mock.get_account("GTESTACCOUNTID").await.unwrap();
529 let env: TransactionEnvelope = dummy_transaction_envelope();
530 mock.simulate_transaction_envelope(&env).await.unwrap();
531 mock.send_transaction_polling(&env).await.unwrap();
532 mock.get_network().await.unwrap();
533 mock.get_latest_ledger().await.unwrap();
534 mock.send_transaction(&env).await.unwrap();
535
536 let h = dummy_hash();
537 mock.get_transaction(&h).await.unwrap();
538
539 let req: GetTransactionsRequest = GetTransactionsRequest {
540 start_ledger: None,
541 pagination: None,
542 };
543 mock.get_transactions(req).await.unwrap();
544
545 let key: LedgerKey = dummy_ledger_key();
546 mock.get_ledger_entries(&[key]).await.unwrap();
547
548 let ev_req = GetEventsRequest {
549 start: EventStart::Ledger(0),
550 event_type: None,
551 contract_ids: vec![],
552 topics: vec![],
553 limit: None,
554 };
555 mock.get_events(ev_req).await.unwrap();
556 }
557
558 #[tokio::test]
559 async fn test_error_propagation() {
560 let mut mock = MockStellarProviderTrait::new();
561
562 mock.expect_get_account()
563 .returning(|_| async { Err(eyre!("boom")) }.boxed());
564
565 let res = mock.get_account("BAD").await;
566 assert!(res.is_err());
567 assert!(res.unwrap_err().to_string().contains("boom"));
568 }
569
570 #[tokio::test]
571 async fn test_get_events_edge_cases() {
572 let mut mock = MockStellarProviderTrait::new();
573
574 mock.expect_get_events()
575 .withf(|req| {
576 req.contract_ids.is_empty() && req.topics.is_empty() && req.limit.is_none()
577 })
578 .times(1)
579 .returning(|_| async { Ok(dummy_get_events_response()) }.boxed());
580
581 let ev_req = GetEventsRequest {
582 start: EventStart::Ledger(0),
583 event_type: None,
584 contract_ids: vec![],
585 topics: vec![],
586 limit: None,
587 };
588
589 mock.get_events(ev_req).await.unwrap();
590 }
591
592 #[test]
593 fn test_provider_send_sync_bounds() {
594 fn assert_send_sync<T: Send + Sync>() {}
595 assert_send_sync::<StellarProvider>();
596 }
597
598 #[cfg(test)]
599 mod concrete_tests {
600 use super::*;
601
602 const NON_EXISTENT_URL: &str = "http://127.0.0.1:9999";
603
604 fn setup_provider() -> StellarProvider {
605 StellarProvider::new(vec![RpcConfig::new(NON_EXISTENT_URL.to_string())], 0)
606 .expect("Provider creation should succeed even with bad URL")
607 }
608
609 #[tokio::test]
610 async fn test_concrete_get_account_error() {
611 let provider = setup_provider();
612 let result = provider.get_account("SOME_ACCOUNT_ID").await;
613 assert!(result.is_err());
614 assert!(result
615 .unwrap_err()
616 .to_string()
617 .contains("Failed to get account"));
618 }
619
620 #[tokio::test]
621 async fn test_concrete_simulate_transaction_envelope_error() {
622 let provider = setup_provider();
623 let envelope: TransactionEnvelope = dummy_transaction_envelope();
624 let result = provider.simulate_transaction_envelope(&envelope).await;
625 assert!(result.is_err());
626 assert!(result
627 .unwrap_err()
628 .to_string()
629 .contains("Failed to simulate transaction"));
630 }
631
632 #[tokio::test]
633 async fn test_concrete_send_transaction_polling_error() {
634 let provider = setup_provider();
635 let envelope: TransactionEnvelope = dummy_transaction_envelope();
636 let result = provider.send_transaction_polling(&envelope).await;
637 assert!(result.is_err());
638 assert!(result
639 .unwrap_err()
640 .to_string()
641 .contains("Failed to send transaction (polling)"));
642 }
643
644 #[tokio::test]
645 async fn test_concrete_get_network_error() {
646 let provider = setup_provider();
647 let result = provider.get_network().await;
648 assert!(result.is_err());
649 assert!(result
650 .unwrap_err()
651 .to_string()
652 .contains("Failed to get network"));
653 }
654
655 #[tokio::test]
656 async fn test_concrete_get_latest_ledger_error() {
657 let provider = setup_provider();
658 let result = provider.get_latest_ledger().await;
659 assert!(result.is_err());
660 assert!(result
661 .unwrap_err()
662 .to_string()
663 .contains("Failed to get latest ledger"));
664 }
665
666 #[tokio::test]
667 async fn test_concrete_send_transaction_error() {
668 let provider = setup_provider();
669 let envelope: TransactionEnvelope = dummy_transaction_envelope();
670 let result = provider.send_transaction(&envelope).await;
671 assert!(result.is_err());
672 assert!(result
673 .unwrap_err()
674 .to_string()
675 .contains("Failed to send transaction"));
676 }
677
678 #[tokio::test]
679 async fn test_concrete_get_transaction_error() {
680 let provider = setup_provider();
681 let hash: Hash = dummy_hash();
682 let result = provider.get_transaction(&hash).await;
683 assert!(result.is_err());
684 assert!(result
685 .unwrap_err()
686 .to_string()
687 .contains("Failed to get transaction"));
688 }
689
690 #[tokio::test]
691 async fn test_concrete_get_transactions_error() {
692 let provider = setup_provider();
693 let req = GetTransactionsRequest {
694 start_ledger: None,
695 pagination: None,
696 };
697 let result = provider.get_transactions(req).await;
698 assert!(result.is_err());
699 assert!(result
700 .unwrap_err()
701 .to_string()
702 .contains("Failed to get transactions"));
703 }
704
705 #[tokio::test]
706 async fn test_concrete_get_ledger_entries_error() {
707 let provider = setup_provider();
708 let key: LedgerKey = dummy_ledger_key();
709 let result = provider.get_ledger_entries(&[key]).await;
710 assert!(result.is_err());
711 assert!(result
712 .unwrap_err()
713 .to_string()
714 .contains("Failed to get ledger entries"));
715 }
716
717 #[tokio::test]
718 async fn test_concrete_get_events_error() {
719 let provider = setup_provider();
720 let req = GetEventsRequest {
721 start: EventStart::Ledger(1),
722 event_type: None,
723 contract_ids: vec![],
724 topics: vec![],
725 limit: None,
726 };
727 let result = provider.get_events(req).await;
728 assert!(result.is_err());
729 assert!(result
730 .unwrap_err()
731 .to_string()
732 .contains("Failed to get events"));
733 }
734 }
735}