use alloy::{
dyn_abi::{Eip712Types, PropertyDef, 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) fn serialize_agent_name<S: Serializer>(
value: &Option<String>,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(value.as_deref().unwrap_or_default())
}
pub(super) fn multisig_typed_data(
mut data: TypedData,
multi_sig_user: Address,
lead: Address,
) -> TypedData {
let mut types = Eip712Types::from(&data.resolver);
let fields = types
.get_mut(&data.primary_type)
.expect("known action schema");
let index = fields
.iter()
.position(|field| field.name() == "hyperliquidChain")
.expect("user-signed actions include hyperliquidChain")
+ 1;
fields.splice(
index..index,
[
PropertyDef::new_unchecked("address", "payloadMultiSigUser"),
PropertyDef::new_unchecked("address", "outerSigner"),
],
);
data.resolver = Resolver::from(types);
data.message["payloadMultiSigUser"] =
const_hex::encode_prefixed(multi_sig_user.as_slice()).into();
data.message["outerSigner"] = const_hex::encode_prefixed(lead.as_slice()).into();
data
}
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 decimal_normalized_option {
use rust_decimal::Decimal;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(value: &Option<Decimal>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(value) => super::decimal_normalized::serialize(value, serializer),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Normalized(#[serde(with = "super::decimal_normalized")] Decimal);
Ok(Option::<Normalized>::deserialize(deserializer)?.map(|Normalized(d)| d))
}
}
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 serialize_option_address_as_hex<S>(
value: &Option<Address>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(address) => serialize_address_as_hex(address, serializer),
None => serializer.serialize_none(),
}
}
pub(super) fn serialize_address_pairs_as_hex<S, T>(
pairs: &[(Address, T)],
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
serializer.collect_seq(
pairs
.iter()
.map(|(address, value)| (format!("{address:#x}"), value)),
)
}
pub(super) fn serialize_option_address_pairs_as_hex<S, T>(
pairs: &Option<Vec<(Address, T)>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
match pairs {
Some(pairs) => serialize_address_pairs_as_hex(pairs, serializer),
None => serializer.serialize_none(),
}
}
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,
}
}