df_hyperliquid_core/client/evm/
records.rs1use crate::account::Signature;
6use crate::client::evm::tx::{AccessItem, EIP1559, Eip1559};
7use crate::client::evm::u256::{B256, U256};
8use crate::utils::{Address, Figure};
9use serde::{Deserialize, Deserializer, de};
10
11#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct Receipt {
15 pub transaction_hash: B256,
16 #[serde(default, deserialize_with = "opt_quantity")]
17 pub transaction_index: Option<u64>,
18 #[serde(default)]
19 pub block_hash: Option<B256>,
20 #[serde(default, deserialize_with = "opt_quantity")]
21 pub block_number: Option<u64>,
22 pub from: Address,
23 #[serde(default)]
25 pub to: Option<Address>,
26 #[serde(default)]
28 pub contract_address: Option<Address>,
29 #[serde(deserialize_with = "quantity")]
30 pub gas_used: u64,
31 #[serde(deserialize_with = "quantity")]
32 pub cumulative_gas_used: u64,
33 #[serde(default)]
34 pub effective_gas_price: Option<U256>,
35 #[serde(default, deserialize_with = "opt_status")]
37 pub status: Option<bool>,
38 pub logs: Vec<Log>,
39 #[serde(default, rename = "type", deserialize_with = "opt_byte")]
41 pub kind: Option<u8>,
42}
43
44impl Receipt {
45 #[must_use]
47 pub fn succeeded(&self) -> bool {
48 self.status == Some(true)
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct Transaction {
56 pub hash: B256,
57 #[serde(default, rename = "type", deserialize_with = "byte")]
59 pub kind: u8,
60 #[serde(default, deserialize_with = "opt_quantity")]
61 pub chain_id: Option<u64>,
62 #[serde(deserialize_with = "quantity")]
63 pub nonce: u64,
64 #[serde(default)]
65 pub block_hash: Option<B256>,
66 #[serde(default, deserialize_with = "opt_quantity")]
67 pub block_number: Option<u64>,
68 #[serde(default, deserialize_with = "opt_quantity")]
69 pub transaction_index: Option<u64>,
70 pub from: Address,
71 #[serde(default)]
73 pub to: Option<Address>,
74 pub value: U256,
75 #[serde(deserialize_with = "quantity")]
76 pub gas: u64,
77 #[serde(default)]
78 pub gas_price: Option<U256>,
79 #[serde(default)]
80 pub max_fee_per_gas: Option<U256>,
81 #[serde(default)]
82 pub max_priority_fee_per_gas: Option<U256>,
83 #[serde(default)]
84 pub access_list: Vec<AccessItem>,
85 #[serde(deserialize_with = "bytes")]
86 pub input: Vec<u8>,
87 #[serde(default)]
88 pub r: Option<U256>,
89 #[serde(default)]
90 pub s: Option<U256>,
91 #[serde(default, deserialize_with = "opt_quantity")]
92 pub y_parity: Option<u64>,
93}
94
95impl Transaction {
96 #[must_use]
99 pub fn to_eip1559(&self) -> Option<Eip1559> {
100 if self.kind != EIP1559 {
101 return None;
102 }
103 Some(Eip1559 {
104 chain_id: self.chain_id?,
105 nonce: self.nonce,
106 max_priority_fee: self.max_priority_fee_per_gas?,
107 max_fee: self.max_fee_per_gas?,
108 gas: self.gas,
109 to: self.to,
110 value: self.value,
111 data: self.input.clone(),
112 access_list: self.access_list.clone(),
113 })
114 }
115
116 #[must_use]
118 pub fn signature(&self) -> Option<Signature> {
119 let parity = u8::try_from(self.y_parity?).ok().filter(|parity| *parity <= 1)?;
120 Some(Signature::from_parts(self.r?.to_be_bytes(), self.s?.to_be_bytes(), 27 + parity))
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct Block {
128 #[serde(deserialize_with = "quantity")]
129 pub number: u64,
130 pub hash: B256,
131 pub parent_hash: B256,
132 #[serde(deserialize_with = "quantity")]
134 pub timestamp: u64,
135 #[serde(deserialize_with = "quantity")]
136 pub gas_limit: u64,
137 #[serde(deserialize_with = "quantity")]
138 pub gas_used: u64,
139 #[serde(default)]
140 pub base_fee_per_gas: Option<U256>,
141 pub miner: Address,
142 pub transactions: BlockTransactions,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
147#[serde(untagged)]
148pub enum BlockTransactions {
149 Hashes(Vec<B256>),
150 Full(Vec<Transaction>),
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct Log {
157 pub address: Address,
158 pub topics: Vec<B256>,
159 #[serde(deserialize_with = "bytes")]
160 pub data: Vec<u8>,
161 #[serde(default, deserialize_with = "opt_quantity")]
162 pub block_number: Option<u64>,
163 #[serde(default)]
164 pub block_hash: Option<B256>,
165 #[serde(default)]
166 pub transaction_hash: Option<B256>,
167 #[serde(default, deserialize_with = "opt_quantity")]
168 pub transaction_index: Option<u64>,
169 #[serde(default, deserialize_with = "opt_quantity")]
170 pub log_index: Option<u64>,
171 #[serde(default)]
173 pub removed: bool,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
178#[serde(rename_all = "camelCase")]
179pub struct FeeHistory {
180 #[serde(deserialize_with = "quantity")]
181 pub oldest_block: u64,
182 pub base_fee_per_gas: Vec<U256>,
184 pub gas_used_ratio: Vec<Figure>,
186 #[serde(default)]
188 pub reward: Vec<Vec<U256>>,
189}
190
191impl FeeHistory {
192 #[must_use]
194 pub fn next_base_fee(&self) -> Option<U256> {
195 self.base_fee_per_gas.last().copied()
196 }
197}
198
199pub(crate) fn quantity<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
201 let value = U256::deserialize(deserializer)?;
202 value.to_u64().ok_or_else(|| de::Error::custom(format!("{value} does not fit 64 bits")))
203}
204
205fn opt_quantity<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<u64>, D::Error> {
206 match Option::<U256>::deserialize(deserializer)? {
207 Some(value) => {
208 value.to_u64().map(Some).ok_or_else(|| de::Error::custom(format!("{value} does not fit 64 bits")))
209 }
210 None => Ok(None),
211 }
212}
213
214fn byte<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u8, D::Error> {
215 let value = quantity(deserializer)?;
216 u8::try_from(value).map_err(|_| de::Error::custom(format!("{value} is not a transaction type")))
217}
218
219fn opt_byte<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<u8>, D::Error> {
220 match opt_quantity(deserializer)? {
221 Some(value) => {
222 u8::try_from(value).map(Some).map_err(|_| de::Error::custom(format!("{value} is not a transaction type")))
223 }
224 None => Ok(None),
225 }
226}
227
228fn opt_status<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<bool>, D::Error> {
229 match opt_quantity(deserializer)? {
230 Some(0) => Ok(Some(false)),
231 Some(1) => Ok(Some(true)),
232 Some(other) => Err(de::Error::custom(format!("{other} is not a receipt status"))),
233 None => Ok(None),
234 }
235}
236
237pub(crate) fn bytes<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
239 let text = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
240 let digits = text.strip_prefix("0x").ok_or_else(|| de::Error::custom(format!("`{text}` is not 0x and hex")))?;
241 crate::utils::hex::decode(digits).map_err(de::Error::custom)
242}
243
244#[cfg(test)]
245#[path = "tests/records.rs"]
246mod tests;