renegade_sdk/renegade_wallet_client/
client.rs

1//! The client for interacting with the Renegade darkpool API
2
3use std::sync::Arc;
4use std::time::Duration;
5
6use crate::auth::HmacKey;
7use alloy::primitives::Address;
8use alloy::signers::local::PrivateKeySigner;
9use futures_util::Stream;
10use renegade_circuit_types::schnorr::{SchnorrPrivateKey, SchnorrPublicKey, SchnorrSignature};
11use renegade_circuit_types::traits::BaseType;
12use renegade_constants::Scalar;
13use uuid::Uuid;
14
15use renegade_external_api::types::websocket::{
16    AdminBalanceUpdateMessage, AdminOrderUpdateMessage, BalanceUpdateMessage, FillMessage,
17    OrderUpdateMessage, TaskUpdateMessage,
18};
19
20use crate::util::get_env_agnostic_chain;
21use crate::websocket::TaskWaiter;
22use crate::{
23    BASE_MAINNET_CHAIN_ID, BASE_SEPOLIA_CHAIN_ID, RenegadeClientError,
24    http::RelayerHttpClient,
25    renegade_wallet_client::{
26        config::RenegadeClientConfig,
27        utils::{
28            derive_account_id, derive_auth_hmac_key, derive_master_view_seed, derive_schnorr_key,
29        },
30    },
31    websocket::RenegadeWebsocketClient,
32};
33
34// -----------
35// | Secrets |
36// -----------
37
38/// The secrets used to authenticate account actions
39#[derive(Copy, Clone)]
40pub struct AccountSecrets {
41    /// The ID of the account
42    pub account_id: Uuid,
43    /// The master view seed, used to sync the account with onchain state &
44    /// derive CSPRNG seeds for new state objects
45    pub master_view_seed: Scalar,
46    /// The private key used for Schnorr signatures over state objects
47    pub schnorr_key: SchnorrPrivateKey,
48    /// The HMAC key used to authenticate account API actions
49    pub auth_hmac_key: HmacKey,
50}
51
52impl AccountSecrets {
53    /// Generate a new set of account secrets from a signing key & chain ID
54    pub fn new(key: &PrivateKeySigner, chain_id: u64) -> Result<Self, RenegadeClientError> {
55        let account_id = derive_account_id(key, chain_id).map_err(RenegadeClientError::setup)?;
56
57        let master_view_seed =
58            derive_master_view_seed(key, chain_id).map_err(RenegadeClientError::setup)?;
59
60        let schnorr_key = derive_schnorr_key(key, chain_id).map_err(RenegadeClientError::setup)?;
61
62        let auth_hmac_key =
63            derive_auth_hmac_key(key, chain_id).map_err(RenegadeClientError::setup)?;
64
65        Ok(Self { account_id, master_view_seed, schnorr_key, auth_hmac_key })
66    }
67}
68
69// -------------------
70// | Darkpool Client |
71// -------------------
72
73/// The Renegade wallet client
74#[derive(Clone)]
75pub struct RenegadeClient {
76    /// The client config
77    pub config: RenegadeClientConfig,
78    /// The account secrets
79    pub secrets: AccountSecrets,
80    /// The relayer HTTP client
81    pub relayer_client: RelayerHttpClient,
82    /// The admin relayer HTTP client
83    pub admin_relayer_client: Option<RelayerHttpClient>,
84    /// The historical state HTTP client.
85    ///
86    /// Also a `RelayerHttpClient` as it mirrors the relayer's historical state
87    /// API.
88    pub historical_state_client: Arc<RelayerHttpClient>,
89    /// The websocket client
90    pub websocket_client: RenegadeWebsocketClient,
91}
92
93impl RenegadeClient {
94    /// Derive the wallet secrets from an ethereum private key
95    pub fn new(config: RenegadeClientConfig) -> Result<Self, RenegadeClientError> {
96        let secrets = AccountSecrets::new(&config.key, config.chain_id)?;
97
98        let relayer_client =
99            RelayerHttpClient::new(config.relayer_base_url.clone(), secrets.auth_hmac_key);
100
101        let admin_relayer_client = config
102            .admin_hmac_key
103            .map(|key| RelayerHttpClient::new(config.relayer_base_url.clone(), key));
104
105        let chain = get_env_agnostic_chain(config.chain_id);
106        let historical_state_client = Arc::new(RelayerHttpClient::new(
107            format!("{}/{chain}", config.historical_state_base_url),
108            secrets.auth_hmac_key,
109        ));
110
111        let websocket_client = RenegadeWebsocketClient::new(
112            &config,
113            secrets.account_id,
114            secrets.auth_hmac_key,
115            config.admin_hmac_key,
116        );
117
118        Ok(Self {
119            config,
120            secrets,
121            relayer_client,
122            admin_relayer_client,
123            historical_state_client,
124            websocket_client,
125        })
126    }
127
128    /// Create a new wallet on Arbitrum Sepolia
129    pub fn new_arbitrum_sepolia(key: &PrivateKeySigner) -> Result<Self, RenegadeClientError> {
130        Self::new(RenegadeClientConfig::new_arbitrum_sepolia(key))
131    }
132
133    /// Create a new admin wallet on Arbitrum Sepolia
134    pub fn new_arbitrum_sepolia_admin(
135        key: &PrivateKeySigner,
136        admin_hmac_key: HmacKey,
137    ) -> Result<Self, RenegadeClientError> {
138        Self::new(RenegadeClientConfig::new_arbitrum_sepolia_admin(key, admin_hmac_key))
139    }
140
141    /// Create a new wallet on Arbitrum One
142    pub fn new_arbitrum_one(key: &PrivateKeySigner) -> Result<Self, RenegadeClientError> {
143        Self::new(RenegadeClientConfig::new_arbitrum_one(key))
144    }
145
146    /// Create a new admin wallet on Arbitrum One
147    pub fn new_arbitrum_one_admin(
148        key: &PrivateKeySigner,
149        admin_hmac_key: HmacKey,
150    ) -> Result<Self, RenegadeClientError> {
151        Self::new(RenegadeClientConfig::new_arbitrum_one_admin(key, admin_hmac_key))
152    }
153
154    /// Create a new wallet on Base Sepolia
155    pub fn new_base_sepolia(key: &PrivateKeySigner) -> Result<Self, RenegadeClientError> {
156        Self::new(RenegadeClientConfig::new_base_sepolia(key))
157    }
158
159    /// Create a new admin wallet on Base Sepolia
160    pub fn new_base_sepolia_admin(
161        key: &PrivateKeySigner,
162        admin_hmac_key: HmacKey,
163    ) -> Result<Self, RenegadeClientError> {
164        Self::new(RenegadeClientConfig::new_base_sepolia_admin(key, admin_hmac_key))
165    }
166
167    /// Create a new wallet on Base Mainnet
168    pub fn new_base_mainnet(key: &PrivateKeySigner) -> Result<Self, RenegadeClientError> {
169        Self::new(RenegadeClientConfig::new_base_mainnet(key))
170    }
171
172    /// Create a new admin wallet on Ethereum Sepolia
173    pub fn new_ethereum_sepolia_admin(
174        key: &PrivateKeySigner,
175        admin_hmac_key: HmacKey,
176    ) -> Result<Self, RenegadeClientError> {
177        Self::new(RenegadeClientConfig::new_ethereum_sepolia_admin(key, admin_hmac_key))
178    }
179
180    /// Create a new admin wallet on Ethereum Sepolia
181    pub fn new_ethereum_sepolia(key: &PrivateKeySigner) -> Result<Self, RenegadeClientError> {
182        Self::new(RenegadeClientConfig::new_ethereum_sepolia(key))
183    }
184
185    /// Create a new admin wallet on Base Mainnet
186    pub fn new_base_mainnet_admin(
187        key: &PrivateKeySigner,
188        admin_hmac_key: HmacKey,
189    ) -> Result<Self, RenegadeClientError> {
190        Self::new(RenegadeClientConfig::new_base_mainnet_admin(key, admin_hmac_key))
191    }
192
193    /// Whether the client is on a chain in which Renegade is deployed as a
194    /// solidity contract
195    pub fn is_solidity_chain(&self) -> bool {
196        self.config.chain_id == BASE_MAINNET_CHAIN_ID
197            || self.config.chain_id == BASE_SEPOLIA_CHAIN_ID
198    }
199
200    // --------------
201    // | WS Methods |
202    // --------------
203
204    /// Create a `TaskWaiter` which can be used to watch a task until it
205    /// completes or times out
206    pub async fn watch_task(
207        &self,
208        task_id: Uuid,
209        timeout: Duration,
210    ) -> Result<TaskWaiter, RenegadeClientError> {
211        self.websocket_client.watch_task(task_id, timeout).await
212    }
213
214    /// Subscribe to the account's task updates stream
215    pub async fn subscribe_task_updates(
216        &self,
217    ) -> Result<impl Stream<Item = TaskUpdateMessage>, RenegadeClientError> {
218        self.websocket_client.subscribe_task_updates().await
219    }
220
221    /// Subscribe to the account's balance updates stream
222    pub async fn subscribe_balance_updates(
223        &self,
224    ) -> Result<impl Stream<Item = BalanceUpdateMessage>, RenegadeClientError> {
225        self.websocket_client.subscribe_balance_updates().await
226    }
227
228    /// Subscribe to the account's order updates stream
229    pub async fn subscribe_order_updates(
230        &self,
231    ) -> Result<impl Stream<Item = OrderUpdateMessage>, RenegadeClientError> {
232        self.websocket_client.subscribe_order_updates().await
233    }
234
235    /// Subscribe to the account's fills stream
236    pub async fn subscribe_fills(
237        &self,
238    ) -> Result<impl Stream<Item = FillMessage>, RenegadeClientError> {
239        self.websocket_client.subscribe_fills().await
240    }
241
242    /// Subscribe to the admin balances updates stream
243    pub async fn subscribe_admin_balance_updates(
244        &self,
245    ) -> Result<impl Stream<Item = AdminBalanceUpdateMessage>, RenegadeClientError> {
246        self.websocket_client.subscribe_admin_balance_updates().await
247    }
248
249    /// Subscribe to the admin order updates stream
250    pub async fn subscribe_admin_order_updates(
251        &self,
252    ) -> Result<impl Stream<Item = AdminOrderUpdateMessage>, RenegadeClientError> {
253        self.websocket_client.subscribe_admin_order_updates().await
254    }
255
256    // --------------
257    // | Misc Utils |
258    // --------------
259
260    /// Get a reference to the admin relayer client, returning an error if one
261    /// has not been configured.
262    pub fn get_admin_client(&self) -> Result<&RelayerHttpClient, RenegadeClientError> {
263        match self.admin_relayer_client.as_ref() {
264            Some(admin_client) => Ok(admin_client),
265            None => Err(RenegadeClientError::NotAdmin),
266        }
267    }
268
269    /// Get the ID of the account
270    pub fn get_account_id(&self) -> Uuid {
271        self.secrets.account_id
272    }
273
274    /// Get the master view seed
275    pub fn get_master_view_seed(&self) -> Scalar {
276        self.secrets.master_view_seed
277    }
278
279    /// Get the HMAC key used to authenticate account API actions
280    pub fn get_auth_hmac_key(&self) -> HmacKey {
281        self.secrets.auth_hmac_key
282    }
283
284    /// Get the signing key client is configured with
285    pub fn get_account_signer(&self) -> &PrivateKeySigner {
286        &self.config.key
287    }
288
289    /// Get the address of the account associated with the private key the
290    /// client is configured with
291    pub fn get_account_address(&self) -> Address {
292        self.config.key.address()
293    }
294
295    /// Get the Schnorr private key client is configured with
296    pub fn schnorr_sign<T: BaseType>(
297        &self,
298        message: &T,
299    ) -> Result<SchnorrSignature, RenegadeClientError> {
300        self.secrets.schnorr_key.sign(message).map_err(RenegadeClientError::signing)
301    }
302
303    /// Get the public key associated with the Schnorr private key the client is
304    /// configured with
305    pub fn get_schnorr_public_key(&self) -> SchnorrPublicKey {
306        self.secrets.schnorr_key.public_key()
307    }
308
309    /// Get the relayer's executor address, which it uses to sign public order
310    /// settlement obligations
311    pub fn get_executor_address(&self) -> Address {
312        self.config.executor_address
313    }
314
315    /// Get the relayer's fee recipient address
316    pub fn get_relayer_fee_recipient(&self) -> Address {
317        self.config.relayer_fee_recipient
318    }
319
320    /// Get the chain ID the client is configured for
321    pub fn get_chain_id(&self) -> u64 {
322        self.config.chain_id
323    }
324
325    /// Get the permit2 address the client is configured for
326    pub fn get_permit2_address(&self) -> Address {
327        self.config.permit2_address
328    }
329
330    /// Get the darkpool address the client is configured for
331    pub fn get_darkpool_address(&self) -> Address {
332        self.config.darkpool_address
333    }
334}