use crate::account::Signature;
use crate::client::http::body;
use crate::client::network::Network;
use crate::utils::address::Address;
use crate::utils::sign::eip712::{Domain, Field, TypedStruct};
use crate::utils::sign::l1::{L1Action, connection_id, phantom_agent};
use crate::utils::sign::msgpack::{Map, Value};
use crate::utils::sign::user_signed::{NonceKey, UserSigned};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MultiSig {
pub user: Address,
pub outer_signer: Address,
pub action: Map,
pub signatures: Vec<Signature>,
}
impl MultiSig {
#[must_use]
pub fn new(user: Address, outer_signer: Address, action: &impl L1Action) -> Self {
MultiSig { user, outer_signer, action: action.action(), signatures: Vec::new() }
}
#[must_use]
pub fn signed(mut self, signatures: Vec<Signature>) -> Self {
self.signatures = signatures;
self
}
#[must_use]
pub fn payload(&self) -> Value {
Value::Array(vec![
Value::from(self.user.to_hex()),
Value::from(self.outer_signer.to_hex()),
Value::Map(self.action.clone()),
])
}
#[must_use]
pub fn payload_connection_id(&self, nonce: u64, vault: Option<Address>) -> [u8; 32] {
connection_id(&self.payload().to_msgpack(), nonce, vault)
}
#[must_use]
pub fn payload_digest(&self, nonce: u64, vault: Option<Address>, network: Network) -> [u8; 32] {
phantom_agent(self.payload_connection_id(nonce, vault), network).digest(&Domain::EXCHANGE)
}
#[must_use]
pub fn action(&self, network: Network) -> Map {
self.untyped(network)
.into_pairs()
.fold(Map::new().put("type", "multiSig"), |map, (key, value)| map.put(key, value))
}
fn untyped(&self, network: Network) -> Map {
let signatures = self.signatures.iter().map(|signature| {
Value::Map(
Map::new().put("r", signature.r_hex()).put("s", signature.s_hex()).put("v", u64::from(signature.v())),
)
});
Map::new()
.put("signatureChainId", network.signature_chain_id_hex())
.put("signatures", Value::Array(signatures.collect()))
.put(
"payload",
Map::new()
.put("multiSigUser", self.user.to_hex())
.put("outerSigner", self.outer_signer.to_hex())
.put("action", self.action.clone()),
)
}
#[must_use]
pub fn action_hash(&self, nonce: u64, vault: Option<Address>, network: Network) -> [u8; 32] {
connection_id(&self.untyped(network).to_msgpack(), nonce, vault)
}
fn send(&self, nonce: u64, vault: Option<Address>, network: Network) -> UserSigned<'static> {
UserSigned::new("multiSig", "HyperliquidTransaction:SendMultiSig", NonceKey::Nonce, nonce)
.field("multiSigActionHash", Field::Bytes32(self.action_hash(nonce, vault, network)))
}
#[must_use]
pub fn typed(&self, nonce: u64, vault: Option<Address>, network: Network) -> TypedStruct<'static> {
self.send(nonce, vault, network).typed(network)
}
#[must_use]
pub fn digest(&self, nonce: u64, vault: Option<Address>, network: Network) -> [u8; 32] {
self.send(nonce, vault, network).digest(network)
}
#[must_use]
pub fn wallet_json(&self, nonce: u64, vault: Option<Address>, network: Network) -> String {
self.send(nonce, vault, network).wallet_json(network)
}
#[must_use]
pub fn body(&self, nonce: u64, signature: &Signature, vault: Option<Address>, network: Network) -> String {
body::l1(&self.action(network), nonce, signature, vault)
}
}
#[cfg(test)]
#[path = "tests/multi_sig.rs"]
mod tests;