1use 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
11pub type AResult<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
13
14#[derive(Clone, Debug, Eq, PartialEq)]
16#[non_exhaustive]
17pub enum ValidationError {
18 ZeroStorageKey,
20 BroadcastFailed(String),
22 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#[derive(Clone, Debug)]
45pub struct ThorNode {
46 pub base_url: Url,
48 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#[serde_with::serde_as]
62#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
63pub struct ExtendedTransaction {
64 #[serde_as(as = "unhex::HexNum<32, U256>")]
66 pub id: U256,
67 pub origin: Address,
69 pub delegator: Option<Address>,
71 pub size: u32,
73 #[serde(rename = "chainTag")]
75 pub chain_tag: u8,
76 #[serde(rename = "blockRef")]
78 #[serde_as(as = "unhex::HexNum<8, u64>")]
79 pub block_ref: u64,
80 pub expiration: u32,
82 pub clauses: Vec<Clause>,
84 #[serde(rename = "gasPriceCoef")]
86 pub gas_price_coef: u8,
87 pub gas: u64,
89 #[serde(rename = "dependsOn")]
91 #[serde_as(as = "Option<unhex::HexNum<32, U256>>")]
92 pub depends_on: Option<U256>,
93 #[serde_as(as = "unhex::HexNum<8, u64>")]
95 pub nonce: u64,
96}
97
98impl ExtendedTransaction {
99 pub fn as_transaction(self) -> Transaction {
100 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#[serde_with::serde_as]
142#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
143pub struct TransactionMeta {
144 #[serde(rename = "blockID")]
146 #[serde_as(as = "unhex::HexNum<32, U256>")]
147 pub block_id: U256,
148 #[serde(rename = "blockNumber")]
150 pub block_number: u32,
151 #[serde(rename = "blockTimestamp")]
153 pub block_timestamp: u32,
154}
155
156#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
158pub struct Receipt {
159 #[serde(rename = "gasUsed")]
161 pub gas_used: u32,
162 #[serde(rename = "gasPayer")]
164 pub gas_payer: Address,
165 pub paid: U256,
167 pub reward: U256,
169 pub reverted: bool,
171 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#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
184pub struct ReceiptOutput {
185 #[serde(rename = "contractAddress")]
187 pub contract_address: Option<Address>,
188 pub events: Vec<Event>,
190 pub transfers: Vec<Transfer>,
192}
193
194#[serde_with::serde_as]
196#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
197pub struct ReceiptMeta {
198 #[serde(rename = "blockID")]
200 #[serde_as(as = "unhex::HexNum<32, U256>")]
201 pub block_id: U256,
202 #[serde(rename = "blockNumber")]
204 pub block_number: u32,
205 #[serde(rename = "blockTimestamp")]
207 pub block_timestamp: u32,
208 #[serde(rename = "txID")]
210 #[serde_as(as = "unhex::HexNum<32, U256>")]
211 pub tx_id: U256,
212 #[serde(rename = "txOrigin")]
214 pub tx_origin: Address,
215}
216
217#[serde_with::serde_as]
219#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
220pub struct Event {
221 pub address: Address,
223 #[serde_as(as = "Vec<unhex::HexNum<32, U256>>")]
225 pub topics: Vec<U256>,
226 #[serde_as(as = "unhex::Hex")]
228 pub data: Bytes,
229}
230
231#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
233pub struct Transfer {
234 pub sender: Address,
236 pub recipient: Address,
238 pub amount: U256,
240}
241
242#[serde_with::serde_as]
244#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
245pub struct BlockInfo {
246 pub number: u32,
248 #[serde_as(as = "unhex::HexNum<32, U256>")]
250 pub id: U256,
251 pub size: u32,
253 #[serde_as(as = "unhex::HexNum<32, U256>")]
255 #[serde(rename = "parentID")]
256 pub parent_id: U256,
257 pub timestamp: u32,
259 #[serde(rename = "gasLimit")]
261 pub gas_limit: u32,
262 pub beneficiary: Address,
264 #[serde(rename = "gasUsed")]
266 pub gas_used: u32,
267 #[serde(rename = "totalScore")]
269 pub total_score: u32,
270 #[serde_as(as = "unhex::HexNum<32, U256>")]
272 #[serde(rename = "txsRoot")]
273 pub txs_root: U256,
274 #[serde(rename = "txsFeatures")]
276 pub txs_features: u32,
277 #[serde_as(as = "unhex::HexNum<32, U256>")]
279 #[serde(rename = "stateRoot")]
280 pub state_root: U256,
281 #[serde_as(as = "unhex::HexNum<32, U256>")]
283 #[serde(rename = "receiptsRoot")]
284 pub receipts_root: U256,
285 #[serde(rename = "isTrunk")]
287 pub is_trunk: bool,
288 #[serde(rename = "isFinalized")]
290 pub is_finalized: bool,
291 pub com: bool,
293 pub signer: Address,
295}
296
297impl BlockInfo {
298 pub const fn block_ref(&self) -> u64 {
299 self.id.0[3]
301 }
302}
303
304#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
308pub struct BlockTransaction {
309 #[serde(flatten)]
311 pub transaction: ExtendedTransaction,
312 #[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#[derive(Clone, Debug, Eq, PartialEq)]
336pub enum BlockReference {
337 Best,
339 Finalized,
341 Number(u64),
343 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#[serde_with::serde_as]
360#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
361pub struct AccountInfo {
362 #[serde_as(as = "unhex::HexNum<32, U256>")]
364 pub balance: U256,
365 #[serde_as(as = "unhex::HexNum<32, U256>")]
367 pub energy: U256,
368 #[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#[serde_with::serde_as]
400#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
401pub struct SimulateCallRequest {
402 pub clauses: Vec<Clause>,
404 pub gas: u64,
406 #[serde_as(as = "serde_with::DisplayFromStr")]
408 #[serde(rename = "gasPrice")]
409 pub gas_price: u64,
410 pub caller: Address,
412 #[serde_as(as = "serde_with::DisplayFromStr")]
414 #[serde(rename = "provedWork")]
415 pub proved_work: u64,
416 #[serde(rename = "gasPayer")]
418 pub gas_payer: Address,
419 pub expiration: u32,
421 #[serde_as(as = "unhex::HexNum<8, u64>")]
423 #[serde(rename = "blockRef")]
424 pub block_ref: u64,
425}
426
427#[serde_with::serde_as]
429#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
430pub struct EthCallRequest {
431 pub clauses: Vec<Clause>,
433 pub gas: Option<u64>,
435 #[serde(rename = "gasPrice")]
437 pub gas_price: Option<u64>,
438 pub caller: Option<Address>,
440}
441
442impl EthCallRequest {
443 pub fn from_clause(clause: Clause) -> Self {
444 return Self {
446 clauses: vec![clause],
447 gas: None,
448 gas_price: None,
449 caller: None,
450 };
451 }
452}
453
454#[serde_with::serde_as]
456#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
457pub struct SimulateCallResponse {
458 #[serde_as(as = "unhex::Hex")]
460 pub data: Bytes,
461 pub events: Vec<Event>,
463 pub transfers: Vec<Transfer>,
465 #[serde(rename = "gasUsed")]
467 pub gas_used: u64,
468 pub reverted: bool,
470 #[serde(rename = "vmError")]
472 pub vm_error: String,
473}
474
475impl ThorNode {
476 pub const MAINNET_CHAIN_TAG: u8 = 0x4A;
478 pub const MAINNET_BASE_URL: &'static str = "https://mainnet.vecha.in/";
480 pub const TESTNET_CHAIN_TAG: u8 = 0x27;
482 pub const TESTNET_BASE_URL: &'static str = "https://testnet.vecha.in/";
484
485 pub fn mainnet() -> Self {
486 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}