1#[cfg(feature = "coin-cache")]
2use std::sync::Arc;
3use std::{collections::HashMap, fmt::Debug, net::SocketAddr};
4
5mod cache;
6mod retry_util;
7mod retryable_client;
8mod supported_fuel_core_version;
9mod supported_versions;
10
11use crate::provider::cache::CacheableRpcs;
12pub use cache::TtlConfig;
13use cache::{CachedClient, SystemClock};
14use chrono::{DateTime, Utc};
15use fuel_core_client::client::{
16 pagination::{PageDirection, PaginatedResult, PaginationRequest},
17 types::{
18 balance::Balance,
19 contract::ContractBalance,
20 gas_price::{EstimateGasPrice, LatestGasPrice},
21 },
22};
23use fuel_core_types::services::executor::TransactionExecutionResult;
24use fuel_tx::{
25 AssetId, ConsensusParameters, Receipt, Transaction as FuelTransaction, TxId, UtxoId,
26};
27use fuel_types::{Address, BlockHeight, Bytes32, Nonce};
28#[cfg(feature = "coin-cache")]
29use fuels_core::types::coin_type_id::CoinTypeId;
30use fuels_core::{
31 constants::{DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON, DEFAULT_GAS_ESTIMATION_TOLERANCE},
32 types::{
33 bech32::{Bech32Address, Bech32ContractId},
34 block::{Block, Header},
35 chain_info::ChainInfo,
36 coin::Coin,
37 coin_type::CoinType,
38 errors::Result,
39 message::Message,
40 message_proof::MessageProof,
41 node_info::NodeInfo,
42 transaction::{Transaction, Transactions},
43 transaction_builders::{Blob, BlobId},
44 transaction_response::TransactionResponse,
45 tx_status::TxStatus,
46 DryRun, DryRunner,
47 },
48};
49pub use retry_util::{Backoff, RetryConfig};
50pub use supported_fuel_core_version::SUPPORTED_FUEL_CORE_VERSION;
51use tai64::Tai64;
52#[cfg(feature = "coin-cache")]
53use tokio::sync::Mutex;
54
55#[cfg(feature = "coin-cache")]
56use crate::coin_cache::CoinsCache;
57use crate::provider::retryable_client::RetryableClient;
58
59const NUM_RESULTS_PER_REQUEST: i32 = 100;
60
61#[derive(Debug, Clone, PartialEq)]
62pub struct TransactionCost {
64 pub gas_price: u64,
65 pub gas_used: u64,
66 pub metered_bytes_size: u64,
67 pub total_fee: u64,
68}
69pub(crate) struct ResourceQueries {
72 utxos: Vec<UtxoId>,
73 messages: Vec<Nonce>,
74 asset_id: Option<AssetId>,
75 amount: u64,
76}
77
78impl ResourceQueries {
79 pub fn exclusion_query(&self) -> Option<(Vec<UtxoId>, Vec<Nonce>)> {
80 if self.utxos.is_empty() && self.messages.is_empty() {
81 return None;
82 }
83
84 Some((self.utxos.clone(), self.messages.clone()))
85 }
86
87 pub fn spend_query(&self, base_asset_id: AssetId) -> Vec<(AssetId, u64, Option<u32>)> {
88 vec![(self.asset_id.unwrap_or(base_asset_id), self.amount, None)]
89 }
90}
91
92#[derive(Default)]
93pub struct ResourceFilter {
95 pub from: Bech32Address,
96 pub asset_id: Option<AssetId>,
97 pub amount: u64,
98 pub excluded_utxos: Vec<UtxoId>,
99 pub excluded_message_nonces: Vec<Nonce>,
100}
101impl ResourceFilter {
104 pub fn owner(&self) -> Address {
105 (&self.from).into()
106 }
107
108 pub(crate) fn resource_queries(&self) -> ResourceQueries {
109 ResourceQueries {
110 utxos: self.excluded_utxos.clone(),
111 messages: self.excluded_message_nonces.clone(),
112 asset_id: self.asset_id,
113 amount: self.amount,
114 }
115 }
116}
117
118#[derive(Debug, Clone)]
122pub struct Provider {
123 cached_client: CachedClient<RetryableClient>,
124 #[cfg(feature = "coin-cache")]
125 coins_cache: Arc<Mutex<CoinsCache>>,
126}
127
128impl Provider {
129 pub async fn from(addr: impl Into<SocketAddr>) -> Result<Self> {
130 let addr = addr.into();
131 Self::connect(format!("http://{addr}")).await
132 }
133
134 pub fn set_cache_ttl(&mut self, ttl: TtlConfig) {
135 self.cached_client.set_ttl(ttl);
136 }
137
138 pub async fn clear_cache(&self) {
139 self.cached_client.clear().await;
140 }
141
142 pub async fn healthy(&self) -> Result<bool> {
143 Ok(self.uncached_client().health().await?)
144 }
145
146 pub async fn connect(url: impl AsRef<str>) -> Result<Provider> {
148 let client = CachedClient::new(
149 RetryableClient::connect(&url, Default::default()).await?,
150 TtlConfig::default(),
151 SystemClock,
152 );
153
154 Ok(Self {
155 cached_client: client,
156 #[cfg(feature = "coin-cache")]
157 coins_cache: Default::default(),
158 })
159 }
160
161 pub fn url(&self) -> &str {
162 self.uncached_client().url()
163 }
164
165 pub async fn blob(&self, blob_id: BlobId) -> Result<Option<Blob>> {
166 Ok(self
167 .uncached_client()
168 .blob(blob_id.into())
169 .await?
170 .map(|blob| Blob::new(blob.bytecode)))
171 }
172
173 pub async fn blob_exists(&self, blob_id: BlobId) -> Result<bool> {
174 Ok(self.uncached_client().blob_exists(blob_id.into()).await?)
175 }
176
177 pub async fn send_transaction_and_await_commit<T: Transaction>(
179 &self,
180 tx: T,
181 ) -> Result<TxStatus> {
182 #[cfg(feature = "coin-cache")]
183 let base_asset_id = *self.consensus_parameters().await?.base_asset_id();
184
185 #[cfg(feature = "coin-cache")]
186 self.check_inputs_already_in_cache(&tx.used_coins(&base_asset_id))
187 .await?;
188
189 let tx = self.prepare_transaction_for_sending(tx).await?;
190 let tx_status = self
191 .uncached_client()
192 .submit_and_await_commit(&tx.clone().into())
193 .await?
194 .into();
195
196 #[cfg(feature = "coin-cache")]
197 if matches!(
198 tx_status,
199 TxStatus::SqueezedOut { .. } | TxStatus::Revert { .. }
200 ) {
201 self.coins_cache
202 .lock()
203 .await
204 .remove_items(tx.used_coins(&base_asset_id))
205 }
206
207 Ok(tx_status)
208 }
209
210 async fn prepare_transaction_for_sending<T: Transaction>(&self, mut tx: T) -> Result<T> {
211 let consensus_parameters = self.consensus_parameters().await?;
212 tx.precompute(&consensus_parameters.chain_id())?;
213
214 let chain_info = self.chain_info().await?;
215 let Header {
216 height: latest_block_height,
217 state_transition_bytecode_version: latest_chain_executor_version,
218 ..
219 } = chain_info.latest_block.header;
220
221 if tx.is_using_predicates() {
222 tx.estimate_predicates(self, Some(latest_chain_executor_version))
223 .await?;
224 tx.clone()
225 .validate_predicates(&consensus_parameters, latest_block_height)?;
226 }
227
228 self.validate_transaction(tx.clone()).await?;
229
230 Ok(tx)
231 }
232
233 pub async fn send_transaction<T: Transaction>(&self, tx: T) -> Result<TxId> {
234 let tx = self.prepare_transaction_for_sending(tx).await?;
235 self.submit(tx).await
236 }
237
238 pub async fn await_transaction_commit<T: Transaction>(&self, id: TxId) -> Result<TxStatus> {
239 Ok(self
240 .uncached_client()
241 .await_transaction_commit(&id)
242 .await?
243 .into())
244 }
245
246 async fn validate_transaction<T: Transaction>(&self, tx: T) -> Result<()> {
247 let tolerance = 0.0;
248 let TransactionCost { gas_used, .. } = self
249 .estimate_transaction_cost(tx.clone(), Some(tolerance), None)
250 .await?;
251
252 tx.validate_gas(gas_used)?;
253
254 Ok(())
255 }
256
257 #[cfg(not(feature = "coin-cache"))]
258 async fn submit<T: Transaction>(&self, tx: T) -> Result<TxId> {
259 Ok(self.uncached_client().submit(&tx.into()).await?)
260 }
261
262 #[cfg(feature = "coin-cache")]
263 async fn find_in_cache<'a>(
264 &self,
265 coin_ids: impl IntoIterator<Item = (&'a (Bech32Address, AssetId), &'a Vec<CoinTypeId>)>,
266 ) -> Option<((Bech32Address, AssetId), CoinTypeId)> {
267 let mut locked_cache = self.coins_cache.lock().await;
268
269 for (key, ids) in coin_ids {
270 let items = locked_cache.get_active(key);
271
272 if items.is_empty() {
273 continue;
274 }
275
276 for id in ids {
277 if items.contains(id) {
278 return Some((key.clone(), id.clone()));
279 }
280 }
281 }
282
283 None
284 }
285
286 #[cfg(feature = "coin-cache")]
287 async fn check_inputs_already_in_cache<'a>(
288 &self,
289 coin_ids: impl IntoIterator<Item = (&'a (Bech32Address, AssetId), &'a Vec<CoinTypeId>)>,
290 ) -> Result<()> {
291 use fuels_core::types::errors::{transaction, Error};
292
293 if let Some(((addr, asset_id), coin_type_id)) = self.find_in_cache(coin_ids).await {
294 let msg = match coin_type_id {
295 CoinTypeId::UtxoId(utxo_id) => format!("coin with utxo_id: `{utxo_id:x}`"),
296 CoinTypeId::Nonce(nonce) => format!("message with nonce: `{nonce}`"),
297 };
298 Err(Error::Transaction(transaction::Reason::Validation(
299 format!("{msg} was submitted recently in a transaction - attempting to spend it again will result in an error. Wallet address: `{addr}`, asset id: `{asset_id}`"),
300 )))
301 } else {
302 Ok(())
303 }
304 }
305
306 #[cfg(feature = "coin-cache")]
307 async fn submit<T: Transaction>(&self, tx: T) -> Result<TxId> {
308 let consensus_parameters = self.consensus_parameters().await?;
309 let base_asset_id = consensus_parameters.base_asset_id();
310
311 let used_utxos = tx.used_coins(base_asset_id);
312 self.check_inputs_already_in_cache(&used_utxos).await?;
313
314 let tx_id = self.uncached_client().submit(&tx.into()).await?;
315 self.coins_cache.lock().await.insert_multiple(used_utxos);
316
317 Ok(tx_id)
318 }
319
320 pub async fn tx_status(&self, tx_id: &TxId) -> Result<TxStatus> {
321 Ok(self
322 .uncached_client()
323 .transaction_status(tx_id)
324 .await?
325 .into())
326 }
327
328 pub async fn chain_info(&self) -> Result<ChainInfo> {
329 Ok(self.uncached_client().chain_info().await?.into())
330 }
331
332 pub async fn consensus_parameters(&self) -> Result<ConsensusParameters> {
333 self.cached_client.consensus_parameters().await
334 }
335
336 pub async fn node_info(&self) -> Result<NodeInfo> {
337 Ok(self.cached_client.node_info().await?.into())
338 }
339
340 pub async fn latest_gas_price(&self) -> Result<LatestGasPrice> {
341 Ok(self.uncached_client().latest_gas_price().await?)
342 }
343
344 pub async fn estimate_gas_price(&self, block_horizon: u32) -> Result<EstimateGasPrice> {
345 Ok(self
346 .uncached_client()
347 .estimate_gas_price(block_horizon)
348 .await?)
349 }
350
351 pub async fn dry_run(&self, tx: impl Transaction) -> Result<TxStatus> {
352 let [tx_status] = self
353 .uncached_client()
354 .dry_run(Transactions::new().insert(tx).as_slice())
355 .await?
356 .into_iter()
357 .map(Into::into)
358 .collect::<Vec<_>>()
359 .try_into()
360 .expect("should have only one element");
361
362 Ok(tx_status)
363 }
364
365 pub async fn dry_run_multiple(
366 &self,
367 transactions: Transactions,
368 ) -> Result<Vec<(TxId, TxStatus)>> {
369 Ok(self
370 .uncached_client()
371 .dry_run(transactions.as_slice())
372 .await?
373 .into_iter()
374 .map(|execution_status| (execution_status.id, execution_status.into()))
375 .collect())
376 }
377
378 pub async fn dry_run_opt(
379 &self,
380 tx: impl Transaction,
381 utxo_validation: bool,
382 gas_price: Option<u64>,
383 ) -> Result<TxStatus> {
384 let [tx_status] = self
385 .uncached_client()
386 .dry_run_opt(
387 Transactions::new().insert(tx).as_slice(),
388 Some(utxo_validation),
389 gas_price,
390 )
391 .await?
392 .into_iter()
393 .map(Into::into)
394 .collect::<Vec<_>>()
395 .try_into()
396 .expect("should have only one element");
397
398 Ok(tx_status)
399 }
400
401 pub async fn dry_run_opt_multiple(
402 &self,
403 transactions: Transactions,
404 utxo_validation: bool,
405 gas_price: Option<u64>,
406 ) -> Result<Vec<(TxId, TxStatus)>> {
407 Ok(self
408 .uncached_client()
409 .dry_run_opt(transactions.as_slice(), Some(utxo_validation), gas_price)
410 .await?
411 .into_iter()
412 .map(|execution_status| (execution_status.id, execution_status.into()))
413 .collect())
414 }
415
416 pub async fn get_coins(&self, from: &Bech32Address, asset_id: AssetId) -> Result<Vec<Coin>> {
418 let mut coins: Vec<Coin> = vec![];
419 let mut cursor = None;
420
421 loop {
422 let response = self
423 .uncached_client()
424 .coins(
425 &from.into(),
426 Some(&asset_id),
427 PaginationRequest {
428 cursor: cursor.clone(),
429 results: NUM_RESULTS_PER_REQUEST,
430 direction: PageDirection::Forward,
431 },
432 )
433 .await?;
434
435 if response.results.is_empty() {
436 break;
437 }
438
439 coins.extend(response.results.into_iter().map(Into::into));
440 cursor = response.cursor;
441 }
442
443 Ok(coins)
444 }
445
446 async fn request_coins_to_spend(&self, filter: ResourceFilter) -> Result<Vec<CoinType>> {
447 let queries = filter.resource_queries();
448
449 let consensus_parameters = self.consensus_parameters().await?;
450 let base_asset_id = *consensus_parameters.base_asset_id();
451
452 let res = self
453 .uncached_client()
454 .coins_to_spend(
455 &filter.owner(),
456 queries.spend_query(base_asset_id),
457 queries.exclusion_query(),
458 )
459 .await?
460 .into_iter()
461 .flatten()
462 .map(CoinType::from)
463 .collect();
464
465 Ok(res)
466 }
467
468 #[cfg(not(feature = "coin-cache"))]
472 pub async fn get_spendable_resources(&self, filter: ResourceFilter) -> Result<Vec<CoinType>> {
473 self.request_coins_to_spend(filter).await
474 }
475
476 #[cfg(feature = "coin-cache")]
481 pub async fn get_spendable_resources(
482 &self,
483 mut filter: ResourceFilter,
484 ) -> Result<Vec<CoinType>> {
485 self.extend_filter_with_cached(&mut filter).await?;
486
487 self.request_coins_to_spend(filter).await
488 }
489
490 #[cfg(feature = "coin-cache")]
491 async fn extend_filter_with_cached(&self, filter: &mut ResourceFilter) -> Result<()> {
492 let consensus_parameters = self.consensus_parameters().await?;
493 let mut cache = self.coins_cache.lock().await;
494 let asset_id = filter
495 .asset_id
496 .unwrap_or(*consensus_parameters.base_asset_id());
497 let used_coins = cache.get_active(&(filter.from.clone(), asset_id));
498
499 let excluded_utxos = used_coins
500 .iter()
501 .filter_map(|coin_id| match coin_id {
502 CoinTypeId::UtxoId(utxo_id) => Some(utxo_id),
503 _ => None,
504 })
505 .cloned()
506 .collect::<Vec<_>>();
507
508 let excluded_message_nonces = used_coins
509 .iter()
510 .filter_map(|coin_id| match coin_id {
511 CoinTypeId::Nonce(nonce) => Some(nonce),
512 _ => None,
513 })
514 .cloned()
515 .collect::<Vec<_>>();
516
517 filter.excluded_utxos.extend(excluded_utxos);
518 filter
519 .excluded_message_nonces
520 .extend(excluded_message_nonces);
521
522 Ok(())
523 }
524
525 pub async fn get_asset_balance(
529 &self,
530 address: &Bech32Address,
531 asset_id: AssetId,
532 ) -> Result<u64> {
533 Ok(self
534 .uncached_client()
535 .balance(&address.into(), Some(&asset_id))
536 .await?)
537 }
538
539 pub async fn get_contract_asset_balance(
541 &self,
542 contract_id: &Bech32ContractId,
543 asset_id: AssetId,
544 ) -> Result<u64> {
545 Ok(self
546 .uncached_client()
547 .contract_balance(&contract_id.into(), Some(&asset_id))
548 .await?)
549 }
550
551 pub async fn get_balances(&self, address: &Bech32Address) -> Result<HashMap<String, u128>> {
555 let mut balances = HashMap::new();
556
557 let mut register_balances = |results: Vec<_>| {
558 let pairs = results.into_iter().map(
559 |Balance {
560 owner: _,
561 amount,
562 asset_id,
563 }| (asset_id.to_string(), amount),
564 );
565 balances.extend(pairs);
566 };
567
568 let indexation_flags = self.cached_client.node_info().await?.indexation;
569 if indexation_flags.balances {
570 let mut cursor = None;
571 loop {
572 let pagination = PaginationRequest {
573 cursor: cursor.clone(),
574 results: NUM_RESULTS_PER_REQUEST,
575 direction: PageDirection::Forward,
576 };
577 let response = self
578 .uncached_client()
579 .balances(&address.into(), pagination)
580 .await?;
581
582 if response.results.is_empty() {
583 break;
584 }
585
586 register_balances(response.results);
587 cursor = response.cursor;
588 }
589 } else {
590 let pagination = PaginationRequest {
591 cursor: None,
592 results: 9999,
593 direction: PageDirection::Forward,
594 };
595 let response = self
596 .uncached_client()
597 .balances(&address.into(), pagination)
598 .await?;
599
600 register_balances(response.results)
601 }
602
603 Ok(balances)
604 }
605
606 pub async fn get_contract_balances(
608 &self,
609 contract_id: &Bech32ContractId,
610 ) -> Result<HashMap<AssetId, u64>> {
611 let mut contract_balances = HashMap::new();
612 let mut cursor = None;
613
614 loop {
615 let response = self
616 .uncached_client()
617 .contract_balances(
618 &contract_id.into(),
619 PaginationRequest {
620 cursor: cursor.clone(),
621 results: NUM_RESULTS_PER_REQUEST,
622 direction: PageDirection::Forward,
623 },
624 )
625 .await?;
626
627 if response.results.is_empty() {
628 break;
629 }
630
631 contract_balances.extend(response.results.into_iter().map(
632 |ContractBalance {
633 contract: _,
634 amount,
635 asset_id,
636 }| (asset_id, amount),
637 ));
638 cursor = response.cursor;
639 }
640
641 Ok(contract_balances)
642 }
643
644 pub async fn get_transaction_by_id(&self, tx_id: &TxId) -> Result<Option<TransactionResponse>> {
645 Ok(self
646 .uncached_client()
647 .transaction(tx_id)
648 .await?
649 .map(Into::into))
650 }
651
652 pub async fn get_transactions(
653 &self,
654 request: PaginationRequest<String>,
655 ) -> Result<PaginatedResult<TransactionResponse, String>> {
656 let pr = self.uncached_client().transactions(request).await?;
657
658 Ok(PaginatedResult {
659 cursor: pr.cursor,
660 results: pr.results.into_iter().map(Into::into).collect(),
661 has_next_page: pr.has_next_page,
662 has_previous_page: pr.has_previous_page,
663 })
664 }
665
666 pub async fn get_transactions_by_owner(
668 &self,
669 owner: &Bech32Address,
670 request: PaginationRequest<String>,
671 ) -> Result<PaginatedResult<TransactionResponse, String>> {
672 let pr = self
673 .uncached_client()
674 .transactions_by_owner(&owner.into(), request)
675 .await?;
676
677 Ok(PaginatedResult {
678 cursor: pr.cursor,
679 results: pr.results.into_iter().map(Into::into).collect(),
680 has_next_page: pr.has_next_page,
681 has_previous_page: pr.has_previous_page,
682 })
683 }
684
685 pub async fn latest_block_height(&self) -> Result<u32> {
686 Ok(self.chain_info().await?.latest_block.header.height)
687 }
688
689 pub async fn latest_block_time(&self) -> Result<Option<DateTime<Utc>>> {
690 Ok(self.chain_info().await?.latest_block.header.time)
691 }
692
693 pub async fn produce_blocks(
694 &self,
695 blocks_to_produce: u32,
696 start_time: Option<DateTime<Utc>>,
697 ) -> Result<u32> {
698 let start_time = start_time.map(|time| Tai64::from_unix(time.timestamp()).0);
699
700 Ok(self
701 .uncached_client()
702 .produce_blocks(blocks_to_produce, start_time)
703 .await?
704 .into())
705 }
706
707 pub async fn block(&self, block_id: &Bytes32) -> Result<Option<Block>> {
708 Ok(self
709 .uncached_client()
710 .block(block_id)
711 .await?
712 .map(Into::into))
713 }
714
715 pub async fn block_by_height(&self, height: BlockHeight) -> Result<Option<Block>> {
716 Ok(self
717 .uncached_client()
718 .block_by_height(height)
719 .await?
720 .map(Into::into))
721 }
722
723 pub async fn get_blocks(
725 &self,
726 request: PaginationRequest<String>,
727 ) -> Result<PaginatedResult<Block, String>> {
728 let pr = self.uncached_client().blocks(request).await?;
729
730 Ok(PaginatedResult {
731 cursor: pr.cursor,
732 results: pr.results.into_iter().map(Into::into).collect(),
733 has_next_page: pr.has_next_page,
734 has_previous_page: pr.has_previous_page,
735 })
736 }
737
738 pub async fn estimate_transaction_cost<T: Transaction>(
739 &self,
740 mut tx: T,
741 tolerance: Option<f64>,
742 block_horizon: Option<u32>,
743 ) -> Result<TransactionCost> {
744 let block_horizon = block_horizon.unwrap_or(DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON);
745 let tolerance = tolerance.unwrap_or(DEFAULT_GAS_ESTIMATION_TOLERANCE);
746
747 let EstimateGasPrice { gas_price, .. } = self.estimate_gas_price(block_horizon).await?;
748
749 let gas_used = self
750 .get_gas_used_with_tolerance(tx.clone(), tolerance)
751 .await?;
752
753 if tx.is_using_predicates() {
754 tx.estimate_predicates(self, None).await?;
755 }
756
757 let transaction_fee = tx
758 .clone()
759 .fee_checked_from_tx(&self.consensus_parameters().await?, gas_price)
760 .expect("Error calculating TransactionFee");
761
762 Ok(TransactionCost {
763 gas_price,
764 gas_used,
765 metered_bytes_size: tx.metered_bytes_size() as u64,
766 total_fee: transaction_fee.max_fee(),
767 })
768 }
769
770 async fn get_gas_used_with_tolerance<T: Transaction>(
772 &self,
773 tx: T,
774 tolerance: f64,
775 ) -> Result<u64> {
776 let receipts = self.dry_run_opt(tx, false, None).await?.take_receipts();
777 let gas_used = self.get_script_gas_used(&receipts);
778
779 Ok((gas_used as f64 * (1.0 + tolerance)).ceil() as u64)
780 }
781
782 fn get_script_gas_used(&self, receipts: &[Receipt]) -> u64 {
783 receipts
784 .iter()
785 .rfind(|r| matches!(r, Receipt::ScriptResult { .. }))
786 .map(|script_result| {
787 script_result
788 .gas_used()
789 .expect("could not retrieve gas used from ScriptResult")
790 })
791 .unwrap_or(0)
792 }
793
794 pub async fn get_messages(&self, from: &Bech32Address) -> Result<Vec<Message>> {
795 let mut messages = Vec::new();
796 let mut cursor = None;
797
798 loop {
799 let response = self
800 .uncached_client()
801 .messages(
802 Some(&from.into()),
803 PaginationRequest {
804 cursor: cursor.clone(),
805 results: NUM_RESULTS_PER_REQUEST,
806 direction: PageDirection::Forward,
807 },
808 )
809 .await?;
810
811 if response.results.is_empty() {
812 break;
813 }
814
815 messages.extend(response.results.into_iter().map(Into::into));
816 cursor = response.cursor;
817 }
818
819 Ok(messages)
820 }
821
822 pub async fn get_message_proof(
823 &self,
824 tx_id: &TxId,
825 nonce: &Nonce,
826 commit_block_id: Option<&Bytes32>,
827 commit_block_height: Option<u32>,
828 ) -> Result<MessageProof> {
829 self.uncached_client()
830 .message_proof(
831 tx_id,
832 nonce,
833 commit_block_id.map(Into::into),
834 commit_block_height.map(Into::into),
835 )
836 .await
837 .map(Into::into)
838 .map_err(Into::into)
839 }
840
841 pub async fn is_user_account(&self, address: impl Into<Bytes32>) -> Result<bool> {
842 self.uncached_client()
843 .is_user_account(*address.into())
844 .await
845 }
846
847 pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
848 self.uncached_client_mut().set_retry_config(retry_config);
849
850 self
851 }
852
853 pub async fn contract_exists(&self, contract_id: &Bech32ContractId) -> Result<bool> {
854 Ok(self
855 .uncached_client()
856 .contract_exists(&contract_id.into())
857 .await?)
858 }
859
860 fn uncached_client(&self) -> &RetryableClient {
861 self.cached_client.inner()
862 }
863
864 fn uncached_client_mut(&mut self) -> &mut RetryableClient {
865 self.cached_client.inner_mut()
866 }
867}
868
869#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
870impl DryRunner for Provider {
871 async fn dry_run(&self, tx: FuelTransaction) -> Result<DryRun> {
872 let [tx_execution_status] = self
873 .uncached_client()
874 .dry_run_opt(&vec![tx], Some(false), Some(0))
875 .await?
876 .try_into()
877 .expect("should have only one element");
878
879 let receipts = tx_execution_status.result.receipts();
880 let script_gas = self.get_script_gas_used(receipts);
881
882 let variable_outputs = receipts
883 .iter()
884 .filter(
885 |receipt| matches!(receipt, Receipt::TransferOut { amount, .. } if *amount != 0),
886 )
887 .count();
888
889 let succeeded = matches!(
890 tx_execution_status.result,
891 TransactionExecutionResult::Success { .. }
892 );
893
894 let dry_run = DryRun {
895 succeeded,
896 script_gas,
897 variable_outputs,
898 };
899
900 Ok(dry_run)
901 }
902
903 async fn estimate_gas_price(&self, block_horizon: u32) -> Result<u64> {
904 Ok(self.estimate_gas_price(block_horizon).await?.gas_price)
905 }
906
907 async fn estimate_predicates(
908 &self,
909 tx: &FuelTransaction,
910 _latest_chain_executor_version: Option<u32>,
911 ) -> Result<FuelTransaction> {
912 Ok(self.uncached_client().estimate_predicates(tx).await?)
913 }
914
915 async fn consensus_parameters(&self) -> Result<ConsensusParameters> {
916 Provider::consensus_parameters(self).await
917 }
918}