1use crate::auth::UsAuth;
2use crate::error::PolymarketUsError;
3use crate::resources::{
4 AccountClient, EventsClient, MarketsClient, OrdersClient, PortfolioClient, SearchClient,
5};
6use crate::types;
7use reqwest::Method;
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10
11const DEFAULT_GATEWAY_BASE_URL: &str = "https://gateway.polymarket.us";
12const DEFAULT_API_BASE_URL: &str = "https://api.polymarket.us";
13
14#[derive(Clone)]
15pub struct PolymarketUsClient {
16 http: reqwest::Client,
17 gateway_base_url: String,
18 api_base_url: String,
19 auth: Option<UsAuth>,
20}
21
22pub struct PolymarketUsClientBuilder {
23 gateway_base_url: String,
24 api_base_url: String,
25 auth: Option<UsAuth>,
26 http: Option<reqwest::Client>,
27 timeout: std::time::Duration,
28}
29
30impl Default for PolymarketUsClientBuilder {
31 fn default() -> Self {
32 Self {
33 gateway_base_url: DEFAULT_GATEWAY_BASE_URL.to_string(),
34 api_base_url: DEFAULT_API_BASE_URL.to_string(),
35 auth: None,
36 http: None,
37 timeout: std::time::Duration::from_secs(30),
38 }
39 }
40}
41
42impl PolymarketUsClientBuilder {
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn gateway_base_url(mut self, url: impl Into<String>) -> Self {
48 self.gateway_base_url = url.into();
49 self
50 }
51
52 pub fn api_base_url(mut self, url: impl Into<String>) -> Self {
53 self.api_base_url = url.into();
54 self
55 }
56
57 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
58 self.timeout = timeout;
59 self
60 }
61
62 pub fn auth(mut self, auth: UsAuth) -> Self {
63 self.auth = Some(auth);
64 self
65 }
66
67 pub fn http_client(mut self, http: reqwest::Client) -> Self {
68 self.http = Some(http);
69 self
70 }
71
72 pub fn build(self) -> Result<PolymarketUsClient, PolymarketUsError> {
73 let http = match self.http {
74 Some(http) => http,
75 None => reqwest::Client::builder().timeout(self.timeout).build()?,
76 };
77 Ok(PolymarketUsClient {
78 http,
79 gateway_base_url: self.gateway_base_url,
80 api_base_url: self.api_base_url,
81 auth: self.auth,
82 })
83 }
84}
85
86impl PolymarketUsClient {
87 pub fn builder() -> PolymarketUsClientBuilder {
88 PolymarketUsClientBuilder::new()
89 }
90
91 pub fn with_reqwest(http: reqwest::Client, auth: Option<UsAuth>) -> Self {
92 Self {
93 http,
94 gateway_base_url: DEFAULT_GATEWAY_BASE_URL.to_string(),
95 api_base_url: DEFAULT_API_BASE_URL.to_string(),
96 auth,
97 }
98 }
99
100 pub fn auth(&self) -> Option<&UsAuth> {
101 self.auth.as_ref()
102 }
103
104 pub fn api_base_url(&self) -> &str {
105 &self.api_base_url
106 }
107
108 pub fn markets(&self) -> MarketsClient<'_> {
114 MarketsClient::new(self)
115 }
116
117 pub fn events(&self) -> EventsClient<'_> {
119 EventsClient::new(self)
120 }
121
122 pub fn orders(&self) -> OrdersClient<'_> {
124 OrdersClient::new(self)
125 }
126
127 pub fn account(&self) -> AccountClient<'_> {
129 AccountClient::new(self)
130 }
131
132 pub fn portfolio(&self) -> PortfolioClient<'_> {
134 PortfolioClient::new(self)
135 }
136
137 pub fn search(&self) -> SearchClient<'_> {
139 SearchClient::new(self)
140 }
141
142 pub async fn health(&self) -> Result<types::HealthResponse, PolymarketUsError> {
143 self.internal_request::<(), (), types::HealthResponse>(
144 Method::GET,
145 "/v1/health",
146 None,
147 None,
148 false,
149 )
150 .await
151 }
152
153 #[deprecated(since = "0.3.0", note = "use client.markets().list() instead")]
158 pub async fn markets_list(&self) -> Result<types::MarketsResponse, PolymarketUsError> {
159 self.markets().list().await
160 }
161
162 #[deprecated(
163 since = "0.3.0",
164 note = "use client.markets().list_with_query() instead"
165 )]
166 pub async fn markets_list_with_query<Q: Serialize>(
167 &self,
168 query: Option<&Q>,
169 ) -> Result<types::MarketsResponse, PolymarketUsError> {
170 self.markets().list_with_query(query).await
171 }
172
173 #[deprecated(
174 since = "0.3.0",
175 note = "use client.markets().list_authenticated() instead"
176 )]
177 pub async fn markets_list_authenticated(
178 &self,
179 ) -> Result<types::MarketsResponse, PolymarketUsError> {
180 self.markets().list_authenticated().await
181 }
182
183 #[deprecated(
184 since = "0.3.0",
185 note = "use client.markets().list_authenticated_with_query() instead"
186 )]
187 pub async fn markets_list_authenticated_with_query<Q: Serialize>(
188 &self,
189 query: Option<&Q>,
190 ) -> Result<types::MarketsResponse, PolymarketUsError> {
191 self.markets().list_authenticated_with_query(query).await
192 }
193
194 #[deprecated(since = "0.3.0", note = "use client.account().balances() instead")]
195 pub async fn account_balances(
196 &self,
197 ) -> Result<types::AccountBalancesResponse, PolymarketUsError> {
198 self.account().balances().await
199 }
200
201 #[deprecated(since = "0.3.0", note = "use client.portfolio().positions() instead")]
202 pub async fn portfolio_positions(
203 &self,
204 ) -> Result<types::PortfolioPositionsResponse, PolymarketUsError> {
205 self.portfolio().positions().await
206 }
207
208 #[deprecated(since = "0.3.0", note = "use client.portfolio().activities() instead")]
209 pub async fn portfolio_activities<Q: Serialize>(
210 &self,
211 query: Option<&Q>,
212 ) -> Result<types::PortfolioActivitiesResponse, PolymarketUsError> {
213 self.portfolio().activities(query).await
214 }
215
216 #[deprecated(since = "0.3.0", note = "use client.orders().place() instead")]
217 pub async fn place_order(
218 &self,
219 body: &types::PlaceOrderRequest,
220 ) -> Result<types::PlaceOrderResponse, PolymarketUsError> {
221 self.orders().place(body).await
222 }
223
224 #[deprecated(since = "0.3.0", note = "use client.orders().place_batch() instead")]
225 pub async fn place_batched_orders(
226 &self,
227 body: &types::BatchedOrderRequest,
228 ) -> Result<types::BatchedOrderResponse, PolymarketUsError> {
229 self.orders().place_batch(body).await
230 }
231
232 #[deprecated(since = "0.3.0", note = "use client.orders().cancel_trading() instead")]
233 pub async fn cancel_trading_order(
234 &self,
235 order_id: &str,
236 ) -> Result<types::CancelOrderResponse, PolymarketUsError> {
237 self.orders().cancel_trading(order_id).await
238 }
239
240 #[deprecated(since = "0.3.0", note = "use client.orders().create() instead")]
241 pub async fn orders_create(
242 &self,
243 body: &types::PlaceOrderRequest,
244 ) -> Result<types::PlaceOrderResponse, PolymarketUsError> {
245 self.orders().create(body).await
246 }
247
248 #[deprecated(since = "0.3.0", note = "use client.orders().open() instead")]
249 pub async fn orders_open<Q: Serialize>(
250 &self,
251 query: Option<&Q>,
252 ) -> Result<types::GetOpenOrdersResponse, PolymarketUsError> {
253 self.orders().open(query).await
254 }
255
256 #[deprecated(since = "0.3.0", note = "use client.orders().retrieve() instead")]
257 pub async fn order_retrieve(
258 &self,
259 order_id: &str,
260 ) -> Result<types::PlaceOrderResponse, PolymarketUsError> {
261 self.orders().retrieve(order_id).await
262 }
263
264 #[deprecated(since = "0.3.0", note = "use client.orders().cancel() instead")]
265 pub async fn order_cancel(
266 &self,
267 order_id: &str,
268 body: &types::CancelOrderParams,
269 ) -> Result<(), PolymarketUsError> {
270 self.orders().cancel(order_id, body).await
271 }
272
273 #[deprecated(since = "0.3.0", note = "use client.orders().modify() instead")]
274 pub async fn order_modify(
275 &self,
276 order_id: &str,
277 body: &types::ModifyOrderRequest,
278 ) -> Result<(), PolymarketUsError> {
279 self.orders().modify(order_id, body).await
280 }
281
282 #[deprecated(since = "0.3.0", note = "use client.orders().cancel_all() instead")]
283 pub async fn orders_cancel_all(
284 &self,
285 body: &types::CancelAllOrdersParams,
286 ) -> Result<types::CancelAllOrdersResponse, PolymarketUsError> {
287 self.orders().cancel_all(body).await
288 }
289
290 #[deprecated(since = "0.3.0", note = "use client.orders().preview() instead")]
291 pub async fn order_preview(
292 &self,
293 body: &types::PreviewOrderRequest,
294 ) -> Result<types::PreviewOrderResponse, PolymarketUsError> {
295 self.orders().preview(body).await
296 }
297
298 #[deprecated(since = "0.3.0", note = "use client.orders().close_position() instead")]
299 pub async fn order_close_position(
300 &self,
301 body: &types::ClosePositionRequest,
302 ) -> Result<types::ClosePositionResponse, PolymarketUsError> {
303 self.orders().close_position(body).await
304 }
305
306 pub(crate) async fn internal_request<Q: Serialize, B: Serialize, T: DeserializeOwned>(
311 &self,
312 method: Method,
313 path: &str,
314 query: Option<&Q>,
315 body: Option<&B>,
316 authenticated: bool,
317 ) -> Result<T, PolymarketUsError> {
318 let base = if authenticated {
319 &self.api_base_url
320 } else {
321 &self.gateway_base_url
322 };
323 let url = format!("{}{}", base, path);
324
325 let mut rb = self
326 .http
327 .request(method.clone(), &url)
328 .header("Content-Type", "application/json");
329 if let Some(query) = query {
330 rb = rb.query(query);
331 }
332 if let Some(body) = body {
333 rb = rb.json(body);
334 }
335 if authenticated {
336 let auth = self
337 .auth
338 .as_ref()
339 .ok_or(PolymarketUsError::MissingAuth("authenticated endpoint"))?;
340 for (name, value) in auth.signed_headers(method.as_str(), path) {
341 rb = rb.header(name, value);
342 }
343 }
344
345 let response = rb.send().await?;
346 let status = response.status();
347 let text = response.text().await?;
348
349 if !status.is_success() {
350 let message = extract_error_message(&text).unwrap_or_else(|| text.clone());
351 return Err(PolymarketUsError::from_status(status, message));
352 }
353
354 if text.trim().is_empty() {
355 serde_json::from_str("{}")
356 } else {
357 serde_json::from_str(&text)
358 }
359 .map_err(PolymarketUsError::from)
360 }
361}
362
363fn extract_error_message(text: &str) -> Option<String> {
364 let json: serde_json::Value = serde_json::from_str(text).ok()?;
365 json.get("message")
366 .and_then(|v| v.as_str())
367 .map(ToOwned::to_owned)
368 .or_else(|| {
369 json.get("error")
370 .and_then(|v| v.as_str())
371 .map(ToOwned::to_owned)
372 })
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 #[test]
380 fn builder_defaults_match_public_endpoints() {
381 let client = PolymarketUsClient::builder().build().unwrap();
382 assert_eq!(client.api_base_url(), "https://api.polymarket.us");
383 }
384}