use ethers::signers::{LocalWallet, Signer};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct HyperliquidConfig {
pub wallet: LocalWallet,
pub testnet: bool,
}
impl HyperliquidConfig {
pub fn new(wallet: LocalWallet, testnet: bool) -> Self {
Self { wallet, testnet }
}
pub fn from_env() -> Result<Self, ConfigError> {
let private_key =
std::env::var("HYPERLIQUID_PRIVATE_KEY").map_err(|_| ConfigError::MissingPrivateKey)?;
let testnet = std::env::var("HYPERLIQUID_TESTNET")
.map(|v| v.eq_ignore_ascii_case("true") || v == "1")
.unwrap_or(false);
Self::from_private_key(&private_key, testnet)
}
pub fn from_private_key(private_key: &str, testnet: bool) -> Result<Self, ConfigError> {
let key = private_key.strip_prefix("0x").unwrap_or(private_key);
let wallet: LocalWallet = key
.parse()
.map_err(|e| ConfigError::InvalidPrivateKey(format!("{e}")))?;
Ok(Self { wallet, testnet })
}
pub fn wallet_address_hex(&self) -> String {
format!("{:#x}", self.wallet.address())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HyperliquidConfigFile {
#[serde(default)]
pub testnet: bool,
}
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("HYPERLIQUID_PRIVATE_KEY environment variable not set")]
MissingPrivateKey,
#[error("Invalid private key: {0}")]
InvalidPrivateKey(String),
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
#[test]
fn test_from_private_key_with_prefix() {
let key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
let config = HyperliquidConfig::from_private_key(key, false).unwrap();
assert!(!config.testnet);
assert!(config.wallet_address_hex().starts_with("0x"));
}
#[test]
fn test_from_private_key_without_prefix() {
let key = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
let config = HyperliquidConfig::from_private_key(key, true).unwrap();
assert!(config.testnet);
}
#[test]
fn test_invalid_private_key() {
let result = HyperliquidConfig::from_private_key("invalid", false);
assert!(result.is_err());
}
}