// Integration tests for polyfill-rs // These tests hit the real Polymarket API and are ignored by default // Run with: cargo test --test integration_tests -- --ignored --test-threads=1 use polyfill_rs::{ClientConfig, ClobClient, OrderArgs, Side}; use rust_decimal_macros::dec; use std::env; const HOST: &str = "https://clob.polymarket.com"; const CHAIN_ID: u64 = 137; fn load_env_vars() -> (String, Option, Option, Option) { dotenvy::dotenv().ok(); let private_key = env::var("POLYMARKET_PRIVATE_KEY").expect("POLYMARKET_PRIVATE_KEY must be set in .env"); let api_key = env::var("POLYMARKET_API_KEY").ok(); let api_secret = env::var("POLYMARKET_API_SECRET") .or_else(|_| env::var("POLYMARKET_SECRET")) .ok(); let api_passphrase = env::var("POLYMARKET_API_PASSPHRASE") .or_else(|_| env::var("POLYMARKET_PASSPHRASE")) .ok(); (private_key, api_key, api_secret, api_passphrase) } fn env_signature_type() -> Option { env::var("POLYMARKET_SIGNATURE_TYPE") .ok() .and_then(|value| value.parse::().ok()) } fn env_funder() -> Option { env::var("POLYMARKET_FUNDER") .or_else(|_| env::var("POLYMARKET_FUNDER_ADDRESS")) .ok() } fn bootstrap_client(private_key: &str) -> ClobClient { ClobClient::from_config(ClientConfig { base_url: HOST.to_string(), chain: CHAIN_ID, private_key: Some(private_key.to_string()), signature_type: env_signature_type(), funder: env_funder(), ..ClientConfig::default() }) .expect("failed to build bootstrap client") } fn authenticated_client( private_key: String, api_credentials: polyfill_rs::ApiCredentials, ) -> ClobClient { ClobClient::from_config(ClientConfig { base_url: HOST.to_string(), chain: CHAIN_ID, private_key: Some(private_key), api_credentials: Some(api_credentials), signature_type: env_signature_type(), funder: env_funder(), ..ClientConfig::default() }) .expect("failed to build authenticated client") } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_create_derive_api_key() { let (private_key, _, _, _) = load_env_vars(); let client = bootstrap_client(&private_key); // Test creating/deriving API key let result = client.create_or_derive_api_key(None).await; assert!( result.is_ok(), "Failed to create/derive API key: {:?}", result ); let api_creds = result.unwrap(); assert!(!api_creds.api_key.is_empty()); assert!(!api_creds.secret.is_empty()); assert!(!api_creds.passphrase.is_empty()); println!("PASS: Successfully created/derived API key"); } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_authenticated_order_flow() { let (private_key, _, _, _) = load_env_vars(); // Initialize client with L1 headers let bootstrap = bootstrap_client(&private_key); // Step 1: Create/derive API credentials println!("Step 1: Creating/deriving API credentials..."); let api_creds = bootstrap .create_or_derive_api_key(None) .await .expect("Failed to create/derive API key"); let client = authenticated_client(private_key, api_creds); println!("PASS: API credentials set"); // Step 2: Get a valid token_id from active markets println!("Step 2: Fetching active markets..."); let markets = client .get_sampling_markets(None) .await .expect("Failed to get markets"); let mut selected_token = None; let mut selected_midpoint = None; for market in markets.data.iter().filter(|m| m.active && !m.closed) { for token in &market.tokens { let midpoint = match client.get_midpoint(&token.token_id).await { Ok(midpoint) => midpoint.mid, Err(_) => continue, }; if midpoint > dec!(0.05) { selected_token = Some(token.token_id.clone()); selected_midpoint = Some(midpoint); break; } } if selected_token.is_some() { break; } } let token_id = selected_token.expect("No active token with midpoint safely above 0.01 found"); let midpoint = selected_midpoint.expect("midpoint should be selected with token"); println!("PASS: Found active token: {}", token_id); println!("PASS: Current midpoint: {}", midpoint); // Step 4: Create and post a tiny non-marketable BUY order. // A BUY at 0.01 validates pUSD balance/allowance without requiring outcome-token inventory. let side = Side::BUY; let order_price = dec!(0.01); let order_book = client .get_order_book(&token_id) .await .expect("Failed to get selected order book"); let order_size = order_book.min_order_size.max(dec!(5)); println!( "Step 4: Posting {:?} order at price {} and size {}...", side, order_price, order_size ); let order_args = OrderArgs { token_id, price: order_price, size: order_size, side, expiration: None, builder_code: None, metadata: None, }; let post_result = client.create_and_post_order(&order_args, None, None).await; // This is the critical test - did we get past the 401 error? match &post_result { Ok(response) => { println!("PASS: Order posted successfully!"); // Step 5: Cancel the order if !response.order_id.is_empty() { println!("Step 5: Canceling order {}...", response.order_id); let cancel_result = client.cancel(&response.order_id).await; assert!( cancel_result.is_ok(), "Failed to cancel order: {:?}", cancel_result ); println!("PASS: Order canceled successfully"); } else { println!( "WARNING: Order posted but no orderID in response: {:?}", response ); } }, Err(e) => { // The critical failure: did we get a 401 (authentication failure)? match &e { polyfill_rs::PolyfillError::Api { status: 401, .. } => { panic!( "FAIL: CRITICAL: 401 Unauthorized error - HMAC authentication is broken!" ); }, // Any 4xx other than 401 indicates auth succeeded and we reached server-side validation. polyfill_rs::PolyfillError::Api { status: 400..=499, .. } => { println!("PASS: Authentication successful (got expected validation error)"); println!(" Error: {:?}", e); }, _ => { panic!("FAIL: Unexpected error: {:?}", e); }, } }, } } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_get_orders() { let (private_key, _, _, _) = load_env_vars(); let bootstrap = bootstrap_client(&private_key); let api_creds = bootstrap .create_or_derive_api_key(None) .await .expect("Failed to create/derive API key"); let client = authenticated_client(private_key, api_creds); println!("Testing get_orders..."); let result = client.get_orders(None, None).await; match result { Ok(orders) => { println!("PASS: Successfully fetched orders"); println!(" Found {} orders", orders.len()); }, Err(e) => { let err_str = format!("{:?}", e); if err_str.contains("401") { panic!("FAIL: 401 Unauthorized - authentication failed!"); } panic!("Failed to get orders: {:?}", e); }, } } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_get_trades() { let (private_key, _, _, _) = load_env_vars(); let bootstrap = bootstrap_client(&private_key); let api_creds = bootstrap .create_or_derive_api_key(None) .await .expect("Failed to create/derive API key"); let client = authenticated_client(private_key, api_creds); println!("Testing get_trades..."); let result = client.get_trades(None, None).await; match result { Ok(_trades) => { println!("PASS: Successfully fetched trades"); }, Err(e) => { let err_str = format!("{:?}", e); if err_str.contains("401") { panic!("FAIL: 401 Unauthorized - authentication failed!"); } panic!("Failed to get trades: {:?}", e); }, } } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_get_balance_allowance() { let (private_key, _, _, _) = load_env_vars(); let bootstrap = bootstrap_client(&private_key); let api_creds = bootstrap .create_or_derive_api_key(None) .await .expect("Failed to create/derive API key"); let client = authenticated_client(private_key, api_creds); println!("Testing get_balance_allowance..."); use polyfill_rs::types::{AssetType, BalanceAllowanceParams}; let collateral_update_params = BalanceAllowanceParams { asset_type: Some(AssetType::COLLATERAL), token_id: None, signature_type: None, }; let collateral_update = client .update_balance_allowance(Some(collateral_update_params)) .await; match collateral_update { Ok(update) => { println!("PASS: Successfully requested collateral balance/allowance update"); println!(" Collateral update: {:?}", update); }, Err(e) => { let err_str = format!("{:?}", e); if err_str.contains("401") { panic!("FAIL: 401 Unauthorized - authentication failed!"); } println!("WARNING: Collateral balance update failed: {:?}", e); }, } let collateral_params = BalanceAllowanceParams { asset_type: Some(AssetType::COLLATERAL), token_id: None, signature_type: None, }; let collateral_result = client.get_balance_allowance(Some(collateral_params)).await; match collateral_result { Ok(balance) => { println!("PASS: Successfully fetched collateral balance/allowance"); println!(" Collateral: {:?}", balance); }, Err(e) => { let err_str = format!("{:?}", e); if err_str.contains("401") { panic!("FAIL: 401 Unauthorized - authentication failed!"); } println!("WARNING: Collateral balance check failed: {:?}", e); }, } // Get a valid token_id first let markets = client .get_sampling_markets(None) .await .expect("Failed to get markets"); let token_id = &markets.data[0].tokens[0].token_id; let params = BalanceAllowanceParams { asset_type: Some(AssetType::CONDITIONAL), token_id: Some(token_id.clone()), signature_type: None, }; let result = client.get_balance_allowance(Some(params)).await; match result { Ok(balance) => { println!("PASS: Successfully fetched balance/allowance"); println!(" Balance: {:?}", balance); }, Err(e) => { let err_str = format!("{:?}", e); if err_str.contains("401") { panic!("FAIL: 401 Unauthorized - authentication failed!"); } println!("WARNING: Balance check failed (may be expected): {:?}", e); }, } } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_get_api_keys() { let (private_key, _, _, _) = load_env_vars(); let bootstrap = bootstrap_client(&private_key); let api_creds = bootstrap .create_or_derive_api_key(None) .await .expect("Failed to create/derive API key"); let client = authenticated_client(private_key, api_creds); println!("Testing get_api_keys..."); let result = client.get_api_keys().await; match result { Ok(keys) => { println!("PASS: Successfully fetched API keys"); println!(" Found {} keys", keys.len()); }, Err(e) => { let err_str = format!("{:?}", e); if err_str.contains("401") { panic!("FAIL: 401 Unauthorized - authentication failed!"); } panic!("Failed to get API keys: {:?}", e); }, } } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_get_notifications() { let (private_key, _, _, _) = load_env_vars(); let bootstrap = bootstrap_client(&private_key); let api_creds = bootstrap .create_or_derive_api_key(None) .await .expect("Failed to create/derive API key"); let client = authenticated_client(private_key, api_creds); println!("Testing get_notifications..."); let result = client.get_notifications().await; match result { Ok(notifications) => { println!("PASS: Successfully fetched notifications"); println!(" Notifications: {:?}", notifications); }, Err(e) => { let err_str = format!("{:?}", e); if err_str.contains("401") { panic!("FAIL: 401 Unauthorized - authentication failed!"); } panic!("Failed to get notifications: {:?}", e); }, } } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_market_data_endpoints() { let (private_key, _, _, _) = load_env_vars(); let client = bootstrap_client(&private_key); println!("Testing market data endpoints (no auth required)..."); // Get a valid token_id let markets = client .get_sampling_markets(None) .await .expect("Failed to get markets"); let token_id = &markets.data[0].tokens[0].token_id; println!("PASS: Using token_id: {}", token_id); // Test multiple endpoints println!("Testing get_order_book..."); let book = client .get_order_book(token_id) .await .expect("Failed to get order book"); println!( "PASS: Order book: {} bids, {} asks", book.bids.len(), book.asks.len() ); println!("Testing get_midpoint..."); let midpoint = client .get_midpoint(token_id) .await .expect("Failed to get midpoint"); println!("PASS: Midpoint: {}", midpoint.mid); println!("Testing get_spread..."); let spread = client .get_spread(token_id) .await .expect("Failed to get spread"); println!("PASS: Spread: {}", spread.spread); println!("Testing get_price..."); let price = client .get_price(token_id, Side::BUY) .await .expect("Failed to get price"); println!("PASS: Buy price: {}", price.price); println!("Testing get_tick_size..."); let tick_size = client .get_tick_size(token_id) .await .expect("Failed to get tick size"); println!("PASS: Tick size: {}", tick_size); println!("Testing get_markets..."); let all_markets = client .get_markets(None) .await .expect("Failed to get all markets"); println!("PASS: Found {} markets", all_markets.data.len()); println!("\nPASS: All market data endpoints working!"); } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_batch_endpoints() { let (private_key, _, _, _) = load_env_vars(); let client = bootstrap_client(&private_key); println!("Testing batch endpoints..."); // Get multiple valid token_ids let markets = client .get_sampling_markets(None) .await .expect("Failed to get markets"); let token_ids: Vec = markets.data[0..2.min(markets.data.len())] .iter() .map(|m| m.tokens[0].token_id.clone()) .collect(); println!("Testing get_order_books (batch)..."); let books = client .get_order_books(&token_ids) .await .expect("Failed to get order books"); println!("PASS: Fetched {} order books", books.len()); println!("Testing get_midpoints (batch)..."); let midpoints = client .get_midpoints(&token_ids) .await .expect("Failed to get midpoints"); println!("PASS: Fetched {} midpoints", midpoints.len()); println!("Testing get_spreads (batch)..."); let spreads = client .get_spreads(&token_ids) .await .expect("Failed to get spreads"); println!("PASS: Fetched {} spreads", spreads.len()); println!("\nPASS: All batch endpoints working!"); } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_real_api_health_check() { let client = ClobClient::new(HOST); println!("Testing health check endpoints..."); let ok = client.get_ok().await; assert!(ok, "API health check failed!"); println!("PASS: API is healthy"); let server_time = client .get_server_time() .await .expect("Failed to get server time"); println!("PASS: Server time: {}", server_time); }