Skip to main content

polymarket_us/
resources.rs

1use crate::client::PolymarketUsClient;
2use crate::error::PolymarketUsError;
3use crate::types;
4use reqwest::Method;
5use serde::Serialize;
6
7// ============================================================================
8// Markets Resource
9// ============================================================================
10
11#[derive(Clone)]
12pub struct MarketsClient<'a> {
13    client: &'a PolymarketUsClient,
14}
15
16impl<'a> MarketsClient<'a> {
17    pub fn new(client: &'a PolymarketUsClient) -> Self {
18        Self { client }
19    }
20
21    /// List all markets
22    pub async fn list(&self) -> Result<types::MarketsResponse, PolymarketUsError> {
23        self.list_with_query::<()>(None).await
24    }
25
26    /// List markets with query parameters
27    pub async fn list_with_query<Q: Serialize>(
28        &self,
29        query: Option<&Q>,
30    ) -> Result<types::MarketsResponse, PolymarketUsError> {
31        self.client
32            .internal_request(Method::GET, "/v1/markets", query, None::<&()>, false)
33            .await
34    }
35
36    /// List markets (authenticated)
37    pub async fn list_authenticated(&self) -> Result<types::MarketsResponse, PolymarketUsError> {
38        self.list_authenticated_with_query::<()>(None).await
39    }
40
41    /// List markets with query parameters (authenticated)
42    pub async fn list_authenticated_with_query<Q: Serialize>(
43        &self,
44        query: Option<&Q>,
45    ) -> Result<types::MarketsResponse, PolymarketUsError> {
46        self.client
47            .internal_request(Method::GET, "/v1/markets", query, None::<&()>, true)
48            .await
49    }
50
51    /// Get order book for a market
52    pub async fn order_book(&self, symbol: &str) -> Result<types::OrderBook, PolymarketUsError> {
53        self.client
54            .internal_request::<(), (), types::OrderBook>(
55                Method::GET,
56                &format!("/v1/markets/{symbol}/book"),
57                None,
58                None,
59                false,
60            )
61            .await
62    }
63
64    /// Get best bid/offer for a market
65    pub async fn bbo(&self, symbol: &str) -> Result<types::BestBidOffer, PolymarketUsError> {
66        self.client
67            .internal_request::<(), (), types::BestBidOffer>(
68                Method::GET,
69                &format!("/v1/markets/{symbol}/bbo"),
70                None,
71                None,
72                false,
73            )
74            .await
75    }
76
77    /// Get settlement price for a market
78    pub async fn settlement_price(
79        &self,
80        symbol: &str,
81    ) -> Result<types::SettlementPrice, PolymarketUsError> {
82        self.client
83            .internal_request::<(), (), types::SettlementPrice>(
84                Method::GET,
85                &format!("/v1/markets/{symbol}/settlement"),
86                None,
87                None,
88                false,
89            )
90            .await
91    }
92}
93
94// ============================================================================
95// Events Resource
96// ============================================================================
97
98#[derive(Clone)]
99pub struct EventsClient<'a> {
100    client: &'a PolymarketUsClient,
101}
102
103impl<'a> EventsClient<'a> {
104    pub fn new(client: &'a PolymarketUsClient) -> Self {
105        Self { client }
106    }
107
108    /// List all events
109    pub async fn list(&self) -> Result<types::EventsResponse, PolymarketUsError> {
110        self.list_with_query::<()>(None).await
111    }
112
113    /// List events with query parameters
114    pub async fn list_with_query<Q: Serialize>(
115        &self,
116        query: Option<&Q>,
117    ) -> Result<types::EventsResponse, PolymarketUsError> {
118        self.client
119            .internal_request(Method::GET, "/v1/events", query, None::<&()>, false)
120            .await
121    }
122
123    /// Get event by ID
124    pub async fn retrieve(&self, event_id: &str) -> Result<types::UsEvent, PolymarketUsError> {
125        self.client
126            .internal_request::<(), (), types::UsEvent>(
127                Method::GET,
128                &format!("/v1/events/{event_id}"),
129                None,
130                None,
131                false,
132            )
133            .await
134    }
135
136    /// Get event by slug
137    pub async fn retrieve_by_slug(&self, slug: &str) -> Result<types::UsEvent, PolymarketUsError> {
138        self.client
139            .internal_request::<(), (), types::UsEvent>(
140                Method::GET,
141                &format!("/v1/events/by-slug/{slug}"),
142                None,
143                None,
144                false,
145            )
146            .await
147    }
148}
149
150// ============================================================================
151// Orders Resource
152// ============================================================================
153
154#[derive(Clone)]
155pub struct OrdersClient<'a> {
156    client: &'a PolymarketUsClient,
157}
158
159impl<'a> OrdersClient<'a> {
160    pub fn new(client: &'a PolymarketUsClient) -> Self {
161        Self { client }
162    }
163
164    /// Create a new order
165    pub async fn create(
166        &self,
167        body: &types::PlaceOrderRequest,
168    ) -> Result<types::PlaceOrderResponse, PolymarketUsError> {
169        self.client
170            .internal_request(Method::POST, "/v1/orders", None::<&()>, Some(body), true)
171            .await
172    }
173
174    /// Place an order (alternative endpoint)
175    pub async fn place(
176        &self,
177        body: &types::PlaceOrderRequest,
178    ) -> Result<types::PlaceOrderResponse, PolymarketUsError> {
179        self.client
180            .internal_request(
181                Method::POST,
182                "/v1/trading/orders",
183                None::<&()>,
184                Some(body),
185                true,
186            )
187            .await
188    }
189
190    /// Place multiple orders atomically
191    pub async fn place_batch(
192        &self,
193        body: &types::BatchedOrderRequest,
194    ) -> Result<types::BatchedOrderResponse, PolymarketUsError> {
195        self.client
196            .internal_request(
197                Method::POST,
198                "/v1/orders/batched",
199                None::<&()>,
200                Some(body),
201                true,
202            )
203            .await
204    }
205
206    /// Get all open orders
207    pub async fn open<Q: Serialize>(
208        &self,
209        query: Option<&Q>,
210    ) -> Result<types::GetOpenOrdersResponse, PolymarketUsError> {
211        self.client
212            .internal_request(Method::GET, "/v1/orders/open", query, None::<&()>, true)
213            .await
214    }
215
216    /// Get order by ID
217    pub async fn retrieve(
218        &self,
219        order_id: &str,
220    ) -> Result<types::PlaceOrderResponse, PolymarketUsError> {
221        self.client
222            .internal_request::<(), (), types::PlaceOrderResponse>(
223                Method::GET,
224                &format!("/v1/order/{order_id}"),
225                None,
226                None,
227                true,
228            )
229            .await
230    }
231
232    /// Cancel an order
233    pub async fn cancel(
234        &self,
235        order_id: &str,
236        body: &types::CancelOrderParams,
237    ) -> Result<(), PolymarketUsError> {
238        let _: serde_json::Value = self
239            .client
240            .internal_request(
241                Method::POST,
242                &format!("/v1/order/{order_id}/cancel"),
243                None::<&()>,
244                Some(body),
245                true,
246            )
247            .await?;
248        Ok(())
249    }
250
251    /// Cancel order by trading endpoint
252    pub async fn cancel_trading(
253        &self,
254        order_id: &str,
255    ) -> Result<types::CancelOrderResponse, PolymarketUsError> {
256        self.client
257            .internal_request::<(), (), types::CancelOrderResponse>(
258                Method::DELETE,
259                &format!("/v1/trading/orders/{order_id}"),
260                None,
261                None,
262                true,
263            )
264            .await
265    }
266
267    /// Cancel all open orders
268    pub async fn cancel_all(
269        &self,
270        body: &types::CancelAllOrdersParams,
271    ) -> Result<types::CancelAllOrdersResponse, PolymarketUsError> {
272        self.client
273            .internal_request(
274                Method::POST,
275                "/v1/orders/open/cancel",
276                None::<&()>,
277                Some(body),
278                true,
279            )
280            .await
281    }
282
283    /// Modify an open order
284    pub async fn modify(
285        &self,
286        order_id: &str,
287        body: &types::ModifyOrderRequest,
288    ) -> Result<(), PolymarketUsError> {
289        let _: serde_json::Value = self
290            .client
291            .internal_request(
292                Method::POST,
293                &format!("/v1/order/{order_id}/modify"),
294                None::<&()>,
295                Some(body),
296                true,
297            )
298            .await?;
299        Ok(())
300    }
301
302    /// Preview an order
303    pub async fn preview(
304        &self,
305        body: &types::PreviewOrderRequest,
306    ) -> Result<types::PreviewOrderResponse, PolymarketUsError> {
307        self.client
308            .internal_request(
309                Method::POST,
310                "/v1/order/preview",
311                None::<&()>,
312                Some(body),
313                true,
314            )
315            .await
316    }
317
318    /// Close a position
319    pub async fn close_position(
320        &self,
321        body: &types::ClosePositionRequest,
322    ) -> Result<types::ClosePositionResponse, PolymarketUsError> {
323        self.client
324            .internal_request(
325                Method::POST,
326                "/v1/order/close-position",
327                None::<&()>,
328                Some(body),
329                true,
330            )
331            .await
332    }
333}
334
335// ============================================================================
336// Account Resource
337// ============================================================================
338
339#[derive(Clone)]
340pub struct AccountClient<'a> {
341    client: &'a PolymarketUsClient,
342}
343
344impl<'a> AccountClient<'a> {
345    pub fn new(client: &'a PolymarketUsClient) -> Self {
346        Self { client }
347    }
348
349    /// Get account balances
350    pub async fn balances(&self) -> Result<types::AccountBalancesResponse, PolymarketUsError> {
351        self.client
352            .internal_request::<(), (), types::AccountBalancesResponse>(
353                Method::GET,
354                "/v1/account/balances",
355                None,
356                None,
357                true,
358            )
359            .await
360    }
361}
362
363// ============================================================================
364// Portfolio Resource
365// ============================================================================
366
367#[derive(Clone)]
368pub struct PortfolioClient<'a> {
369    client: &'a PolymarketUsClient,
370}
371
372impl<'a> PortfolioClient<'a> {
373    pub fn new(client: &'a PolymarketUsClient) -> Self {
374        Self { client }
375    }
376
377    /// Get portfolio positions
378    pub async fn positions(&self) -> Result<types::PortfolioPositionsResponse, PolymarketUsError> {
379        self.client
380            .internal_request::<(), (), types::PortfolioPositionsResponse>(
381                Method::GET,
382                "/v1/portfolio/positions",
383                None,
384                None,
385                true,
386            )
387            .await
388    }
389
390    /// Get portfolio activities with optional query parameters
391    pub async fn activities<Q: Serialize>(
392        &self,
393        query: Option<&Q>,
394    ) -> Result<types::PortfolioActivitiesResponse, PolymarketUsError> {
395        self.client
396            .internal_request(
397                Method::GET,
398                "/v1/portfolio/activities",
399                query,
400                None::<&()>,
401                true,
402            )
403            .await
404    }
405}
406
407// ============================================================================
408// Search Resource
409// ============================================================================
410
411#[derive(Clone)]
412pub struct SearchClient<'a> {
413    client: &'a PolymarketUsClient,
414}
415
416impl<'a> SearchClient<'a> {
417    pub fn new(client: &'a PolymarketUsClient) -> Self {
418        Self { client }
419    }
420
421    /// Full-text search across markets and events
422    pub async fn search<Q: Serialize>(
423        &self,
424        query: Option<&Q>,
425    ) -> Result<types::SearchResults, PolymarketUsError> {
426        self.client
427            .internal_request(Method::GET, "/v1/search", query, None::<&()>, false)
428            .await
429    }
430
431    /// Search markets
432    pub async fn markets<Q: Serialize>(
433        &self,
434        query: Option<&Q>,
435    ) -> Result<types::MarketsResponse, PolymarketUsError> {
436        self.client
437            .internal_request(Method::GET, "/v1/search/markets", query, None::<&()>, false)
438            .await
439    }
440
441    /// Search events
442    pub async fn events<Q: Serialize>(
443        &self,
444        query: Option<&Q>,
445    ) -> Result<types::EventsResponse, PolymarketUsError> {
446        self.client
447            .internal_request(Method::GET, "/v1/search/events", query, None::<&()>, false)
448            .await
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455
456    fn create_test_client() -> PolymarketUsClient {
457        PolymarketUsClient::builder().build().unwrap()
458    }
459
460    // ========================================================================
461    // MarketsClient Tests
462    // ========================================================================
463
464    #[test]
465    fn markets_client_creation() {
466        let client = create_test_client();
467        // Just verify it can be created; actual API calls need mocking
468        let _markets = client.markets();
469    }
470
471    #[test]
472    fn markets_client_has_expected_methods() {
473        let client = create_test_client();
474        let markets = client.markets();
475
476        // Verify methods exist by checking they can be called (won't execute due to no server)
477        // This is primarily a compile-time check through the type system
478        assert_eq!(
479            std::any::type_name_of_val(&markets),
480            "polymarket_us::resources::MarketsClient<'_>"
481        );
482    }
483
484    // ========================================================================
485    // EventsClient Tests
486    // ========================================================================
487
488    #[test]
489    fn events_client_creation() {
490        let client = create_test_client();
491        let _events = client.events();
492    }
493
494    #[test]
495    fn events_client_type_check() {
496        let client = create_test_client();
497        let events = client.events();
498        assert_eq!(
499            std::any::type_name_of_val(&events),
500            "polymarket_us::resources::EventsClient<'_>"
501        );
502    }
503
504    // ========================================================================
505    // OrdersClient Tests
506    // ========================================================================
507
508    #[test]
509    fn orders_client_creation() {
510        let client = create_test_client();
511        let _orders = client.orders();
512    }
513
514    #[test]
515    fn orders_client_type_check() {
516        let client = create_test_client();
517        let orders = client.orders();
518        assert_eq!(
519            std::any::type_name_of_val(&orders),
520            "polymarket_us::resources::OrdersClient<'_>"
521        );
522    }
523
524    #[test]
525    fn place_order_request_serializes() {
526        let req = types::PlaceOrderRequest {
527            symbol: "BTC-USD".to_string(),
528            action: types::OrderAction::Buy,
529            outcome_side: types::OrderSide::Long,
530            order_type: types::OrderType::Limit,
531            price: types::Money {
532                value: "0.50".to_string(),
533                currency: "USD".to_string(),
534            },
535            quantity: 100,
536            tif: types::TimeInForce::GoodTillCancel,
537            client_order_id: Some("test-123".to_string()),
538            post_only: false,
539            expires_at: None,
540        };
541
542        let json = serde_json::to_string(&req).expect("should serialize");
543        assert!(json.contains("BTC-USD"));
544        assert!(json.contains("ORDER_ACTION_BUY"));
545        assert!(json.contains("0.50"));
546    }
547
548    #[test]
549    fn batched_order_request_serializes() {
550        let req = types::BatchedOrderRequest {
551            orders: vec![types::PlaceOrderRequest {
552                symbol: "BTC-USD".to_string(),
553                action: types::OrderAction::Buy,
554                outcome_side: types::OrderSide::Long,
555                order_type: types::OrderType::Limit,
556                price: types::Money {
557                    value: "0.50".to_string(),
558                    currency: "USD".to_string(),
559                },
560                quantity: 100,
561                tif: types::TimeInForce::GoodTillCancel,
562                client_order_id: None,
563                post_only: false,
564                expires_at: None,
565            }],
566            atomic: true,
567        };
568
569        let json = serde_json::to_string(&req).expect("should serialize");
570        assert!(json.contains("atomic"));
571        assert!(json.contains("BTC-USD"));
572    }
573
574    #[test]
575    fn cancel_order_params_serializes() {
576        let params = types::CancelOrderParams { quantity: Some(50) };
577        let json = serde_json::to_string(&params).expect("should serialize");
578        assert!(json.contains("50"));
579    }
580
581    // ========================================================================
582    // AccountClient Tests
583    // ========================================================================
584
585    #[test]
586    fn account_client_creation() {
587        let client = create_test_client();
588        let _account = client.account();
589    }
590
591    #[test]
592    fn account_client_type_check() {
593        let client = create_test_client();
594        let account = client.account();
595        assert_eq!(
596            std::any::type_name_of_val(&account),
597            "polymarket_us::resources::AccountClient<'_>"
598        );
599    }
600
601    // ========================================================================
602    // PortfolioClient Tests
603    // ========================================================================
604
605    #[test]
606    fn portfolio_client_creation() {
607        let client = create_test_client();
608        let _portfolio = client.portfolio();
609    }
610
611    #[test]
612    fn portfolio_client_type_check() {
613        let client = create_test_client();
614        let portfolio = client.portfolio();
615        assert_eq!(
616            std::any::type_name_of_val(&portfolio),
617            "polymarket_us::resources::PortfolioClient<'_>"
618        );
619    }
620
621    // ========================================================================
622    // SearchClient Tests
623    // ========================================================================
624
625    #[test]
626    fn search_client_creation() {
627        let client = create_test_client();
628        let _search = client.search();
629    }
630
631    #[test]
632    fn search_client_type_check() {
633        let client = create_test_client();
634        let search = client.search();
635        assert_eq!(
636            std::any::type_name_of_val(&search),
637            "polymarket_us::resources::SearchClient<'_>"
638        );
639    }
640
641    // ========================================================================
642    // Type Serialization Tests
643    // ========================================================================
644
645    #[test]
646    fn money_serializes() {
647        let money = types::Money {
648            value: "100.00".to_string(),
649            currency: "USD".to_string(),
650        };
651        let json = serde_json::to_string(&money).expect("should serialize");
652        assert!(json.contains("100.00"));
653        assert!(json.contains("USD"));
654    }
655
656    #[test]
657    fn preview_order_request_serializes() {
658        let req = types::PreviewOrderRequest {
659            symbol: "BTC-USD".to_string(),
660            action: types::OrderAction::Sell,
661            outcome_side: types::OrderSide::Short,
662            order_type: types::OrderType::Limit,
663            price: types::Money {
664                value: "0.75".to_string(),
665                currency: "USD".to_string(),
666            },
667            quantity: 50,
668        };
669
670        let json = serde_json::to_string(&req).expect("should serialize");
671        assert!(json.contains("ORDER_ACTION_SELL"));
672        assert!(json.contains("0.75"));
673    }
674
675    #[test]
676    fn close_position_request_serializes() {
677        let req = types::ClosePositionRequest {
678            symbol: "BTC-USD".to_string(),
679            quantity: 100,
680        };
681        let json = serde_json::to_string(&req).expect("should serialize");
682        assert!(json.contains("BTC-USD"));
683        assert!(json.contains("100"));
684    }
685
686    #[test]
687    fn modify_order_request_serializes() {
688        let req = types::ModifyOrderRequest {
689            price: types::Money {
690                value: "0.60".to_string(),
691                currency: "USD".to_string(),
692            },
693            quantity: 200,
694        };
695        let json = serde_json::to_string(&req).expect("should serialize");
696        assert!(json.contains("0.60"));
697        assert!(json.contains("200"));
698    }
699
700    #[test]
701    fn order_book_deserializes() {
702        let json = r#"{"bids": [{"price": "0.50", "quantity": "100"}], "asks": [{"price": "0.55", "quantity": "150"}]}"#;
703        let book: types::OrderBook = serde_json::from_str(json).expect("should deserialize");
704        assert_eq!(book.bids.len(), 1);
705        assert_eq!(book.asks.len(), 1);
706        assert_eq!(book.bids[0].price, "0.50");
707    }
708
709    #[test]
710    fn best_bid_offer_deserializes() {
711        let json = r#"{"bid": {"price": "0.50", "quantity": "100"}, "ask": {"price": "0.55", "quantity": "150"}}"#;
712        let bbo: types::BestBidOffer = serde_json::from_str(json).expect("should deserialize");
713        assert!(bbo.bid.is_some());
714        assert!(bbo.ask.is_some());
715        assert_eq!(bbo.bid.unwrap().price, "0.50");
716    }
717
718    #[test]
719    fn price_level_serializes() {
720        let level = types::PriceLevel {
721            price: "0.55".to_string(),
722            quantity: "200".to_string(),
723        };
724        let json = serde_json::to_string(&level).expect("should serialize");
725        assert!(json.contains("0.55"));
726        assert!(json.contains("200"));
727    }
728
729    #[test]
730    fn settlement_price_deserializes() {
731        let json = r#"{"symbol": "BTC-USD", "price": "0.75", "timestamp": "2024-01-01T00:00:00Z"}"#;
732        let settlement: types::SettlementPrice =
733            serde_json::from_str(json).expect("should deserialize");
734        assert_eq!(settlement.symbol, "BTC-USD");
735        assert_eq!(settlement.price, "0.75");
736    }
737
738    #[test]
739    fn user_balance_deserializes() {
740        let json = r#"{
741            "currentBalance": 1000.00,
742            "currency": "USD",
743            "buyingPower": 950.00,
744            "assetNotional": 500.00,
745            "assetAvailable": 450.00
746        }"#;
747        let balance: types::UserBalance = serde_json::from_str(json).expect("should deserialize");
748        assert_eq!(balance.current_balance, 1000.00);
749        assert_eq!(balance.buying_power, 950.00);
750        assert_eq!(balance.currency, "USD");
751    }
752
753    #[test]
754    fn cancel_all_orders_params_serializes() {
755        let params = types::CancelAllOrdersParams {
756            symbol: Some("BTC-USD".to_string()),
757        };
758        let json = serde_json::to_string(&params).expect("should serialize");
759        assert!(json.contains("BTC-USD"));
760    }
761
762    #[test]
763    fn us_position_deserializes() {
764        let json = r#"{
765            "symbol": "BTC-USD",
766            "quantity": 100,
767            "avgEntryPrice": "0.50",
768            "unrealizedPnl": "25.00"
769        }"#;
770        let position: types::UsPosition = serde_json::from_str(json).expect("should deserialize");
771        assert_eq!(position.symbol, "BTC-USD");
772        assert_eq!(position.quantity, 100);
773        assert_eq!(position.avg_entry_price, "0.50");
774    }
775
776    #[test]
777    fn us_market_deserializes() {
778        let json = r#"{
779            "id": "market-123",
780            "slug": "btc-usd",
781            "question": "Will BTC be above $50k?",
782            "status": "open",
783            "category": "crypto",
784            "startDate": "2024-01-01",
785            "endDate": "2024-12-31",
786            "description": "Test market",
787            "active": true,
788            "closed": false,
789            "marketType": "binary",
790            "marketSides": [],
791            "instruments": [],
792            "outcomes": []
793        }"#;
794        let market: types::UsMarket = serde_json::from_str(json).expect("should deserialize");
795        assert_eq!(market.id, "market-123");
796        assert_eq!(market.slug, "btc-usd");
797        assert!(market.active);
798    }
799
800    #[test]
801    fn us_event_deserializes() {
802        let json = r#"{
803            "id": "event-123",
804            "slug": "2024-election",
805            "title": "2024 US Election",
806            "category": "politics",
807            "startDate": "2024-01-01",
808            "endDate": "2024-11-05"
809        }"#;
810        let event: types::UsEvent = serde_json::from_str(json).expect("should deserialize");
811        assert_eq!(event.id, "event-123");
812        assert_eq!(event.slug, "2024-election");
813        assert_eq!(event.title, "2024 US Election");
814    }
815
816    #[test]
817    fn client_has_all_resource_accessors() {
818        let client = create_test_client();
819
820        // Just verify all resources can be accessed
821        let _ = client.markets();
822        let _ = client.events();
823        let _ = client.orders();
824        let _ = client.account();
825        let _ = client.portfolio();
826        let _ = client.search();
827
828        // If this compiles, all accessors are available
829    }
830
831    #[test]
832    fn resources_are_cheap_to_clone() {
833        let client = create_test_client();
834        let markets1 = client.markets();
835        let _markets2 = markets1.clone();
836    }
837}