use alloy::{
dyn_abi::{Eip712Types, Resolver, TypedData},
primitives::{Address, B256, U256, keccak256},
sol_types::SolStruct,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::Cloid;
use crate::hypercore::Chain;
const HYPERLIQUID_EIP_PREFIX: &str = "HyperliquidTransaction:";
pub(super) mod decimal_normalized {
use std::str::FromStr;
use rust_decimal::Decimal;
use serde::{Deserialize, Deserializer, Serializer, de};
pub fn serialize<S>(value: &Decimal, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let normalized = value.normalize();
serializer.serialize_str(&normalized.to_string())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Decimal::from_str(&s)
.map(|d| d.normalize())
.map_err(de::Error::custom)
}
}
pub(super) mod oid_or_cloid {
use either::Either;
use serde::{Deserializer, Serializer, de};
use super::Cloid;
pub fn serialize<S>(value: &Either<u64, Cloid>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Either::Left(oid) => serializer.serialize_u64(*oid),
Either::Right(cloid) => serializer.serialize_str(&format!("{:#x}", cloid)),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Either<u64, Cloid>, D::Error>
where
D: Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Either<u64, Cloid>;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a u64 oid or a hex string cloid")
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
Ok(Either::Left(v))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse::<Cloid>()
.map(Either::Right)
.map_err(de::Error::custom)
}
}
deserializer.deserialize_any(Visitor)
}
}
pub(super) fn serialize_cloid_as_hex<S>(value: &Cloid, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{:#x}", value))
}
pub(super) fn deserialize_cloid_from_hex<'de, D>(deserializer: D) -> Result<Cloid, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse::<Cloid>().map_err(serde::de::Error::custom)
}
pub(super) fn is_cloid_zero(value: &Cloid) -> bool {
value.is_zero()
}
pub(super) fn serialize_cloid_option<S>(value: &Cloid, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_cloid_as_hex(value, serializer)
}
pub(super) fn deserialize_cloid_option<'de, D>(deserializer: D) -> Result<Cloid, D::Error>
where
D: Deserializer<'de>,
{
deserialize_cloid_from_hex(deserializer)
}
pub(super) fn serialize_address_as_hex<S>(value: &Address, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{:#x}", value))
}
pub(super) fn deserialize_address_from_hex<'de, D>(deserializer: D) -> Result<Address, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse::<Address>().map_err(serde::de::Error::custom)
}
pub(super) fn serialize_as_hex<S>(value: &U256, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{:#x}", value))
}
pub(super) fn serialize_signers_as_json<S>(
value: &super::types::api::SignersConfig,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if value.authorized_users.is_empty() {
serializer.serialize_str("null")
} else {
let json = serde_json::to_string(value).map_err(serde::ser::Error::custom)?;
serializer.serialize_str(&json)
}
}
pub(super) fn deserialize_signers_as_json<'de, D>(
deserializer: D,
) -> Result<super::types::api::SignersConfig, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s == "null" {
Ok(Default::default())
} else {
let data = serde_json::from_str(&s).map_err(serde::de::Error::custom)?;
Ok(data)
}
}
pub(super) fn deserialize_from_hex<'de, D>(deserializer: D) -> Result<U256, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let s = s.strip_prefix("0x").unwrap_or(&s);
U256::from_str_radix(s, 16).map_err(serde::de::Error::custom)
}
pub(super) fn rmp_hash<T: Serialize>(
value: &T,
nonce: u64,
maybe_vault_address: Option<Address>,
maybe_expires_after: Option<u64>,
) -> Result<B256, rmp_serde::encode::Error> {
let mut bytes = rmp_serde::to_vec_named(value)?;
bytes.extend(nonce.to_be_bytes());
if let Some(vault_address) = maybe_vault_address {
bytes.push(1);
bytes.extend(vault_address.as_slice());
} else {
bytes.push(0);
}
if let Some(expires_after) = maybe_expires_after {
bytes.push(0);
bytes.extend(expires_after.to_be_bytes());
}
let signature = keccak256(bytes);
Ok(B256::from(signature))
}
pub(super) fn get_typed_data<T: SolStruct>(
msg: &impl Serialize,
chain: Chain,
multi_sig: Option<(Address, Address)>,
) -> TypedData {
let mut resolver = Resolver::from_struct::<T>();
resolver
.ingest_string(T::eip712_encode_type())
.expect("failed to ingest EIP-712 type");
let mut types = Eip712Types::from(&resolver);
let agent_type = types.remove(T::NAME).unwrap();
let mut msg = serde_json::to_value(msg).unwrap();
if let Some((multi_sig_address, lead)) = multi_sig {
let obj = msg.as_object_mut().unwrap();
obj.insert(
"payloadMultiSigUser".into(),
multi_sig_address.to_string().to_lowercase().into(),
);
obj.insert("outerSigner".into(), lead.to_string().to_lowercase().into());
}
types.insert(format!("{HYPERLIQUID_EIP_PREFIX}{}", T::NAME), agent_type);
TypedData {
domain: chain.domain(),
resolver: Resolver::from(types),
primary_type: format!("{HYPERLIQUID_EIP_PREFIX}{}", T::NAME),
message: msg,
}
}