hyperqit 0.1.9

rust sdk for interacting with hyperliquid
Documentation
use alloy::{
    dyn_abi::Eip712Domain,
    primitives::{FixedBytes, address, keccak256},
    sol as alloy_sol,
    sol_types::{SolStruct, eip712_domain},
};

use hl_sol::sol;
use serde::{Deserialize, Serialize};

use crate::errors::*;
use crate::requests::*;
use crate::signing::*;
use crate::utils::*;
use crate::wallet::*;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct ExchangeResponse {
    pub status: String,
    pub response: serde_json::Value,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct ExchangeRequest {
    pub action: serde_json::Value,
    pub signature: SignedMessage,
    pub nonce: u64,
}

sol! {
    #[multisig]
    #[derive(Serialize)]
    struct UsdClassTransfer {
        hyperliquidChain: string,
        amount: string,
        toPerp: bool,
        nonce: uint64
    }
}

sol! {
    #[multisig]
    #[derive(Serialize)]
    struct SendAsset {
        hyperliquidChain: string,
        destination: string,
        sourceDex: string,
        destinationDex: string,
        token: string,
        amount: string,
        fromSubAccount: string,
        nonce: uint64
    }
}
sol! {
    #[multisig]
    #[derive(Serialize)]
    struct UserDexAbstraction {
        hyperliquidChain: string,
        user: address,
        enabled: bool,
        nonce: uint64
    }
}

sol! {
    #[multisig]
    #[derive(Serialize)]
    struct UsdSend {
        hyperliquidChain: string,
        destination: string,
        amount: string,
        time: uint64
    }
}

sol! {
    #[derive(Serialize)]
    struct SendMultiSig {
        hyperliquidChain: string,
        multiSigActionHash: bytes32,
        nonce: uint64
    }
}

sol! {
    #[multisig]
    #[derive(Serialize, Debug)]
    struct ConvertToMultiSigUser {
        hyperliquidChain: string,
        signers: string,
        nonce: uint64
    }
}

alloy_sol! {
    #[derive(Serialize, Debug)]
    struct Agent {
        string source;
        bytes32 connectionId;
    }
}

impl HyperLiquidSigningHash for Agent {
    fn hyperliquid_signing_hash(&self, domain: &Eip712Domain) -> FixedBytes<32> {
        self.eip712_signing_hash(domain)
    }
}

pub fn hyperliquid_signing_hash<S: SolStruct>(
    type_str: String,
    data: S,
    domain: &Eip712Domain,
) -> FixedBytes<32> {
    let type_hash = keccak256(type_str.as_bytes());

    let encoded_data = data.eip712_encode_data();

    let mut struct_hash_input = Vec::new();
    struct_hash_input.extend_from_slice(type_hash.as_slice());
    struct_hash_input.extend_from_slice(&encoded_data);
    let struct_hash: FixedBytes<32> = keccak256(&struct_hash_input);

    let mut signing_input = [0u8; 2 + 32 + 32];
    signing_input[0] = 0x19;
    signing_input[1] = 0x01;
    signing_input[2..34].copy_from_slice(domain.hash_struct().as_slice());
    signing_input[34..66].copy_from_slice(struct_hash.as_slice());

    keccak256(signing_input)
}

pub fn hyperliquid_signing_hash_with_default_domain<S: SolStruct>(
    type_str: String,
    data: S,
    sig_chain: u64,
) -> FixedBytes<32> {
    let domain = eip712_domain! {
        name : "HyperliquidSignTransaction",
        version : "1",
        chain_id : sig_chain,
        verifying_contract : address!("0x0000000000000000000000000000000000000000"),
    };
    hyperliquid_signing_hash(type_str, data, &domain)
}

pub fn generate_multi_sig_hash(
    payload: MultiSigRequest,
    chain: Network,
    nonce: u64,
) -> Result<FixedBytes<32>> {
    let sig_chain_id_u64 = parse_chain_id(&payload.sig_chain_id)?;
    let mut bytes =
        rmp_serde::to_vec_named(&payload).map_err(|e| Errors::AgentSignature(e.to_string()))?;
    bytes.extend(nonce.to_be_bytes());
    bytes.push(0);

    let out: FixedBytes<32> = keccak256(bytes.clone());
    Ok(hyperliquid_signing_hash_with_default_domain(
        SEND_MULTI_SIG_TYPE.to_owned(),
        SendMultiSig {
            hyperliquidChain: chain.name(),
            multiSigActionHash: out,
            nonce,
        },
        sig_chain_id_u64,
    ))
}

pub fn generate_multi_sig_l1_hash(
    action: &crate::Actions,
    payload_multi_sig_user: String,
    outer_signer: String,
    is_mainnet: bool,
    nonce: u64,
) -> Result<FixedBytes<32>> {
    let envelope = vec![
        serde_json::Value::String(payload_multi_sig_user.to_lowercase()),
        serde_json::Value::String(outer_signer.to_lowercase()),
        serde_json::to_value(action)?, // Convert action to Value
    ];

    let mut bytes =
        rmp_serde::to_vec(&envelope).map_err(|e| Errors::AgentSignature(e.to_string()))?;

    bytes.extend(nonce.to_be_bytes());
    bytes.push(0);
    let out: FixedBytes<32> = keccak256(bytes.clone());
    let source = if is_mainnet { "a" } else { "b" }.to_string();
    let data = Agent {
        source,
        connectionId: out,
    };

    let domain = eip712_domain! {
        name: "Exchange",
        version: "1",
        chain_id: 1337,
        verifying_contract: address!("0x0000000000000000000000000000000000000000"),
    };

    Ok(data.eip712_signing_hash(&domain))
}

pub fn generate_action_params(
    action: &crate::Actions,
    is_mainnet: bool,
    nonce: u64,
) -> Result<(Agent, Eip712Domain)> {
    let mut bytes =
        rmp_serde::to_vec_named(action).map_err(|e| Errors::AgentSignature(e.to_string()))?;
    bytes.extend(nonce.to_be_bytes());
    bytes.push(0);
    let out: FixedBytes<32> = keccak256(bytes.clone());
    let source = if is_mainnet { "a" } else { "b" }.to_string();
    let data = Agent {
        source,
        connectionId: out,
    };
    Ok((
        data,
        eip712_domain! {
            name : "Exchange",
            version : "1",
            chain_id : 1337,
            verifying_contract : address!("0x0000000000000000000000000000000000000000"),
        },
    ))
}