use std::{
collections::{HashMap, VecDeque},
time::Duration,
};
use alloy::{
primitives::Address,
signers::{Signer, SignerSync},
};
use anyhow::{Result, anyhow};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use rust_decimal::prelude::ToPrimitive;
use serde::Deserialize;
use url::Url;
use super::{AssetTarget, signing::*};
use crate::hypercore::{
ActionError, ApiAgent, CandleInterval, Chain, Cloid, Dex, MultiSigConfig, OidOrCloid,
OutcomeMeta, PerpMarket, Signature, SpotMarket, SpotToken,
api::{
Action, ActionRequest, ApproveAgent, ConvertToMultiSigUser, OkResponse, Response,
SignersConfig, VaultTransfer,
},
mainnet_url, testnet_url,
types::{
BasicOrder, BatchCancel, BatchCancelCloid, BatchModify, BatchOrder, ClearinghouseState,
Fill, FundingRate, InfoRequest, OrderResponseStatus, OrderUpdate, ScheduleCancel,
SendAsset, SendToken, SpotSend, SubAccount, UsdSend, UserBalance, UserFees, UserRole,
UserVaultEquity, VaultDetails,
},
};
pub struct Client {
http_client: reqwest::Client,
base_url: Url,
chain: Chain,
}
impl Client {
pub fn new(chain: Chain) -> Self {
let base_url = if chain.is_mainnet() {
mainnet_url()
} else {
testnet_url()
};
let http_client = reqwest::Client::builder()
.timeout(Duration::from_secs(10))
.tcp_nodelay(true)
.build()
.unwrap();
Self {
http_client,
base_url,
chain,
}
}
pub fn with_url(self, base_url: Url) -> Self {
Self { base_url, ..self }
}
#[must_use]
pub fn with_http_client(self, http_client: reqwest::Client) -> Self {
Self { http_client, ..self }
}
#[must_use]
pub const fn chain(&self) -> Chain {
self.chain
}
pub fn websocket(&self) -> super::WebSocket {
let mut url = self.base_url.clone();
let _ = url.set_scheme("wss");
url.set_path("/ws");
super::WebSocket::new(url)
}
pub fn websocket_no_tls(&self) -> super::WebSocket {
let mut url = self.base_url.clone();
let _ = url.set_scheme("ws");
url.set_path("/ws");
super::WebSocket::new(url)
}
#[inline(always)]
pub async fn perps(&self) -> Result<Vec<PerpMarket>> {
super::perp_markets(self.base_url.clone(), self.http_client.clone(), None).await
}
#[inline(always)]
pub async fn perps_from(&self, dex: Dex) -> Result<Vec<PerpMarket>> {
super::perp_markets(self.base_url.clone(), self.http_client.clone(), Some(dex)).await
}
#[inline(always)]
pub async fn perp_dexs(&self) -> Result<Vec<Dex>> {
super::perp_dexs(self.base_url.clone(), self.http_client.clone()).await
}
#[inline(always)]
pub async fn spot(&self) -> Result<Vec<SpotMarket>> {
super::spot_markets(self.base_url.clone(), self.http_client.clone()).await
}
#[inline(always)]
pub async fn spot_tokens(&self) -> Result<Vec<SpotToken>> {
super::spot_tokens(self.base_url.clone(), self.http_client.clone()).await
}
#[inline(always)]
pub async fn outcome_meta(&self) -> Result<OutcomeMeta> {
super::outcome_meta(self.base_url.clone(), self.http_client.clone()).await
}
pub async fn open_orders(
&self,
user: Address,
dex_name: Option<String>,
) -> Result<Vec<BasicOrder>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let data = self
.http_client
.post(api_url)
.json(&InfoRequest::FrontendOpenOrders {
user,
dex: dex_name,
})
.send()
.await?
.json()
.await?;
Ok(data)
}
pub async fn all_mids(&self, dex_name: Option<String>) -> Result<HashMap<String, Decimal>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let data = self
.http_client
.post(api_url)
.json(&InfoRequest::AllMids { dex: dex_name })
.send()
.await?
.json()
.await?;
Ok(data)
}
pub async fn historical_orders(&self, user: Address) -> Result<Vec<BasicOrder>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let data = self
.http_client
.post(api_url)
.json(&InfoRequest::HistoricalOrders { user })
.send()
.await?
.json()
.await?;
Ok(data)
}
pub async fn user_fills(&self, user: Address) -> Result<Vec<Fill>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let data = self
.http_client
.post(api_url)
.json(&InfoRequest::UserFills { user })
.send()
.await?
.json()
.await?;
Ok(data)
}
pub async fn user_fills_by_time(
&self,
user: Address,
start_time: u64,
end_time: Option<u64>,
) -> Result<Vec<Fill>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let data = self
.http_client
.post(api_url)
.json(&InfoRequest::UserFillsByTime {
user,
start_time,
end_time,
})
.send()
.await?
.json()
.await?;
Ok(data)
}
pub async fn order_status(
&self,
user: Address,
oid: OidOrCloid,
) -> Result<Option<OrderUpdate<BasicOrder>>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "status")]
enum Response {
Order { order: OrderUpdate<BasicOrder> },
UnknownOid,
}
let data: Response = self
.http_client
.post(api_url)
.json(&InfoRequest::OrderStatus { user, oid })
.send()
.await?
.json()
.await?;
Ok(match data {
Response::Order { order } => Some(order),
Response::UnknownOid => None,
})
}
pub async fn candle_snapshot(
&self,
coin: impl Into<String>,
interval: CandleInterval,
start_time: u64,
end_time: u64,
) -> Result<Vec<super::types::Candle>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let req = super::types::CandleSnapshotRequest {
coin: coin.into(),
interval,
start_time,
end_time,
};
let data = self
.http_client
.post(api_url)
.json(&InfoRequest::CandleSnapshot { req })
.send()
.await?
.json()
.await?;
Ok(data)
}
pub async fn user_balances(&self, user: Address) -> Result<Vec<UserBalance>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
#[derive(Deserialize)]
struct Balances {
balances: Vec<UserBalance>,
}
let data: Balances = self
.http_client
.post(api_url)
.json(&InfoRequest::SpotClearinghouseState { user })
.send()
.await?
.json()
.await?;
Ok(data.balances)
}
pub async fn user_fees(&self, user: Address) -> Result<UserFees> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let data = self
.http_client
.post(api_url)
.json(&InfoRequest::UserFees { user })
.send()
.await?
.json()
.await?;
Ok(data)
}
pub async fn clearinghouse_state(
&self,
user: Address,
dex_name: Option<String>,
) -> Result<ClearinghouseState> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let data = self
.http_client
.post(api_url)
.json(&InfoRequest::ClearinghouseState {
user,
dex: dex_name,
})
.send()
.await?
.json()
.await?;
Ok(data)
}
pub async fn funding_history(
&self,
coin: impl Into<String>,
start_time: u64,
end_time: Option<u64>,
) -> Result<Vec<FundingRate>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let data = self
.http_client
.post(api_url)
.json(&InfoRequest::FundingHistory {
coin: coin.into(),
start_time,
end_time,
})
.send()
.await?
.json()
.await?;
Ok(data)
}
pub async fn multi_sig_config(&self, user: Address) -> Result<MultiSigConfig> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let resp = self
.http_client
.post(api_url)
.json(&InfoRequest::UserToMultiSigSigners { user })
.send()
.await?
.json()
.await?;
Ok(resp)
}
pub async fn api_agents(&self, user: Address) -> Result<Vec<ApiAgent>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let resp = self
.http_client
.post(api_url)
.json(&InfoRequest::ExtraAgents { user })
.send()
.await?
.json()
.await?;
Ok(resp)
}
pub async fn vault_details(
&self,
vault_address: Address,
user: Option<Address>,
) -> Result<VaultDetails> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let resp = self
.http_client
.post(api_url)
.json(&InfoRequest::VaultDetails {
vault_address,
user,
})
.send()
.await?
.json()
.await?;
Ok(resp)
}
pub async fn user_vault_equities(&self, user: Address) -> Result<Vec<UserVaultEquity>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let resp = self
.http_client
.post(api_url)
.json(&InfoRequest::UserVaultEquities { user })
.send()
.await?
.json()
.await?;
Ok(resp)
}
pub async fn user_role(&self, user: Address) -> Result<UserRole> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let resp = self
.http_client
.post(api_url)
.json(&InfoRequest::UserRole { user })
.send()
.await?
.json()
.await?;
Ok(resp)
}
pub async fn subaccounts(&self, user: Address) -> Result<Vec<SubAccount>> {
let mut api_url = self.base_url.clone();
api_url.set_path("/info");
let resp = self
.http_client
.post(api_url)
.json(&InfoRequest::SubAccounts { user })
.send()
.await?
.json()
.await?;
Ok(resp)
}
pub async fn schedule_cancel<S: SignerSync>(
&self,
signer: &S,
nonce: u64,
when: DateTime<Utc>,
vault_address: Option<Address>,
expires_after: Option<DateTime<Utc>>,
) -> Result<()> {
let resp = self
.sign_and_send_sync(
signer,
ScheduleCancel {
time: Some(when.timestamp_millis() as u64),
},
nonce,
vault_address,
expires_after,
)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => {
anyhow::bail!("schedule_cancel: {err}")
}
_ => anyhow::bail!("schedule_cancel: unexpected response type: {resp:?}"),
}
}
pub fn place<S: SignerSync>(
&self,
signer: &S,
batch: BatchOrder,
nonce: u64,
vault_address: Option<Address>,
expires_after: Option<DateTime<Utc>>,
) -> impl Future<Output = Result<Vec<OrderResponseStatus>, ActionError<Cloid>>> + Send + 'static
{
let cloids: Vec<_> = batch.orders.iter().map(|req| req.cloid).collect();
let future = self.sign_and_send_sync(signer, batch, nonce, vault_address, expires_after);
async move {
let resp = future.await.map_err(|err| ActionError {
ids: cloids.clone(),
err: err.to_string(),
})?;
match resp {
Response::Ok(OkResponse::Order { statuses }) => Ok(statuses),
Response::Err(err) => Err(ActionError { ids: cloids, err }),
_ => Err(ActionError {
ids: cloids,
err: format!("unexpected response type: {resp:?}"),
}),
}
}
}
pub fn cancel<S: SignerSync>(
&self,
signer: &S,
batch: BatchCancel,
nonce: u64,
vault_address: Option<Address>,
expires_after: Option<DateTime<Utc>>,
) -> impl Future<Output = Result<Vec<OrderResponseStatus>, ActionError<u64>>> + Send + 'static
{
let oids: Vec<_> = batch.cancels.iter().map(|req| req.oid).collect();
let future = self.sign_and_send_sync(signer, batch, nonce, vault_address, expires_after);
async move {
let resp = future.await.map_err(|err| ActionError {
ids: oids.clone(),
err: err.to_string(),
})?;
match resp {
Response::Ok(OkResponse::Cancel { statuses }) => Ok(statuses),
Response::Err(err) => Err(ActionError { ids: oids, err }),
_ => Err(ActionError {
ids: oids,
err: format!("unexpected response type: {resp:?}"),
}),
}
}
}
pub fn cancel_by_cloid<S: SignerSync>(
&self,
signer: &S,
batch: BatchCancelCloid,
nonce: u64,
vault_address: Option<Address>,
expires_after: Option<DateTime<Utc>>,
) -> impl Future<Output = Result<Vec<OrderResponseStatus>, ActionError<Cloid>>> + Send + 'static
{
let cloids: Vec<_> = batch.cancels.iter().map(|req| req.cloid).collect();
let future = self.sign_and_send_sync(signer, batch, nonce, vault_address, expires_after);
async move {
let resp = future.await.map_err(|err| ActionError {
ids: cloids.clone(),
err: err.to_string(),
})?;
match resp {
Response::Ok(OkResponse::Cancel { statuses }) => Ok(statuses),
Response::Err(err) => Err(ActionError { ids: cloids, err }),
_ => Err(ActionError {
ids: cloids,
err: format!("unexpected response type: {resp:?}"),
}),
}
}
}
pub fn modify<S: SignerSync>(
&self,
signer: &S,
batch: BatchModify,
nonce: u64,
vault_address: Option<Address>,
expires_after: Option<DateTime<Utc>>,
) -> impl Future<Output = Result<Vec<OrderResponseStatus>, ActionError<OidOrCloid>>> + Send + 'static
{
let cloids: Vec<_> = batch.modifies.iter().map(|req| req.oid).collect();
let future = self.sign_and_send_sync(signer, batch, nonce, vault_address, expires_after);
async move {
let resp = future.await.map_err(|err| ActionError {
ids: cloids.clone(),
err: err.to_string(),
})?;
match resp {
Response::Ok(OkResponse::Order { statuses }) => Ok(statuses),
Response::Err(err) => Err(ActionError { ids: cloids, err }),
_ => Err(ActionError {
ids: cloids,
err: format!("unexpected response type: {resp:?}"),
}),
}
}
}
pub async fn approve_agent<S: Signer + Send + Sync>(
&self,
signer: &S,
agent: Address,
name: String,
nonce: u64,
) -> Result<()> {
let signature_chain_id = self.chain.arbitrum_id().to_owned();
let approve_agent = ApproveAgent {
signature_chain_id,
hyperliquid_chain: self.chain,
agent_address: agent,
agent_name: if name.is_empty() { None } else { Some(name) },
nonce,
};
let resp = self
.sign_and_send(signer, approve_agent, nonce, None, None)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => {
anyhow::bail!("approve_agent: {err}")
}
_ => anyhow::bail!("approve_agent: unexpected response type: {resp:?}"),
}
}
pub async fn convert_to_multisig<S: Signer + Send + Sync>(
&self,
signer: &S,
authorized_users: Vec<Address>,
threshold: usize,
nonce: u64,
) -> Result<()> {
let chain = self.chain;
let signature_chain_id = chain.arbitrum_id().to_owned();
let convert = ConvertToMultiSigUser {
signature_chain_id,
hyperliquid_chain: chain,
signers: SignersConfig {
authorized_users,
threshold,
},
nonce,
};
let resp = self
.sign_and_send(signer, convert, nonce, None, None)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => {
anyhow::bail!("convert_to_multisig: {err}")
}
_ => anyhow::bail!("convert_to_multisig: unexpected response type: {resp:?}"),
}
}
pub async fn transfer_to_evm<S: Send + SignerSync>(
&self,
signer: &S,
token: SpotToken,
amount: Decimal,
nonce: u64,
) -> Result<()> {
let destination = token
.cross_chain_address
.ok_or_else(|| anyhow::anyhow!("token {token} doesn't have a cross chain address"))?;
self.spot_send(
&signer,
SpotSend {
destination,
token: SendToken(token),
amount,
time: nonce,
},
nonce,
)
.await
}
pub async fn transfer_to_spot<S: Signer + SignerSync>(
&self,
signer: &S,
token: SpotToken,
amount: Decimal,
nonce: u64,
) -> Result<()> {
if token.name != "USDC" {
return Err(anyhow::anyhow!(
"only USDC is accepted, tried to transfer {}",
token.name
));
}
self.send_asset(
signer,
SendAsset {
destination: signer.address(),
source_dex: AssetTarget::Perp,
destination_dex: AssetTarget::Spot,
token: SendToken(token),
from_sub_account: "".into(),
amount,
nonce,
},
nonce,
)
.await
}
pub async fn transfer_to_perps<S: Signer + SignerSync>(
&self,
signer: &S,
token: SpotToken,
amount: Decimal,
nonce: u64,
) -> Result<()> {
if token.name != "USDC" {
return Err(anyhow::anyhow!(
"only USDC is accepted, tried to transfer {}",
token.name
));
}
self.send_asset(
signer,
SendAsset {
destination: signer.address(),
source_dex: AssetTarget::Spot,
destination_dex: AssetTarget::Perp,
token: SendToken(token),
from_sub_account: "".into(),
amount,
nonce,
},
nonce,
)
.await
}
pub async fn send_usdc<S: SignerSync>(
&self,
signer: &S,
send: UsdSend,
nonce: u64,
) -> Result<()> {
let resp = self
.sign_and_send_sync(signer, send.into_action(self.chain), nonce, None, None)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => {
anyhow::bail!("send_usdc: {err}")
}
_ => anyhow::bail!("send_usdc: unexpected response type: {resp:?}"),
}
}
pub async fn vault_transfer<S: SignerSync>(
&self,
signer: &S,
vault_address: Address,
usd: Decimal,
nonce: u64,
is_deposit: bool,
) -> Result<()> {
let usd_raw = (usd * rust_decimal::Decimal::from(1_000_000))
.to_u64()
.ok_or_else(|| anyhow::anyhow!("vault_transfer: usd amount out of range: {usd}"))?;
let action = VaultTransfer { vault_address, is_deposit, usd: usd_raw };
let resp = self
.sign_and_send_sync(signer, action, nonce, None, None)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => anyhow::bail!("vault_transfer: {err}"),
_ => anyhow::bail!("vault_transfer: unexpected response type: {resp:?}"),
}
}
pub fn send_asset<S: SignerSync>(
&self,
signer: &S,
send: SendAsset,
nonce: u64,
) -> impl Future<Output = Result<()>> + Send + 'static {
let future =
self.sign_and_send_sync(signer, send.into_action(self.chain), nonce, None, None);
async move {
let resp = future.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => {
anyhow::bail!("send_asset: {err}")
}
_ => anyhow::bail!("send_asset: unexpected response type: {resp:?}"),
}
}
}
pub fn spot_send<S: SignerSync>(
&self,
signer: &S,
send: SpotSend,
nonce: u64,
) -> impl Future<Output = Result<()>> + Send + 'static {
let future =
self.sign_and_send_sync(signer, send.into_action(self.chain), nonce, None, None);
async move {
let resp = future.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => {
anyhow::bail!("spot send: {err}")
}
_ => anyhow::bail!("spot_send: unexpected response type: {resp:?}"),
}
}
}
pub async fn evm_user_modify<S: SignerSync>(
&self,
signer: &S,
toggle: bool,
nonce: u64,
vault_address: Option<Address>,
expires_after: Option<DateTime<Utc>>,
) -> Result<()> {
let resp = self
.sign_and_send_sync(
signer,
Action::EvmUserModify {
using_big_blocks: toggle,
},
nonce,
vault_address,
expires_after,
)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => {
anyhow::bail!("evm_user_modify: {err}")
}
_ => anyhow::bail!("evm_user_modify: unexpected response type: {resp:?}"),
}
}
pub async fn noop<S: SignerSync>(
&self,
signer: &S,
nonce: u64,
vault_address: Option<Address>,
expires_after: Option<DateTime<Utc>>,
) -> Result<()> {
let resp = self
.sign_and_send_sync(signer, Action::Noop, nonce, vault_address, expires_after)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => {
anyhow::bail!("noop: {err}")
}
_ => anyhow::bail!("noop: unexpected response type: {resp:?}"),
}
}
pub fn multi_sig<'a, S: Signer + Send + Sync>(
&'a self,
lead: &'a S,
multi_sig_user: Address,
nonce: u64,
) -> MultiSig<'a, S> {
MultiSig {
lead,
multi_sig_user,
signers: VecDeque::new(),
signatures: VecDeque::new(),
client: self,
nonce,
}
}
fn sign_and_send_sync<S: SignerSync, A: Into<Action>>(
&self,
signer: &S,
action: A,
nonce: u64,
maybe_vault_address: Option<Address>,
maybe_expires_after: Option<DateTime<Utc>>,
) -> impl Future<Output = Result<Response>> + Send + 'static {
let action: Action = action.into();
let res = action.sign_sync(
signer,
nonce,
maybe_vault_address,
maybe_expires_after,
self.chain,
);
let http_client = self.http_client.clone();
let mut url = self.base_url.clone();
url.set_path("/exchange");
async move {
let req = res?;
let res = http_client.post(url).json(&req).send().await?;
let status = res.status();
let text = res.text().await?;
if !status.is_success() {
return Err(anyhow!("HTTP {status} body={text}"));
}
let parsed = serde_json::from_str(&text)
.map_err(|e| anyhow!("decode failed: {e}; body={text}"))?;
Ok(parsed)
}
}
async fn sign_and_send<S: Signer + Send + Sync, A: Into<Action>>(
&self,
signer: &S,
action: A,
nonce: u64,
maybe_vault_address: Option<Address>,
maybe_expires_after: Option<DateTime<Utc>>,
) -> Result<Response> {
let action: Action = action.into();
let req = action
.sign(
signer,
nonce,
maybe_vault_address,
maybe_expires_after,
self.chain,
)
.await?;
self.send(req).await
}
#[doc(hidden)]
pub async fn send(&self, req: ActionRequest) -> Result<Response> {
let http_client = self.http_client.clone();
let mut url = self.base_url.clone();
url.set_path("/exchange");
let res = http_client
.post(url)
.timeout(Duration::from_secs(5))
.json(&req)
.send()
.await?
.json()
.await?;
Ok(res)
}
}
pub struct MultiSig<'a, S: Signer + Send + Sync> {
lead: &'a S,
multi_sig_user: Address,
signers: VecDeque<&'a S>,
signatures: VecDeque<Signature>,
nonce: u64,
client: &'a Client,
}
impl<'a, S> MultiSig<'a, S>
where
S: Signer + Send + Sync,
{
pub fn signer(mut self, signer: &'a S) -> Self {
self.signers.push_back(signer);
self
}
pub fn signers(mut self, signers: impl IntoIterator<Item = &'a S>) -> Self {
self.signers.extend(signers);
self
}
pub fn signatures(mut self, signatures: impl IntoIterator<Item = Signature>) -> Self {
self.signatures.extend(signatures);
self
}
pub async fn place(
&self,
batch: BatchOrder,
vault_address: Option<Address>,
expires_after: Option<DateTime<Utc>>,
) -> Result<Vec<OrderResponseStatus>, ActionError<Cloid>> {
let cloids: Vec<_> = batch.orders.iter().map(|req| req.cloid).collect();
let action = multisig_collect_signatures(
self.lead.address(),
self.multi_sig_user,
self.signers.iter().copied(),
self.signatures.iter().copied(),
Action::Order(batch),
self.nonce,
self.client.chain,
)
.await
.map_err(|err| ActionError {
ids: cloids.clone(),
err: err.to_string(),
})?;
let resp = self
.client
.sign_and_send(self.lead, action, self.nonce, vault_address, expires_after)
.await
.map_err(|err| ActionError {
ids: cloids.clone(),
err: err.to_string(),
})?;
match resp {
Response::Ok(OkResponse::Order { statuses }) => Ok(statuses),
Response::Err(err) => Err(ActionError { ids: cloids, err }),
_ => Err(ActionError {
ids: cloids,
err: format!("unexpected response type: {resp:?}"),
}),
}
}
pub async fn send_usdc(&self, send: UsdSend) -> Result<()> {
let nonce = send.time;
let action = multisig_collect_signatures(
self.lead.address(),
self.multi_sig_user,
self.signers.iter().copied(),
self.signatures.iter().copied(),
send.into_action(self.client.chain()).into(),
nonce,
self.client.chain,
)
.await?;
let resp = self
.client
.sign_and_send(self.lead, action, self.nonce, None, None)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => anyhow::bail!("send_usdc: {err}"),
_ => anyhow::bail!("send_usdc: unexpected response type: {resp:?}"),
}
}
pub async fn send_asset(&self, send: SendAsset) -> Result<()> {
let nonce = send.nonce;
let action = multisig_collect_signatures(
self.lead.address(),
self.multi_sig_user,
self.signers.iter().copied(),
self.signatures.iter().copied(),
send.into_action(self.client.chain()).into(),
nonce,
self.client.chain,
)
.await?;
let resp = self
.client
.sign_and_send(self.lead, action, self.nonce, None, None)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => anyhow::bail!("send_asset: {err}"),
_ => anyhow::bail!("send_asset: unexpected response type: {resp:?}"),
}
}
pub async fn approve_agent(&self, agent: Address, name: String) -> Result<()> {
let chain = self.client.chain;
let signature_chain_id = chain.arbitrum_id().to_owned();
let approve_agent = ApproveAgent {
signature_chain_id,
hyperliquid_chain: chain,
agent_address: agent,
agent_name: if name.is_empty() { None } else { Some(name) },
nonce: self.nonce,
};
let action = multisig_collect_signatures(
self.lead.address(),
self.multi_sig_user,
self.signers.iter().copied(),
self.signatures.iter().copied(),
Action::ApproveAgent(approve_agent),
self.nonce,
self.client.chain,
)
.await?;
let resp = self
.client
.sign_and_send(self.lead, action, self.nonce, None, None)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => anyhow::bail!("approve_agent: {err}"),
_ => anyhow::bail!("approve_agent: unexpected response type: {resp:?}"),
}
}
pub async fn convert_to_normal_user(&self) -> Result<()> {
let chain = self.client.chain;
let convert = ConvertToMultiSigUser {
signature_chain_id: chain.arbitrum_id().to_owned(),
hyperliquid_chain: chain,
signers: SignersConfig {
authorized_users: vec![], threshold: 0,
},
nonce: self.nonce,
};
let action = multisig_collect_signatures(
self.lead.address(),
self.multi_sig_user,
self.signers.iter().copied(),
self.signatures.iter().copied(),
Action::ConvertToMultiSigUser(convert),
self.nonce,
self.client.chain,
)
.await?;
let resp = self
.client
.sign_and_send(self.lead, action, self.nonce, None, None)
.await?;
match resp {
Response::Ok(OkResponse::Default) => Ok(()),
Response::Err(err) => anyhow::bail!("convert_to_normal_user: {err}"),
_ => anyhow::bail!("convert_to_normal_user: unexpected response type: {resp:?}"),
}
}
}