Skip to main content

vertex_sdk/
vertex_client.rs

1use async_trait::async_trait;
2use ethers::core::k256::ecdsa::SigningKey;
3use ethers::types::{H160, U256};
4use ethers_core::types::TransactionReceipt;
5use ethers_signers::Wallet;
6use eyre::eyre;
7use eyre::Result;
8use serde::de::DeserializeOwned;
9use std::time::Duration;
10
11use engine::Status;
12
13use crate::bindings::clearinghouse::Clearinghouse;
14use crate::bindings::withdraw_pool::WithdrawPool;
15use crate::builders::execute::deposit_collateral::DepositCollateralParams;
16use crate::builders::execute::slow_mode::SubmitSlowModeTxParams;
17use crate::eip712_structs::to_bytes12;
18use crate::engine::{ExecuteResponse, QueryV2};
19use crate::prelude::*;
20use crate::provider::VertexProvider;
21use crate::utils::client_error::{none_error, ClientError};
22use crate::utils::constants::SLOW_MODE_FEE;
23use crate::utils::deployment::Deployment;
24use crate::utils::deposit::{deposit_collateral, provider_with_signer};
25use crate::utils::rest::RestClient;
26use crate::utils::signer::wallet_with_chain_id;
27use crate::{engine, indexer, trigger};
28use crate::{extract_response_data, fields_to_vars};
29
30#[derive(Clone)]
31pub struct VertexClient {
32    pub client_mode: ClientMode,
33    pub deployment: Deployment,
34    pub client: RestClient,
35    pub gateway_url: String,
36    pub archive_url: String,
37    pub trigger_url: String,
38    pub subaccount_name_bytes: Option<[u8; 12]>,
39    pub book_addrs: Option<Vec<H160>>,
40    pub wallet: Option<Wallet<SigningKey>>,
41    pub chain_id: Option<U256>,
42    pub custom_node_url: Option<String>,
43}
44
45impl VertexClient {
46    pub fn new(client_mode: ClientMode) -> Self {
47        Self {
48            gateway_url: client_mode.default_gateway_url(),
49            archive_url: client_mode.default_archive_url(),
50            trigger_url: client_mode.default_trigger_url(),
51            client: RestClient::new(),
52            deployment: client_mode.deployment(),
53            book_addrs: None,
54            chain_id: None,
55            wallet: None,
56            subaccount_name_bytes: None,
57            custom_node_url: None,
58            client_mode,
59        }
60    }
61
62    pub fn with_gateway_url(&self, gateway_url: String) -> Self {
63        Self {
64            gateway_url,
65            ..self.clone()
66        }
67    }
68
69    pub fn with_archive_url(&self, archive_url: String) -> Self {
70        Self {
71            archive_url,
72            ..self.clone()
73        }
74    }
75
76    pub fn with_custom_node_url(&self, node_url: String) -> Self {
77        Self {
78            custom_node_url: Some(node_url),
79            ..self.clone()
80        }
81    }
82
83    pub fn with_trigger_url(&self, trigger_url: String) -> Self {
84        Self {
85            trigger_url,
86            ..self.clone()
87        }
88    }
89
90    pub async fn withdraw_pool(&self) -> Result<WithdrawPool<VertexProvider>> {
91        let provider = provider_with_signer(self)?;
92        let clearinghouse = Clearinghouse::new(self.deployment.clearinghouse, provider.clone());
93        let withdraw_pool_address = clearinghouse.get_withdraw_pool().call().await?;
94        let withdraw_pool = WithdrawPool::new(withdraw_pool_address, provider);
95        Ok(withdraw_pool)
96    }
97}
98
99#[async_trait]
100impl VertexBase for VertexClient {
101    async fn with_signer(&self, private_key: String) -> Result<Self> {
102        let contracts_response = self.get_contracts().await?;
103        let chain_id = U256::from(contracts_response.chain_id);
104        let wallet = wallet_with_chain_id(&private_key, chain_id)?;
105        let book_addrs: Vec<H160> = contracts_response
106            .book_addrs
107            .iter()
108            .map(H160::from)
109            .collect();
110        Ok(Self {
111            chain_id: Some(chain_id),
112            wallet: Some(wallet),
113            book_addrs: Some(book_addrs),
114            ..self.clone()
115        })
116    }
117
118    fn with_subaccount_name_bytes(&self, subaccount_name: [u8; 12]) -> Self {
119        Self {
120            subaccount_name_bytes: Some(subaccount_name),
121            ..self.clone()
122        }
123    }
124    fn wallet(&self) -> Result<&Wallet<SigningKey>> {
125        self.wallet.as_ref().ok_or(none_error("wallet"))
126    }
127
128    fn subaccount_name_bytes(&self) -> [u8; 12] {
129        self.subaccount_name_bytes.unwrap_or(to_bytes12("default"))
130    }
131
132    fn node_url(&self) -> String {
133        self.custom_node_url
134            .clone()
135            .unwrap_or(self.deployment.node_url.clone())
136    }
137
138    fn endpoint_addr(&self) -> H160 {
139        self.deployment.endpoint
140    }
141
142    fn querier_addr(&self) -> H160 {
143        self.deployment.querier
144    }
145
146    fn chain_id(&self) -> Result<U256> {
147        fields_to_vars!(self, chain_id);
148        Ok(chain_id)
149    }
150
151    fn book_addr(&self, product_id: u32) -> Result<H160> {
152        fields_to_vars!(self, (book_addrs: clone));
153        if product_id as usize >= book_addrs.len() {
154            return Err(eyre!(ClientError::ProductIdOutOfBounds(product_id)));
155        }
156        Ok(book_addrs[product_id as usize])
157    }
158
159    fn is_rest_client(&self) -> bool {
160        true
161    }
162}
163
164impl VertexBuilder for VertexClient {}
165
166#[async_trait]
167impl VertexExecute for VertexClient {
168    async fn execute(
169        &self,
170        execute: engine::Execute,
171    ) -> Result<Option<engine::ExecuteResponseData>> {
172        let url = format!("{}/execute", self.gateway_url);
173        let response: ExecuteResponse = self.client.post_request(&url, &execute).await?;
174        extract_response_data!(response, engine::ExecuteResponse => engine::ExecuteResponseData)
175    }
176
177    async fn execute_trigger(
178        &self,
179        execute: trigger::Execute,
180    ) -> Result<Option<engine::ExecuteResponseData>> {
181        let url = format!("{}/execute", self.trigger_url);
182        let response: ExecuteResponse = self.client.post_request(&url, &execute).await?;
183        extract_response_data!(response, engine::ExecuteResponse => engine::ExecuteResponseData)
184    }
185
186    async fn submit_slow_mode_tx(
187        &self,
188        params: SubmitSlowModeTxParams,
189    ) -> Result<Option<TransactionReceipt>> {
190        if params.mints_fee {
191            self.mint_mock_erc20(0, SLOW_MODE_FEE).await?;
192            if let Some(sleep_secs) = params.erc20_sleep_secs {
193                println!("sleeping for {}s (erc20)", sleep_secs);
194                tokio::time::sleep(Duration::from_secs(sleep_secs)).await;
195            }
196        }
197        if params.approves_fee {
198            self.approve_endpoint_allowance(0, SLOW_MODE_FEE).await?;
199            if let Some(sleep_secs) = params.erc20_sleep_secs {
200                println!("sleeping for {}s (erc20)", sleep_secs);
201                tokio::time::sleep(Duration::from_secs(sleep_secs)).await;
202            }
203        }
204        let endpoint = self.endpoint()?;
205        let mut tx = endpoint.submit_slow_mode_transaction(params.tx);
206        if let Some(gas_price) = params.gas_price {
207            tx = tx.gas_price(gas_price)
208        }
209        let tx_receipt = tx.send().await?.log_msg("pending tx").await?;
210        Ok(tx_receipt)
211    }
212
213    async fn deposit_collateral(
214        &self,
215        deposit_collateral_params: DepositCollateralParams,
216    ) -> Result<Option<TransactionReceipt>> {
217        deposit_collateral(self, deposit_collateral_params).await
218    }
219}
220
221#[async_trait]
222impl VertexQuery for VertexClient {
223    async fn query(&self, query: engine::Query) -> Result<engine::QueryResponseData> {
224        let url = format!("{}/query", self.gateway_url);
225        let response: engine::QueryResponse = self.client.post_request(&url, &query).await?;
226        let data =
227            extract_response_data!(response, engine::QueryResponse => engine::QueryResponseData)?;
228        Ok(data.unwrap())
229    }
230
231    async fn query_trigger(&self, query: trigger::Query) -> Result<trigger::QueryResponseData> {
232        let url = format!("{}/query", self.trigger_url);
233        let response: trigger::QueryResponse = self.client.post_request(&url, &query).await?;
234        let data =
235            extract_response_data!(response, trigger::QueryResponse => trigger::QueryResponseData)?;
236        Ok(data.unwrap())
237    }
238
239    async fn query_v2<R: DeserializeOwned + Send>(&self, path: &str, query: QueryV2) -> Result<R> {
240        let params = serde_url_params::to_string(&query)?;
241        let url = format!("{}/v2{path}?{params}", self.gateway_url.replace("/v1", ""));
242        self.client.get_request(&url).await
243    }
244}
245
246#[async_trait]
247impl VertexIndexer for VertexClient {
248    async fn query<R: DeserializeOwned + Send>(&self, query: indexer::Query) -> Result<R> {
249        self.client.post_request(&self.archive_url, &query).await
250    }
251
252    async fn query_v2<R: DeserializeOwned + Send>(
253        &self,
254        path: &str,
255        query: indexer::QueryV2,
256    ) -> Result<R> {
257        let params = serde_url_params::to_string(&query)?;
258        let url = format!("{}/v2{path}?{params}", self.archive_url.replace("/v1", ""));
259        self.client.get_request(&url).await
260    }
261}