1use polysqueeze::{
2 OrderArgs,
3 client::ClobClient,
4 errors::{PolyError, Result},
5 types::{GammaListParams, OrderType, Side},
6 wss::{WssUserClient, WssUserEvent},
7};
8use rust_decimal::{Decimal, prelude::FromPrimitive};
9use std::{env, str::FromStr};
10
11fn env_var(key: &str) -> String {
13 env::var(key).expect(&format!("{} must be set for the user-channel example", key))
14}
15
16#[tokio::main]
17async fn main() -> Result<()> {
18 let base_url =
19 env::var("POLY_API_URL").unwrap_or_else(|_| "https://clob.polymarket.com".into());
20 let private_key = env_var("POLY_PRIVATE_KEY");
21 let chain_id = env::var("POLY_CHAIN_ID")
22 .ok()
23 .and_then(|value| value.parse::<u64>().ok())
24 .unwrap_or(137);
25
26 let l1_client = ClobClient::with_l1_headers(&base_url, &private_key, chain_id);
27 let creds = l1_client.create_or_derive_api_key(None).await?;
28 let mut l2_client =
29 ClobClient::with_l2_headers(&base_url, &private_key, chain_id, creds.clone());
30
31 if let Ok(funder) = env::var("POLY_FUNDER") {
32 l2_client.set_funder(&funder)?;
33 }
34
35 let min_liquidity = env::var("POLY_WSS_MIN_LIQUIDITY")
36 .ok()
37 .and_then(|value| Decimal::from_str(&value).ok())
38 .unwrap_or_else(|| Decimal::from(1_000_000));
39
40 let gamma_params = GammaListParams {
41 limit: Some(5),
42 liquidity_num_min: Some(min_liquidity),
43 ..Default::default()
44 };
45 let markets_response = l2_client.get_markets(None, Some(&gamma_params)).await?;
46
47 let market_ids: Vec<String> = markets_response
48 .data
49 .iter()
50 .filter(|market| market.liquidity_num.unwrap_or(Decimal::ZERO) >= min_liquidity)
51 .map(|market| market.condition_id.clone())
52 .filter(|id| !id.is_empty())
53 .take(2)
54 .collect();
55
56 if market_ids.is_empty() {
57 return Err(PolyError::validation(
58 "Gamma did not return any markets with condition_ids",
59 ));
60 }
61
62 let primary_market = markets_response
63 .data
64 .iter()
65 .find(|market| {
66 !market.clob_token_ids.is_empty()
67 && market.liquidity_num.unwrap_or(Decimal::ZERO) >= min_liquidity
68 })
69 .ok_or_else(|| {
70 PolyError::validation("No Gamma markets returned a CLOB token id for trading")
71 })?;
72 let token_id = primary_market.clob_token_ids.first().unwrap().clone();
73
74 let book = l2_client.get_order_book(&token_id).await?;
75 let _best_ask = book.asks.first().ok_or_else(|| {
76 PolyError::validation("Order book has no asks; cannot derive a safe price")
77 })?;
78 let tick_size = primary_market.minimum_tick_size;
79 let max_bid_level = book
80 .bids
81 .iter()
82 .max_by(|a, b| {
83 a.size
84 .partial_cmp(&b.size)
85 .unwrap_or(std::cmp::Ordering::Equal)
86 })
87 .ok_or_else(|| PolyError::validation("Order book has no depth data"))?;
88 let far_price =
89 max_bid_level.price - tick_size * Decimal::from_u32(10).unwrap_or(Decimal::ZERO);
90 let order_price = if far_price > Decimal::ZERO {
91 far_price
92 } else {
93 max_bid_level.price - tick_size
94 };
95 let order_size = Decimal::new(5, 0); let order_args = OrderArgs::new(&token_id, order_price, order_size, Side::BUY);
97
98 let mut user_client = WssUserClient::new(creds.clone());
99 user_client.subscribe(market_ids.clone()).await?;
100
101 println!(
102 "Subscribed to user channel for markets {market_ids:?} (waiting for your cancel/update)..."
103 );
104 let signed_order = l2_client
105 .create_order(&order_args, None, None, None)
106 .await?;
107 let response = l2_client.post_order(signed_order, OrderType::GTC).await?;
108 let order_id = response
109 .get("orderID")
110 .and_then(|value| value.as_str())
111 .ok_or_else(|| PolyError::validation("Post order response missing orderID"))?
112 .to_string();
113 println!(
114 "Placed order on {} @ {}: {response:#}",
115 token_id, order_price
116 );
117 println!(
118 "Order id {} - cancel it via the `wss_cancel` example while this stream is running.",
119 order_id
120 );
121
122 loop {
123 match user_client.next_event().await {
124 Ok(WssUserEvent::Order(order)) => {
125 println!(
126 "order {} {} matched={} price={} side={}",
127 order.id,
128 order.message_type,
129 order.size_matched,
130 order.price,
131 order.side.as_str()
132 );
133 if order.message_type.eq_ignore_ascii_case("CANCELLATION") {
134 println!("Order {} cancelled; exiting.", order.id);
135 break;
136 }
137 }
138 Ok(WssUserEvent::Trade(trade)) => {
139 println!(
140 "trade {} {} {}@{} status={}",
141 trade.id,
142 trade.side.as_str(),
143 trade.size,
144 trade.price,
145 trade.status
146 );
147 }
148 Err(err) => {
149 eprintln!("user stream error: {}", err);
150 break;
151 }
152 }
153 }
154
155 Ok(())
156}