mirror of
https://github.com/floor-licker/polyfill-rs.git
synced 2026-07-28 04:57:46 +00:00
544 lines
17 KiB
Rust
544 lines
17 KiB
Rust
// 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<String>, Option<String>, Option<String>) {
|
|
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<u8> {
|
|
env::var("POLYMARKET_SIGNATURE_TYPE")
|
|
.ok()
|
|
.and_then(|value| value.parse::<u8>().ok())
|
|
}
|
|
|
|
fn env_funder() -> Option<String> {
|
|
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<String> = 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);
|
|
}
|