df_papertrade_core/account/session/
lifecycle.rs1use df_hyperliquid_core::client::Http;
5use df_hyperliquid_core::utils::Address;
6use zeroize::Zeroizing;
7
8use super::{KeyState, SessionRecord, key_state};
9use crate::account::Account;
10use crate::account::session_key::{self, RENEW_BEFORE_MS, SESSION_TTL_MS, SessionKey};
11use crate::account::store::{Slot, Store};
12use crate::account::wallet::{Purpose, Wallet};
13use crate::client::{Client, Error};
14use crate::trade::Intent;
15use crate::utils::Lane;
16
17impl<H: Http + Clone> Client<H> {
18 pub async fn session(&self, store: &impl Store, owner: Address) -> Result<Option<SessionRecord>, Error> {
20 let bytes = store.get(owner, Slot::Session).await?.map(Zeroizing::new);
21 Ok(bytes.as_deref().and_then(|bytes| SessionRecord::from_bytes(bytes)))
22 }
23
24 async fn keep(&self, store: &impl Store, owner: Address, record: &SessionRecord) -> Result<(), Error> {
25 let ttl = record.held_until_ms.saturating_sub(self.now_ms());
26 Ok(store.put(owner, Slot::Session, record.to_bytes().to_vec(), Some(ttl)).await?)
27 }
28
29 pub async fn key_state(
31 &self,
32 store: &impl Store,
33 owner: Address,
34 account: Option<&Account>,
35 ) -> Result<KeyState, Error> {
36 Ok(key_state(self.session(store, owner).await?.as_ref(), account, self.now_ms()))
37 }
38
39 pub async fn new_session(&self, store: &impl Store, owner: Address) -> Result<SessionRecord, Error> {
41 let now = self.now_ms();
42 if let Some(record) = self.session(store, owner).await?
43 && record.held_until_ms > now.saturating_add(RENEW_BEFORE_MS)
44 {
45 return Ok(record);
46 }
47 let record = SessionRecord {
48 secret: Zeroizing::new(self.random::<32>()?),
49 held_until_ms: now.saturating_add(SESSION_TTL_MS),
50 registered_at_ms: None,
51 };
52 record.address()?;
54 self.keep(store, owner, &record).await?;
55 Ok(record)
56 }
57
58 pub async fn register(&self, store: &impl Store, wallet: &impl Wallet) -> Result<SessionRecord, Error> {
61 let owner = wallet.address();
62 let mut record = self.new_session(store, owner).await?;
63 let nonce = self.take_nonce(store, owner, Lane::Exchange).await?;
64 let registration =
65 session_key::registration(owner, record.address()?, record.held_until_ms, nonce, self.now_ms())?;
66 let signed = self
67 .wallet_sign(wallet, owner, Intent::RegisterSessionKey(registration), Purpose::RegisterSessionKey)
68 .await?;
69 self.submit(&signed).await?;
70 record.registered_at_ms = Some(self.now_ms());
71 self.keep(store, owner, &record).await?;
72 Ok(record)
73 }
74
75 pub async fn enable(
78 &self,
79 store: &impl Store,
80 wallet: &impl Wallet,
81 account: Option<&Account>,
82 ) -> Result<KeyState, Error> {
83 if self.key_state(store, wallet.address(), account).await? == KeyState::Ready {
84 return Ok(KeyState::Ready);
85 }
86 self.register(store, wallet).await?;
87 Ok(KeyState::Ready)
88 }
89
90 pub async fn session_key(
92 &self,
93 store: &impl Store,
94 owner: Address,
95 account: Option<&Account>,
96 ) -> Result<SessionKey, Error> {
97 let record = self.session(store, owner).await?;
98 match key_state(record.as_ref(), account, self.now_ms()) {
99 KeyState::Ready => record.ok_or(Error::NeedsKey(KeyState::Missing))?.signer(owner),
100 other => Err(Error::NeedsKey(other)),
101 }
102 }
103}