pub mod event;
pub mod method;
pub mod network;
pub mod read;
pub mod records;
pub mod rpc;
#[cfg(feature = "native")]
pub use df_hyperliquid_core::client::native;
#[cfg(feature = "web")]
pub use df_hyperliquid_core::client::web;
pub use df_hyperliquid_core::client::evm::tx::{Call, Eip1559, Fees, create_address, tx_hash, with_headroom};
pub use df_hyperliquid_core::client::{Http, Method as HttpMethod, OFFLINE, Request, Response, TransportError};
pub use event::{BadLog, Event};
pub use method::{Arg, Method};
pub use network::{Network, TESTNET_CHAIN_ID, UnknownNetwork};
pub use read::{Read, ReadError};
pub use records::{Block, BlockTransactions, FeeHistory, Log, Receipt, Transaction, TxKind};
pub use rpc::{BlockTag, CallRequest, LogFilter, Query, Rpc, RpcError};
use crate::account::Nonces;
#[cfg(feature = "sign")]
use crate::account::{SignError, Signer, verify};
use crate::gas::{self, Estimate};
use crate::utils::{Address, Dec};
use crate::{B256, U256};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SendError {
#[cfg(feature = "sign")]
#[error(transparent)]
Sign(#[from] SignError),
#[error(transparent)]
Rpc(#[from] RpcError),
}
pub struct Client<H, S = ()> {
rpc: Rpc<H>,
network: Network,
chain: u64,
signer: S,
nonces: Nonces,
}
impl<H: Http, S> std::fmt::Debug for Client<H, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("network", &self.network)
.field("chain", &self.chain)
.field("url", &self.rpc.url())
.finish_non_exhaustive()
}
}
impl<H: Http> Client<H> {
pub fn reader(http: H, network: Network) -> Self {
Client::new(http, network, ())
}
}
impl<H: Http, S> Client<H, S> {
pub fn new(http: H, network: Network, signer: S) -> Self {
Client::at(http, network.rpc_url(), network, signer)
}
pub fn at(http: H, url: impl Into<String>, network: Network, signer: S) -> Self {
Client { rpc: Rpc::new(http, url), network, chain: network.chain_id(), signer, nonces: Nonces::new() }
}
pub fn parent(http: H, network: Network, signer: S) -> Self {
Client {
rpc: Rpc::new(http, network.hyperliquid().evm_rpc_url()),
network,
chain: network.parent_chain_id(),
signer,
nonces: Nonces::new(),
}
}
#[must_use]
pub const fn network(&self) -> Network {
self.network
}
#[must_use]
pub const fn chain(&self) -> u64 {
self.chain
}
#[must_use]
pub const fn rpc(&self) -> &Rpc<H> {
&self.rpc
}
#[must_use]
pub const fn http(&self) -> &H {
self.rpc.http()
}
#[must_use]
pub const fn signer(&self) -> &S {
&self.signer
}
#[must_use]
pub const fn nonces(&self) -> &Nonces {
&self.nonces
}
pub async fn run<T>(&self, query: Query<T>) -> Result<T, RpcError> {
rpc::run(&self.rpc, query).await
}
pub async fn read<T>(&self, read: &Read<T>) -> Result<T, ReadError> {
read.on(&self.rpc, BlockTag::Latest).await
}
pub async fn chain_id(&self) -> Result<u64, RpcError> {
self.rpc.chain_id().await
}
pub async fn block_number(&self) -> Result<u64, RpcError> {
self.rpc.block_number().await
}
pub async fn balance(&self, owner: Address) -> Result<U256, RpcError> {
self.rpc.balance(owner, BlockTag::Latest).await
}
pub async fn hype(&self, owner: Address) -> Result<Dec, RpcError> {
let wei = self.balance(owner).await?;
crate::utils::hype(wei)
.map_err(|error| RpcError::Unreadable { method: "eth_getBalance", detail: error.to_string() })
}
pub async fn nonce(&self, owner: Address) -> Result<u64, RpcError> {
self.rpc.transaction_count(owner, BlockTag::Pending).await
}
pub async fn code(&self, at: Address) -> Result<Vec<u8>, RpcError> {
self.rpc.code(at, BlockTag::Latest).await
}
pub async fn gas_price(&self) -> Result<U256, RpcError> {
self.rpc.gas_price().await
}
pub async fn estimate(&self, from: Option<Address>, call: &Call) -> Result<Estimate, ReadError> {
self.read(&gas::components(from, call)).await
}
pub async fn receipt(&self, hash: B256) -> Result<Option<Receipt>, RpcError> {
self.run(rpc::nitro::receipt(hash)).await
}
pub async fn transaction(&self, hash: B256) -> Result<Option<Transaction>, RpcError> {
self.run(rpc::nitro::transaction(hash)).await
}
pub async fn block(&self, block: BlockTag, full: bool) -> Result<Option<Block>, RpcError> {
self.run(rpc::nitro::block(block, full)).await
}
pub async fn block_by_hash(&self, hash: B256, full: bool) -> Result<Option<Block>, RpcError> {
self.run(rpc::nitro::block_by_hash(hash, full)).await
}
pub async fn block_receipts(&self, block: BlockTag) -> Result<Option<Vec<Receipt>>, RpcError> {
self.run(rpc::nitro::block_receipts(block)).await
}
pub async fn logs(&self, filter: &LogFilter) -> Result<Vec<Log>, RpcError> {
if filter.block_hash.is_some() || rpc::is_narrow(filter) {
return self.rpc.logs(filter).await;
}
let fixed = |tag: Option<BlockTag>| match tag {
Some(BlockTag::Number(number)) => Some(number),
Some(BlockTag::Earliest) => Some(0),
_ => None,
};
let head = match (fixed(filter.from_block), fixed(filter.to_block)) {
(Some(_), Some(_)) => 0,
_ => self.rpc.block_number().await?,
};
let from = fixed(filter.from_block).unwrap_or(head);
let to = fixed(filter.to_block).unwrap_or(head);
let mut logs = Vec::new();
for (first, last) in rpc::pages(from, to, self.network.log_span()) {
logs.extend(self.rpc.logs(&rpc::window(filter, first, last)).await?);
}
Ok(logs)
}
}
#[cfg(feature = "sign")]
impl<H: Http, S: Signer> Client<H, S> {
pub fn address(&self) -> Address {
self.signer.address()
}
pub async fn prepare(&self, call: Call) -> Result<Eip1559, SendError> {
let from = self.signer.address();
let gas = self.rpc.estimate_gas(&CallRequest::of(Some(from), &call)).await?;
let base = self.rpc.gas_price().await?;
let nonce = match self.nonces.kept() {
Some(nonce) => nonce,
None => self.nonces.read(self.nonce(from).await?),
};
Ok(call.transaction(self.chain, nonce, with_headroom(gas), gas::fees(base)))
}
pub async fn sign(&self, tx: &Eip1559) -> Result<Vec<u8>, SignError> {
let hash = tx.signing_hash();
let signature = self.signer.sign_hash(&hash).await?;
verify(&hash.0, &signature, self.signer.address())?;
tx.signed(&signature)
}
pub async fn send(&self, call: Call) -> Result<B256, SendError> {
let tx = self.prepare(call).await?;
self.send_prepared(&tx).await
}
pub async fn send_prepared(&self, tx: &Eip1559) -> Result<B256, SendError> {
let sent = async { Ok::<_, SendError>(self.rpc.send_raw_transaction(&self.sign(tx).await?).await?) };
self.kept(tx.nonce, sent.await)
}
pub async fn send_sync(&self, call: Call, timeout_ms: Option<u64>) -> Result<Receipt, SendError> {
let tx = self.prepare(call).await?;
self.send_prepared_sync(&tx, timeout_ms).await
}
pub async fn send_prepared_sync(&self, tx: &Eip1559, timeout_ms: Option<u64>) -> Result<Receipt, SendError> {
let sent = async {
let raw = self.sign(tx).await?;
Ok::<_, SendError>(self.run(rpc::nitro::send_raw_transaction_sync(&raw, timeout_ms)).await?)
};
self.kept(tx.nonce, sent.await)
}
fn kept<T>(&self, nonce: u64, sent: Result<T, SendError>) -> Result<T, SendError> {
match &sent {
Ok(_) => self.nonces.sent(nonce),
Err(_) => self.nonces.forget(),
}
sent
}
}
#[cfg(test)]
#[path = "tests/client.rs"]
mod tests;