1pub mod event;
7pub mod method;
8pub mod network;
9pub mod read;
10pub mod records;
11pub mod rpc;
12
13#[cfg(feature = "native")]
14pub use df_hyperliquid_core::client::native;
15#[cfg(feature = "web")]
16pub use df_hyperliquid_core::client::web;
17
18pub use df_hyperliquid_core::client::evm::tx::{Call, Eip1559, Fees, create_address, tx_hash, with_headroom};
19pub use df_hyperliquid_core::client::{Http, Method as HttpMethod, OFFLINE, Request, Response, TransportError};
20pub use event::{BadLog, Event};
21pub use method::{Arg, Method};
22pub use network::{Network, TESTNET_CHAIN_ID, UnknownNetwork};
23pub use read::{Read, ReadError};
24pub use records::{Block, BlockTransactions, FeeHistory, Log, Receipt, Transaction, TxKind};
25pub use rpc::{BlockTag, CallRequest, LogFilter, Query, Rpc, RpcError};
26
27use crate::account::Nonces;
28#[cfg(feature = "sign")]
29use crate::account::{SignError, Signer, verify};
30use crate::gas::{self, Estimate};
31use crate::utils::{Address, Dec};
32use crate::{B256, U256};
33
34#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
37pub enum SendError {
38 #[cfg(feature = "sign")]
39 #[error(transparent)]
40 Sign(#[from] SignError),
41 #[error(transparent)]
42 Rpc(#[from] RpcError),
43}
44
45pub struct Client<H, S = ()> {
51 rpc: Rpc<H>,
52 network: Network,
53 chain: u64,
54 signer: S,
55 nonces: Nonces,
56}
57
58impl<H: Http, S> std::fmt::Debug for Client<H, S> {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("Client")
62 .field("network", &self.network)
63 .field("chain", &self.chain)
64 .field("url", &self.rpc.url())
65 .finish_non_exhaustive()
66 }
67}
68
69impl<H: Http> Client<H> {
70 pub fn reader(http: H, network: Network) -> Self {
72 Client::new(http, network, ())
73 }
74}
75
76impl<H: Http, S> Client<H, S> {
77 pub fn new(http: H, network: Network, signer: S) -> Self {
79 Client::at(http, network.rpc_url(), network, signer)
80 }
81
82 pub fn at(http: H, url: impl Into<String>, network: Network, signer: S) -> Self {
84 Client { rpc: Rpc::new(http, url), network, chain: network.chain_id(), signer, nonces: Nonces::new() }
85 }
86
87 pub fn parent(http: H, network: Network, signer: S) -> Self {
93 Client {
94 rpc: Rpc::new(http, network.hyperliquid().evm_rpc_url()),
95 network,
96 chain: network.parent_chain_id(),
97 signer,
98 nonces: Nonces::new(),
99 }
100 }
101
102 #[must_use]
103 pub const fn network(&self) -> Network {
104 self.network
105 }
106
107 #[must_use]
109 pub const fn chain(&self) -> u64 {
110 self.chain
111 }
112
113 #[must_use]
115 pub const fn rpc(&self) -> &Rpc<H> {
116 &self.rpc
117 }
118
119 #[must_use]
120 pub const fn http(&self) -> &H {
121 self.rpc.http()
122 }
123
124 #[must_use]
125 pub const fn signer(&self) -> &S {
126 &self.signer
127 }
128
129 #[must_use]
131 pub const fn nonces(&self) -> &Nonces {
132 &self.nonces
133 }
134
135 pub async fn run<T>(&self, query: Query<T>) -> Result<T, RpcError> {
137 rpc::run(&self.rpc, query).await
138 }
139
140 pub async fn read<T>(&self, read: &Read<T>) -> Result<T, ReadError> {
142 read.on(&self.rpc, BlockTag::Latest).await
143 }
144
145 pub async fn chain_id(&self) -> Result<u64, RpcError> {
146 self.rpc.chain_id().await
147 }
148
149 pub async fn block_number(&self) -> Result<u64, RpcError> {
150 self.rpc.block_number().await
151 }
152
153 pub async fn balance(&self, owner: Address) -> Result<U256, RpcError> {
155 self.rpc.balance(owner, BlockTag::Latest).await
156 }
157
158 pub async fn hype(&self, owner: Address) -> Result<Dec, RpcError> {
160 let wei = self.balance(owner).await?;
161 crate::utils::hype(wei)
162 .map_err(|error| RpcError::Unreadable { method: "eth_getBalance", detail: error.to_string() })
163 }
164
165 pub async fn nonce(&self, owner: Address) -> Result<u64, RpcError> {
167 self.rpc.transaction_count(owner, BlockTag::Pending).await
168 }
169
170 pub async fn code(&self, at: Address) -> Result<Vec<u8>, RpcError> {
172 self.rpc.code(at, BlockTag::Latest).await
173 }
174
175 pub async fn gas_price(&self) -> Result<U256, RpcError> {
177 self.rpc.gas_price().await
178 }
179
180 pub async fn estimate(&self, from: Option<Address>, call: &Call) -> Result<Estimate, ReadError> {
183 self.read(&gas::components(from, call)).await
184 }
185
186 pub async fn receipt(&self, hash: B256) -> Result<Option<Receipt>, RpcError> {
187 self.run(rpc::nitro::receipt(hash)).await
188 }
189
190 pub async fn transaction(&self, hash: B256) -> Result<Option<Transaction>, RpcError> {
191 self.run(rpc::nitro::transaction(hash)).await
192 }
193
194 pub async fn block(&self, block: BlockTag, full: bool) -> Result<Option<Block>, RpcError> {
195 self.run(rpc::nitro::block(block, full)).await
196 }
197
198 pub async fn block_by_hash(&self, hash: B256, full: bool) -> Result<Option<Block>, RpcError> {
199 self.run(rpc::nitro::block_by_hash(hash, full)).await
200 }
201
202 pub async fn block_receipts(&self, block: BlockTag) -> Result<Option<Vec<Receipt>>, RpcError> {
203 self.run(rpc::nitro::block_receipts(block)).await
204 }
205
206 pub async fn logs(&self, filter: &LogFilter) -> Result<Vec<Log>, RpcError> {
209 if filter.block_hash.is_some() || rpc::is_narrow(filter) {
210 return self.rpc.logs(filter).await;
211 }
212 let fixed = |tag: Option<BlockTag>| match tag {
213 Some(BlockTag::Number(number)) => Some(number),
214 Some(BlockTag::Earliest) => Some(0),
215 _ => None,
216 };
217 let head = match (fixed(filter.from_block), fixed(filter.to_block)) {
218 (Some(_), Some(_)) => 0,
219 _ => self.rpc.block_number().await?,
220 };
221 let from = fixed(filter.from_block).unwrap_or(head);
222 let to = fixed(filter.to_block).unwrap_or(head);
223 let mut logs = Vec::new();
224 for (first, last) in rpc::pages(from, to, self.network.log_span()) {
225 logs.extend(self.rpc.logs(&rpc::window(filter, first, last)).await?);
226 }
227 Ok(logs)
228 }
229}
230
231#[cfg(feature = "sign")]
234impl<H: Http, S: Signer> Client<H, S> {
235 pub fn address(&self) -> Address {
237 self.signer.address()
238 }
239
240 pub async fn prepare(&self, call: Call) -> Result<Eip1559, SendError> {
243 let from = self.signer.address();
244 let gas = self.rpc.estimate_gas(&CallRequest::of(Some(from), &call)).await?;
245 let base = self.rpc.gas_price().await?;
246 let nonce = match self.nonces.kept() {
247 Some(nonce) => nonce,
248 None => self.nonces.read(self.nonce(from).await?),
249 };
250 Ok(call.transaction(self.chain, nonce, with_headroom(gas), gas::fees(base)))
251 }
252
253 pub async fn sign(&self, tx: &Eip1559) -> Result<Vec<u8>, SignError> {
255 let hash = tx.signing_hash();
256 let signature = self.signer.sign_hash(&hash).await?;
257 verify(&hash.0, &signature, self.signer.address())?;
258 tx.signed(&signature)
259 }
260
261 pub async fn send(&self, call: Call) -> Result<B256, SendError> {
263 let tx = self.prepare(call).await?;
264 self.send_prepared(&tx).await
265 }
266
267 pub async fn send_prepared(&self, tx: &Eip1559) -> Result<B256, SendError> {
269 let sent = async { Ok::<_, SendError>(self.rpc.send_raw_transaction(&self.sign(tx).await?).await?) };
270 self.kept(tx.nonce, sent.await)
271 }
272
273 pub async fn send_sync(&self, call: Call, timeout_ms: Option<u64>) -> Result<Receipt, SendError> {
277 let tx = self.prepare(call).await?;
278 self.send_prepared_sync(&tx, timeout_ms).await
279 }
280
281 pub async fn send_prepared_sync(&self, tx: &Eip1559, timeout_ms: Option<u64>) -> Result<Receipt, SendError> {
283 let sent = async {
284 let raw = self.sign(tx).await?;
285 Ok::<_, SendError>(self.run(rpc::nitro::send_raw_transaction_sync(&raw, timeout_ms)).await?)
286 };
287 self.kept(tx.nonce, sent.await)
288 }
289
290 fn kept<T>(&self, nonce: u64, sent: Result<T, SendError>) -> Result<T, SendError> {
292 match &sent {
293 Ok(_) => self.nonces.sent(nonce),
294 Err(_) => self.nonces.forget(),
295 }
296 sent
297 }
298}
299
300#[cfg(test)]
301#[path = "tests/client.rs"]
302mod tests;