use alloy::{
primitives::{keccak256, Address, B256},
signers::{local::PrivateKeySigner, Signature, SignerSync},
sol_types::{eip712_domain, SolStruct},
};
use serde::Serialize;
use crate::{
error::{HyperliquidError, Result},
signature::{
agent::l1,
eip712::{get_hyperliquid_domain, Eip712},
},
types::signature::Eip712Signature,
};
#[allow(dead_code)] pub fn sign_user_signed_action<T>(
wallet: &PrivateKeySigner,
action: &T,
_is_mainnet: bool,
) -> Result<Eip712Signature>
where
T: SolStruct,
{
let signature_chain_id = 0x66eee_u64;
let domain = get_hyperliquid_domain(signature_chain_id);
let signing_hash = action.eip712_signing_hash(&domain);
let signature = wallet.sign_hash_sync(&signing_hash)?;
let v = sig_v_from_bool(signature.v());
Ok(Eip712Signature {
r: format!("0x{:x}", signature.r()),
s: format!("0x{:x}", signature.s()),
v,
})
}
#[allow(dead_code)] pub fn sign_l1_action(
wallet: &PrivateKeySigner,
action: &impl Serialize,
vault_address: Option<String>,
nonce: u64,
expires_after: Option<u64>,
is_mainnet: bool,
) -> Result<Signature> {
use alloy::sol_types::SolStruct;
let connection_id = action_hash(action, vault_address, nonce, expires_after)?;
let source = if is_mainnet { "a" } else { "b" };
let phantom_agent = l1::Agent {
source: source.to_string(),
connectionId: connection_id,
};
let struct_hash = phantom_agent.eip712_hash_struct();
let domain = eip712_domain! {
name: "Exchange",
version: "1",
chain_id: 1337,
verifying_contract: Address::ZERO,
};
let domain_hash = domain.hash_struct();
let mut digest_input = [0u8; 66];
digest_input[0] = 0x19;
digest_input[1] = 0x01;
digest_input[2..34].copy_from_slice(&domain_hash[..]);
digest_input[34..66].copy_from_slice(&struct_hash[..]);
let signing_hash = keccak256(digest_input);
let sig = wallet.sign_hash_sync(&signing_hash)?;
let recovered = sig.recover_address_from_prehash(&signing_hash)?;
if recovered != wallet.address() {
return Err(HyperliquidError::SignatureFailure(format!(
"Signature verification failed: recovered {:?} but expected {:?}",
recovered,
wallet.address()
)));
}
tracing::trace!(
"Signature r: {:?}, s: {:?}, v: {}",
sig.r(),
sig.s(),
sig.v()
);
Ok(sig)
}
fn action_hash(
action: &impl Serialize,
vault_address: Option<String>,
nonce: u64,
expires_after: Option<u64>,
) -> Result<B256> {
let mut data = rmp_serde::to_vec_named(action)?;
data.extend_from_slice(&nonce.to_be_bytes());
if let Some(addr) = vault_address {
data.push(0x01);
data.extend_from_slice(addr.as_bytes());
} else {
data.push(0x00);
}
if let Some(expires) = expires_after {
data.push(0x00);
data.extend_from_slice(&expires.to_be_bytes());
}
let hash = keccak256(&data);
Ok(hash)
}
#[allow(dead_code)] pub fn sign_typed_data<T: Eip712>(payload: &T, wallet: &PrivateKeySigner) -> Result<Signature> {
wallet
.sign_hash_sync(&payload.eip712_signing_hash())
.map_err(|e| HyperliquidError::SignatureFailure(e.to_string()))
}
pub fn sig_v_from_bool(v: bool) -> u8 {
if v {
28
} else {
27
}
}