thor_devkit/
network.rs

1//! Module for interacting with node HTTP APIs.
2
3use crate::rlp::{Bytes, Decodable};
4use crate::transactions::Reserved;
5use crate::transactions::{Clause, Transaction};
6use crate::utils::unhex;
7use crate::{Address, U256};
8use reqwest::{Client, Url};
9use serde::{Deserialize, Serialize};
10
11/// Generic result of all asynchronous calls in this module.
12pub type AResult<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
13
14/// Validation errors (not related to HTTP failures)
15#[derive(Clone, Debug, Eq, PartialEq)]
16#[non_exhaustive]
17pub enum ValidationError {
18    /// Account storage keys start from one, there's no key 0.
19    ZeroStorageKey,
20    /// Transaction broadcast failed
21    BroadcastFailed(String),
22    /// Unexpected failure
23    Unknown(String),
24}
25
26impl std::error::Error for ValidationError {}
27impl std::fmt::Display for ValidationError {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Self::ZeroStorageKey => f.write_str("Account storage key cannot be zero"),
31            Self::BroadcastFailed(text) => {
32                f.write_str("Failed to broadcast: ")?;
33                f.write_str(text.strip_suffix('\n').unwrap_or(text))
34            }
35            Self::Unknown(text) => {
36                f.write_str("Unknown error: ")?;
37                f.write_str(text.strip_suffix('\n').unwrap_or(text))
38            }
39        }
40    }
41}
42
43/// A simple HTTP REST client for a VeChain node.
44#[derive(Clone, Debug)]
45pub struct ThorNode {
46    /// API base url
47    pub base_url: Url,
48    /// Chain tag used for this network.
49    pub chain_tag: u8,
50}
51
52#[serde_with::serde_as]
53#[derive(Deserialize)]
54struct RawTxResponse {
55    #[serde_as(as = "unhex::Hex")]
56    raw: Bytes,
57    meta: Option<TransactionMeta>,
58}
59
60/// Extended transaction data
61#[serde_with::serde_as]
62#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
63pub struct ExtendedTransaction {
64    /// Identifier of the transaction
65    #[serde_as(as = "unhex::HexNum<32, U256>")]
66    pub id: U256,
67    /// The one who signed the transaction
68    pub origin: Address,
69    /// The delegator who paid the gas fee
70    pub delegator: Option<Address>,
71    /// Byte size of the transaction that is RLP encoded
72    pub size: u32,
73    /// Last byte of genesis block ID
74    #[serde(rename = "chainTag")]
75    pub chain_tag: u8,
76    /// 8 bytes prefix of some block ID
77    #[serde(rename = "blockRef")]
78    #[serde_as(as = "unhex::HexNum<8, u64>")]
79    pub block_ref: u64,
80    /// Expiration relative to blockRef, in unit block
81    pub expiration: u32,
82    /// Transaction clauses
83    pub clauses: Vec<Clause>,
84    /// Coefficient used to calculate the final gas price
85    #[serde(rename = "gasPriceCoef")]
86    pub gas_price_coef: u8,
87    /// Max amount of gas can be consumed to execute this transaction
88    pub gas: u64,
89    /// ID of the transaction on which the current transaction depends on. can be null.
90    #[serde(rename = "dependsOn")]
91    #[serde_as(as = "Option<unhex::HexNum<32, U256>>")]
92    pub depends_on: Option<U256>,
93    /// Transaction nonce
94    #[serde_as(as = "unhex::HexNum<8, u64>")]
95    pub nonce: u64,
96}
97
98impl ExtendedTransaction {
99    pub fn as_transaction(self) -> Transaction {
100        //! Convert to package-compatible [`Transaction`]
101        let Self {
102            chain_tag,
103            block_ref,
104            expiration,
105            clauses,
106            gas_price_coef,
107            gas,
108            depends_on,
109            nonce,
110            delegator,
111            ..
112        } = self;
113        Transaction {
114            chain_tag,
115            block_ref,
116            expiration,
117            clauses,
118            gas_price_coef,
119            gas,
120            depends_on,
121            nonce,
122            reserved: if delegator.is_some() {
123                Some(Reserved::new_delegated())
124            } else {
125                None
126            },
127            signature: None,
128        }
129    }
130}
131
132#[serde_with::serde_as]
133#[derive(Deserialize)]
134struct ExtendedTransactionResponse {
135    #[serde(flatten)]
136    transaction: ExtendedTransaction,
137    meta: Option<TransactionMeta>,
138}
139
140/// Transaction metadata
141#[serde_with::serde_as]
142#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
143pub struct TransactionMeta {
144    /// Block identifier
145    #[serde(rename = "blockID")]
146    #[serde_as(as = "unhex::HexNum<32, U256>")]
147    pub block_id: U256,
148    /// Block number (height)
149    #[serde(rename = "blockNumber")]
150    pub block_number: u32,
151    /// Block unix timestamp
152    #[serde(rename = "blockTimestamp")]
153    pub block_timestamp: u32,
154}
155
156/// Transaction receipt
157#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
158pub struct Receipt {
159    /// Amount of gas consumed by this transaction
160    #[serde(rename = "gasUsed")]
161    pub gas_used: u32,
162    /// Address of account who paid used gas
163    #[serde(rename = "gasPayer")]
164    pub gas_payer: Address,
165    /// Hex form of amount of paid energy
166    pub paid: U256,
167    /// Hex form of amount of reward
168    pub reward: U256,
169    /// true means the transaction was reverted
170    pub reverted: bool,
171    /// Outputs (if this transaction was a contract call)
172    pub outputs: Vec<ReceiptOutput>,
173}
174
175#[derive(Clone, Debug, PartialEq, Deserialize)]
176struct ReceiptResponse {
177    #[serde(flatten)]
178    body: Receipt,
179    meta: ReceiptMeta,
180}
181
182/// Single output in the transaction receipt
183#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
184pub struct ReceiptOutput {
185    /// Deployed contract address, if the corresponding clause is a contract deployment clause
186    #[serde(rename = "contractAddress")]
187    pub contract_address: Option<Address>,
188    /// Emitted contract events
189    pub events: Vec<Event>,
190    /// Transfers executed during the contract call
191    pub transfers: Vec<Transfer>,
192}
193
194/// Transaction receipt metadata
195#[serde_with::serde_as]
196#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
197pub struct ReceiptMeta {
198    /// Block identifier
199    #[serde(rename = "blockID")]
200    #[serde_as(as = "unhex::HexNum<32, U256>")]
201    pub block_id: U256,
202    /// Block number (height)
203    #[serde(rename = "blockNumber")]
204    pub block_number: u32,
205    /// Block unix timestamp
206    #[serde(rename = "blockTimestamp")]
207    pub block_timestamp: u32,
208    /// Transaction identifier
209    #[serde(rename = "txID")]
210    #[serde_as(as = "unhex::HexNum<32, U256>")]
211    pub tx_id: U256,
212    /// Transaction origin (signer)
213    #[serde(rename = "txOrigin")]
214    pub tx_origin: Address,
215}
216
217/// Emitted contract event
218#[serde_with::serde_as]
219#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
220pub struct Event {
221    /// The address of contract which produces the event
222    pub address: Address,
223    /// Event topics
224    #[serde_as(as = "Vec<unhex::HexNum<32, U256>>")]
225    pub topics: Vec<U256>,
226    /// Event data
227    #[serde_as(as = "unhex::Hex")]
228    pub data: Bytes,
229}
230
231/// Single transfer during the contract call
232#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
233pub struct Transfer {
234    /// Address that sends tokens
235    pub sender: Address,
236    /// Address that receives tokens
237    pub recipient: Address,
238    /// Amount of tokens
239    pub amount: U256,
240}
241
242/// A blockchain block.
243#[serde_with::serde_as]
244#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
245pub struct BlockInfo {
246    /// Block number (height)
247    pub number: u32,
248    /// Block identifier
249    #[serde_as(as = "unhex::HexNum<32, U256>")]
250    pub id: U256,
251    /// RLP encoded block size in bytes
252    pub size: u32,
253    /// Parent block ID
254    #[serde_as(as = "unhex::HexNum<32, U256>")]
255    #[serde(rename = "parentID")]
256    pub parent_id: U256,
257    /// Block unix timestamp
258    pub timestamp: u32,
259    /// Block gas limit (max allowed accumulative gas usage of transactions)
260    #[serde(rename = "gasLimit")]
261    pub gas_limit: u32,
262    /// Address of account to receive block reward
263    pub beneficiary: Address,
264    /// Accumulative gas usage of transactions
265    #[serde(rename = "gasUsed")]
266    pub gas_used: u32,
267    /// Sum of all ancestral blocks' score
268    #[serde(rename = "totalScore")]
269    pub total_score: u32,
270    /// Root hash of transactions in the block
271    #[serde_as(as = "unhex::HexNum<32, U256>")]
272    #[serde(rename = "txsRoot")]
273    pub txs_root: U256,
274    /// Supported txs features bitset
275    #[serde(rename = "txsFeatures")]
276    pub txs_features: u32,
277    /// Root hash of accounts state
278    #[serde_as(as = "unhex::HexNum<32, U256>")]
279    #[serde(rename = "stateRoot")]
280    pub state_root: U256,
281    /// Root hash of transaction receipts
282    #[serde_as(as = "unhex::HexNum<32, U256>")]
283    #[serde(rename = "receiptsRoot")]
284    pub receipts_root: U256,
285    /// Is in trunk?
286    #[serde(rename = "isTrunk")]
287    pub is_trunk: bool,
288    /// Is finalized?
289    #[serde(rename = "isFinalized")]
290    pub is_finalized: bool,
291    /// Whether the block signer voted COM(Commit) in BFT
292    pub com: bool,
293    /// The one who signed this block
294    pub signer: Address,
295}
296
297impl BlockInfo {
298    pub const fn block_ref(&self) -> u64 {
299        //! Extract blockRef for transaction.
300        self.id.0[3]
301    }
302}
303
304/// Transaction data included in the block extended details.
305///
306/// Combines [`ExtendedTransaction`] and [`Receipt`].
307#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
308pub struct BlockTransaction {
309    /// Transaction details
310    #[serde(flatten)]
311    pub transaction: ExtendedTransaction,
312    /// Transaction receipt
313    #[serde(flatten)]
314    pub receipt: Receipt,
315}
316
317#[serde_with::serde_as]
318#[derive(Clone, Debug, PartialEq, Deserialize)]
319struct BlockResponse {
320    #[serde(flatten)]
321    base: BlockInfo,
322    #[serde_as(as = "Vec<unhex::HexNum<32, U256>>")]
323    transactions: Vec<U256>,
324}
325
326#[serde_with::serde_as]
327#[derive(Clone, Debug, PartialEq, Deserialize)]
328struct BlockExtendedResponse {
329    #[serde(flatten)]
330    base: BlockInfo,
331    transactions: Vec<BlockTransaction>,
332}
333
334/// Block reference: a way to identify the block on the chain.
335#[derive(Clone, Debug, Eq, PartialEq)]
336pub enum BlockReference {
337    /// Latest: already approved by some node, but not finalized yet.
338    Best,
339    /// Finalized: block is frozen on chain.
340    Finalized,
341    /// Block ordinal number (1..)
342    Number(u64),
343    /// Block ID
344    ID(U256),
345}
346
347impl BlockReference {
348    fn as_query_param(&self) -> String {
349        match self {
350            BlockReference::Best => "best".to_string(),
351            BlockReference::Finalized => "finalized".to_string(),
352            BlockReference::Number(num) => format!("0x{:02x}", num),
353            BlockReference::ID(id) => format!("0x{:064x}", id),
354        }
355    }
356}
357
358/// Account details
359#[serde_with::serde_as]
360#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
361pub struct AccountInfo {
362    /// VET balance
363    #[serde_as(as = "unhex::HexNum<32, U256>")]
364    pub balance: U256,
365    /// VTHO balance
366    #[serde_as(as = "unhex::HexNum<32, U256>")]
367    pub energy: U256,
368    /// Is a contract?
369    #[serde(rename = "hasCode")]
370    pub has_code: bool,
371}
372
373#[serde_with::serde_as]
374#[derive(Clone, Debug, PartialEq, Deserialize)]
375struct AccountCodeResponse {
376    #[serde_as(as = "unhex::Hex")]
377    code: Bytes,
378}
379#[serde_with::serde_as]
380#[derive(Clone, Debug, PartialEq, Deserialize)]
381struct AccountStorageResponse {
382    #[serde_as(as = "unhex::HexNum<32, U256>")]
383    value: U256,
384}
385#[serde_with::serde_as]
386#[derive(Clone, Debug, PartialEq, Serialize)]
387struct TransactionBroadcastRequest {
388    #[serde_as(as = "unhex::Hex")]
389    raw: Bytes,
390}
391#[serde_with::serde_as]
392#[derive(Clone, Debug, PartialEq, Deserialize)]
393struct TransactionIdResponse {
394    #[serde_as(as = "unhex::HexNum<32, U256>")]
395    id: U256,
396}
397
398/// Transaction execution simulation request
399#[serde_with::serde_as]
400#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
401pub struct SimulateCallRequest {
402    /// Clauses of transaction
403    pub clauses: Vec<Clause>,
404    /// Maximal amount of gas
405    pub gas: u64,
406    /// Gas price
407    #[serde_as(as = "serde_with::DisplayFromStr")]
408    #[serde(rename = "gasPrice")]
409    pub gas_price: u64,
410    /// Caller address
411    pub caller: Address,
412    /// ???
413    #[serde_as(as = "serde_with::DisplayFromStr")]
414    #[serde(rename = "provedWork")]
415    pub proved_work: u64,
416    /// Gas payer address
417    #[serde(rename = "gasPayer")]
418    pub gas_payer: Address,
419    /// Expiration (in blocks)
420    pub expiration: u32,
421    /// Block reference to count expiration from.
422    #[serde_as(as = "unhex::HexNum<8, u64>")]
423    #[serde(rename = "blockRef")]
424    pub block_ref: u64,
425}
426
427/// `eth_call` (pure or view function call without on-chain transaction) request
428#[serde_with::serde_as]
429#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
430pub struct EthCallRequest {
431    /// Clauses of transaction
432    pub clauses: Vec<Clause>,
433    /// Maximal amount of gas
434    pub gas: Option<u64>,
435    /// Gas price
436    #[serde(rename = "gasPrice")]
437    pub gas_price: Option<u64>,
438    /// Caller address
439    pub caller: Option<Address>,
440}
441
442impl EthCallRequest {
443    pub fn from_clause(clause: Clause) -> Self {
444        //! Shortcut for a single clause request.
445        return Self {
446            clauses: vec![clause],
447            gas: None,
448            gas_price: None,
449            caller: None,
450        };
451    }
452}
453
454/// Transaction execution simulation request
455#[serde_with::serde_as]
456#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
457pub struct SimulateCallResponse {
458    /// Output data
459    #[serde_as(as = "unhex::Hex")]
460    pub data: Bytes,
461    /// Emitted events
462    pub events: Vec<Event>,
463    /// Executed transfers
464    pub transfers: Vec<Transfer>,
465    /// Gas spent
466    #[serde(rename = "gasUsed")]
467    pub gas_used: u64,
468    /// Will be reverted?
469    pub reverted: bool,
470    /// Error description returned by VM
471    #[serde(rename = "vmError")]
472    pub vm_error: String,
473}
474
475impl ThorNode {
476    /// Chain tag for mainnet
477    pub const MAINNET_CHAIN_TAG: u8 = 0x4A;
478    /// REST API URL for mainnet (one possible)
479    pub const MAINNET_BASE_URL: &'static str = "https://mainnet.vecha.in/";
480    /// Chain tag for testnet
481    pub const TESTNET_CHAIN_TAG: u8 = 0x27;
482    /// REST API URL for testnet (one possible)
483    pub const TESTNET_BASE_URL: &'static str = "https://testnet.vecha.in/";
484
485    pub fn mainnet() -> Self {
486        //! Mainnet parameters
487        Self {
488            base_url: Self::MAINNET_BASE_URL.parse().unwrap(),
489            chain_tag: Self::MAINNET_CHAIN_TAG,
490        }
491    }
492
493    pub fn testnet() -> Self {
494        //! Testnet parameters
495        Self {
496            base_url: Self::TESTNET_BASE_URL.parse().unwrap(),
497            chain_tag: Self::TESTNET_CHAIN_TAG,
498        }
499    }
500
501    pub async fn fetch_transaction(
502        &self,
503        transaction_id: U256,
504    ) -> AResult<Option<(Transaction, Option<TransactionMeta>)>> {
505        //! Retrieve a [`Transaction`] from node by its ID.
506        //!
507        //! Returns [`None`] for nonexistent transactions.
508        //!
509        //! Meta can be [`None`] if a transaction was broadcasted, but
510        //! not yet included into a block.
511        //!
512        //! This method exists for interoperability with [`Transaction`]
513        //! from other parts of library. You can get more info from node
514        //! with [`ThorNode::fetch_extended_transaction`].
515        let client = Client::new();
516        let path = format!("/transactions/0x{:064x}", transaction_id);
517        let response = client
518            .get(self.base_url.join(&path)?)
519            .query(&[("raw", "true")])
520            .send()
521            .await?
522            .text()
523            .await?;
524        if response.strip_suffix('\n').unwrap_or(&response) == "null" {
525            Ok(None)
526        } else {
527            let decoded: RawTxResponse = serde_json::from_str(&response)?;
528            let tx = Transaction::decode(&mut &decoded.raw[..])?;
529            Ok(Some((tx, decoded.meta)))
530        }
531    }
532
533    pub async fn fetch_extended_transaction(
534        &self,
535        transaction_id: U256,
536    ) -> AResult<Option<(ExtendedTransaction, Option<TransactionMeta>)>> {
537        //! Retrieve a [`Transaction`] from node by its ID.
538        //!
539        //! Returns [`None`] for nonexistent transactions.
540        //!
541        //! Meta can be [`None`] if a transaction was broadcasted, but
542        //! not yet included into a block.
543        //!
544        //! This method returns more data than [`ThorNode::fetch_transaction`],
545        //! but is not interoperable with [`Transaction`].
546        let client = Client::new();
547        let path = format!("/transactions/0x{:064x}", transaction_id);
548        let response = client
549            .get(self.base_url.join(&path)?)
550            .send()
551            .await?
552            .text()
553            .await?;
554        if response.strip_suffix('\n').unwrap_or(&response) == "null" {
555            Ok(None)
556        } else {
557            let decoded: ExtendedTransactionResponse = serde_json::from_str(&response)?;
558            Ok(Some((decoded.transaction, decoded.meta)))
559        }
560    }
561
562    pub async fn fetch_transaction_receipt(
563        &self,
564        transaction_id: U256,
565    ) -> AResult<Option<(Receipt, ReceiptMeta)>> {
566        //! Retrieve a transaction receipt from node given a transaction ID.
567        //!
568        //! Returns [`None`] for nonexistent or not mined transactions.
569        let client = Client::new();
570        let path = format!("/transactions/0x{:064x}/receipt", transaction_id);
571        let response = client
572            .get(self.base_url.join(&path)?)
573            .send()
574            .await?
575            .text()
576            .await?;
577        if response.strip_suffix('\n').unwrap_or(&response) == "null" {
578            Ok(None)
579        } else {
580            let decoded: ReceiptResponse = serde_json::from_str(&response)?;
581            Ok(Some((decoded.body, decoded.meta)))
582        }
583    }
584
585    pub async fn fetch_block(
586        &self,
587        block_ref: BlockReference,
588    ) -> AResult<Option<(BlockInfo, Vec<U256>)>> {
589        //! Retrieve a block from node by given identifier.
590        //!
591        //! Returns [`None`] for nonexistent blocks.
592        let client = Client::new();
593        let path = format!("/blocks/{}", block_ref.as_query_param());
594        let response = client
595            .get(self.base_url.join(&path)?)
596            .send()
597            .await?
598            .text()
599            .await?;
600        if response.strip_suffix('\n').unwrap_or(&response) == "null" {
601            Ok(None)
602        } else {
603            let decoded: BlockResponse = serde_json::from_str(&response)?;
604            Ok(Some((decoded.base, decoded.transactions)))
605        }
606    }
607
608    pub async fn fetch_best_block(&self) -> AResult<(BlockInfo, Vec<U256>)> {
609        //! Retrieve a best block from node.
610        let info = self.fetch_block(BlockReference::Best).await?;
611        Ok(info.ok_or(ValidationError::Unknown("Best block not found".to_string()))?)
612    }
613
614    pub async fn fetch_block_expanded(
615        &self,
616        block_ref: BlockReference,
617    ) -> AResult<Option<(BlockInfo, Vec<BlockTransaction>)>> {
618        //! Retrieve a block from node by given identifier together with extended
619        //! transaction details.
620        //!
621        //! Returns [`None`] for nonexistent blocks.
622        let client = Client::new();
623        let path = format!("/blocks/{}", block_ref.as_query_param());
624        let response = client
625            .get(self.base_url.join(&path)?)
626            .query(&[("expanded", "true")])
627            .send()
628            .await?
629            .text()
630            .await?;
631        if response.strip_suffix('\n').unwrap_or(&response) == "null" {
632            Ok(None)
633        } else {
634            let decoded: BlockExtendedResponse = serde_json::from_str(&response)?;
635            Ok(Some((decoded.base, decoded.transactions)))
636        }
637    }
638
639    pub async fn broadcast_transaction(&self, transaction: &Transaction) -> AResult<U256> {
640        //! Broadcast a new [`Transaction`] to the node.
641        let client = Client::new();
642        let response = client
643            .post(self.base_url.join("/transactions")?)
644            .json(&TransactionBroadcastRequest {
645                raw: transaction.to_broadcastable_bytes()?,
646            })
647            .send()
648            .await?
649            .text()
650            .await?;
651        let decoded: TransactionIdResponse = serde_json::from_str(&response)
652            .map_err(|_| ValidationError::BroadcastFailed(response.to_string()))?;
653        Ok(decoded.id)
654    }
655
656    pub async fn fetch_account(&self, address: Address) -> AResult<AccountInfo> {
657        //! Retrieve account details.
658        let client = Client::new();
659        let path = format!("/accounts/{}", address.to_hex());
660        Ok(client
661            .get(self.base_url.join(&path)?)
662            .send()
663            .await?
664            .json::<AccountInfo>()
665            .await?)
666    }
667
668    pub async fn fetch_account_code(&self, address: Address) -> AResult<Option<Bytes>> {
669        //! Retrieve account code.
670        //!
671        //! Returns [`None`] for non-contract accounts.
672        let client = Client::new();
673        let path = format!("/accounts/{}/code", address.to_hex());
674        let response = client
675            .get(self.base_url.join(&path)?)
676            .send()
677            .await?
678            .json::<AccountCodeResponse>()
679            .await?;
680        if response.code.is_empty() {
681            Ok(None)
682        } else {
683            Ok(Some(response.code))
684        }
685    }
686
687    pub async fn fetch_account_storage(&self, address: Address, key: U256) -> AResult<U256> {
688        //! Retrieve account storage at key.
689        //!
690        //! Returns [`None`] for non-contract accounts or for missing storage keys.
691        if key == 0.into() {
692            return Err(Box::new(ValidationError::ZeroStorageKey));
693        }
694        let client = Client::new();
695        let path = format!("/accounts/{}/storage/0x{:064x}", address.to_hex(), key);
696        let response = client
697            .get(self.base_url.join(&path)?)
698            .send()
699            .await?
700            .json::<AccountStorageResponse>()
701            .await?;
702        Ok(response.value)
703    }
704
705    pub async fn simulate_execution(
706        &self,
707        request: SimulateCallRequest,
708    ) -> AResult<Vec<SimulateCallResponse>> {
709        //! Simulate a transaction execution.
710        //!
711        //! This is an equivalent of eth_call and can be used to call `pure` and
712        //! `view` functions without broadcasting a transaction. See
713        //! [`eth_call`] for a better interface
714        let client = Client::new();
715        let response = client
716            .post(self.base_url.join("/accounts/*")?)
717            .json(&request)
718            .send()
719            .await?
720            .json::<Vec<SimulateCallResponse>>()
721            .await?;
722        Ok(response)
723    }
724
725    pub async fn eth_call_advanced(
726        &self,
727        request: EthCallRequest,
728        block_ref: BlockReference,
729    ) -> AResult<Vec<SimulateCallResponse>> {
730        //! Call a `pure` or `view` function as defined by `clause.data`,
731        //! possibly providing additional options.
732        let client = Client::new();
733        let response = client
734            .post(self.base_url.join("/accounts/*")?)
735            .query(&[("revision", block_ref.as_query_param())])
736            .json(&request)
737            .send()
738            .await?
739            .json::<Vec<SimulateCallResponse>>()
740            .await?;
741        Ok(response)
742    }
743
744    pub async fn eth_call(&self, clause: Clause, block_ref: BlockReference) -> AResult<Bytes> {
745        //! Call a `pure` or `view` function as defined by `clause.data`.
746        //!
747        //! Returns byte representation of the returned data, error on revert
748        //! or when unexpected payload is returned
749        let mut response = self
750            .eth_call_advanced(EthCallRequest::from_clause(clause), block_ref)
751            .await?;
752        if response.len() > 1 {
753            return Err("Multiple responses".into());
754        } else if response.is_empty() {
755            return Err("Empty response".into());
756        }
757        let tx = response.remove(0);
758        if tx.reverted {
759            return Err("Transaction reverted".into());
760        }
761        Ok(tx.data)
762    }
763}