polyfill2 0.3.0

Polymarket CLOB V2 Rust client (fork of polyfill-rs). High-performance, EIP-712 signing, WebSocket streaming.
Documentation
//! Quick Demo for polyfill-rs
//!
//! This example demonstrates all available API endpoints in a simple, easy-to-run format.
//! It can be run without authentication credentials and will test all public endpoints.

use polyfill2::{ClobClient, PolyfillError, Result, Side};
use rust_decimal::Decimal;
use tokio::time::{sleep, Duration};
use tracing::{error, info, warn};

/// Quick demo that tests all available endpoints
#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging
    tracing_subscriber::fmt::init();

    info!("Polyfill-rs Quick Demo");
    info!("======================");

    // Create client
    let client = ClobClient::new("https://clob.polymarket.com");

    // Test 1: Basic connectivity
    info!("\nTesting API Connectivity...");
    match test_connectivity(&client).await {
        Ok(_) => info!("API connectivity test passed"),
        Err(e) => {
            error!("API connectivity test failed: {}", e);
            return Err(e);
        },
    }

    // Test 2: Get a valid token ID from markets
    info!("\nGetting Market Data...");
    let token_id = match get_valid_token_id(&client).await {
        Ok(id) => {
            info!("Found valid token ID: {}", id);
            id
        },
        Err(e) => {
            error!("Failed to get valid token ID: {}", e);
            return Err(e);
        },
    };

    // Test 3: Test all market data endpoints
    info!("\nTesting Market Data Endpoints...");
    test_market_data_endpoints(&client, &token_id).await?;

    // Test 4: Test error handling
    info!("\nTesting Error Handling...");
    test_error_handling(&client).await?;

    // Test 5: Performance test
    info!("\nTesting Performance...");
    test_performance(&client, &token_id).await?;

    info!("\nAll tests completed successfully!");
    info!("The polyfill-rs client is working correctly with the Polymarket API.");

    Ok(())
}

/// Test basic API connectivity
async fn test_connectivity(client: &ClobClient) -> Result<()> {
    // Test /ok endpoint
    let is_ok = client.get_ok().await;
    if !is_ok {
        return Err(PolyfillError::network(
            "API not responding",
            std::io::Error::other("API not responding"),
        ));
    }
    info!("  /ok endpoint responding");

    // Test /time endpoint
    let server_time = client.get_server_time().await?;
    info!("  Server time: {}", server_time);

    // Verify server time is reasonable (within last 24 hours)
    let current_time = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs();

    let time_diff = server_time.abs_diff(current_time);

    if time_diff > 86400 {
        // 24 hours
        warn!("  Server time seems off (diff: {} seconds)", time_diff);
    } else {
        info!("  Server time is reasonable");
    }

    Ok(())
}

/// Get a valid token ID from the markets endpoint
async fn get_valid_token_id(client: &ClobClient) -> Result<String> {
    let markets = client.get_sampling_markets(None).await?;

    if markets.data.is_empty() {
        return Err(PolyfillError::api(404, "No markets found"));
    }

    // Find a market with active tokens
    for market in &markets.data {
        if market.active && !market.closed {
            for token in &market.tokens {
                if !token.token_id.is_empty() {
                    info!("  Found active market: {}", market.question);
                    info!("  Market slug: {}", market.market_slug);
                    info!("  Token ID: {}", token.token_id);
                    info!("  Outcome: {}", token.outcome);
                    return Ok(token.token_id.clone());
                }
            }
        }
    }

    Err(PolyfillError::api(
        404,
        "No active markets with valid tokens found",
    ))
}

/// Test all market data endpoints
async fn test_market_data_endpoints(client: &ClobClient, token_id: &str) -> Result<()> {
    // Test order book
    info!("  Testing order book endpoint...");
    let order_book = client.get_order_book(token_id).await?;
    info!(
        "    Order book: {} bids, {} asks",
        order_book.bids.len(),
        order_book.asks.len()
    );

    // Test midpoint
    info!("  Testing midpoint endpoint...");
    let midpoint = client.get_midpoint(token_id).await?;
    info!("    Midpoint: {}", midpoint.mid);

    // Test spread
    info!("  Testing spread endpoint...");
    let spread = client.get_spread(token_id).await?;
    info!("    Spread: {}", spread.spread);

    // Test buy price
    info!("  Testing buy price endpoint...");
    let buy_price = client.get_price(token_id, Side::BUY).await?;
    info!("    Buy price: {}", buy_price.price);

    // Test sell price
    info!("  Testing sell price endpoint...");
    let sell_price = client.get_price(token_id, Side::SELL).await?;
    info!("    Sell price: {}", sell_price.price);

    // Test tick size
    info!("  Testing tick size endpoint...");
    let tick_size = client.get_tick_size(token_id).await?;
    info!("    Tick size: {}", tick_size);

    // Test neg risk
    info!("  Testing neg risk endpoint...");
    let neg_risk = client.get_neg_risk(token_id).await?;
    info!("    Neg risk: {}", neg_risk);

    // Validate data consistency
    info!("  Validating data consistency...");
    validate_market_data(&order_book, &midpoint, &spread, &buy_price, &sell_price)?;

    Ok(())
}

/// Validate that market data is consistent
fn validate_market_data(
    order_book: &polyfill2::client::OrderBookSummary,
    midpoint: &polyfill2::client::MidpointResponse,
    spread: &polyfill2::client::SpreadResponse,
    buy_price: &polyfill2::client::PriceResponse,
    sell_price: &polyfill2::client::PriceResponse,
) -> Result<()> {
    // Check that we have some liquidity
    if order_book.bids.is_empty() && order_book.asks.is_empty() {
        warn!("    Order book is empty");
    } else {
        info!("    Order book has liquidity");
    }

    // Check that prices are positive
    if buy_price.price <= Decimal::ZERO {
        warn!("    Buy price is not positive: {}", buy_price.price);
    } else {
        info!("    Buy price is positive");
    }

    if sell_price.price <= Decimal::ZERO {
        warn!("    Sell price is not positive: {}", sell_price.price);
    } else {
        info!("    Sell price is positive");
    }

    // Check that spread is reasonable
    if spread.spread < Decimal::ZERO {
        warn!("    Spread is negative: {}", spread.spread);
    } else {
        info!("    Spread is non-negative");
    }

    // Check that midpoint is between buy and sell prices (if both exist)
    if buy_price.price > Decimal::ZERO && sell_price.price > Decimal::ZERO {
        if midpoint.mid < buy_price.price || midpoint.mid > sell_price.price {
            warn!(
                "    Midpoint {} is not between buy {} and sell {}",
                midpoint.mid, buy_price.price, sell_price.price
            );
        } else {
            info!("    Midpoint is between buy and sell prices");
        }
    }

    Ok(())
}

/// Test error handling with invalid requests
async fn test_error_handling(client: &ClobClient) -> Result<()> {
    // Test with invalid token ID
    info!("  Testing invalid token ID...");
    let result = client.get_order_book("invalid_token_12345").await;
    match result {
        Ok(_) => {
            warn!("    Invalid token ID returned data instead of error");
        },
        Err(e) => match e {
            PolyfillError::Api { status, .. } => {
                if status >= 400 {
                    info!("    Invalid token ID correctly returned error: {}", status);
                } else {
                    warn!("    Unexpected status code for invalid token: {}", status);
                }
            },
            _ => {
                info!("    Invalid token ID returned error: {:?}", e);
            },
        },
    }

    // Test with empty token ID
    info!("  Testing empty token ID...");
    let result = client.get_order_book("").await;
    match result {
        Ok(_) => {
            warn!("    Empty token ID returned data instead of error");
        },
        Err(e) => {
            info!("    Empty token ID correctly returned error: {:?}", e);
        },
    }

    Ok(())
}

/// Test performance characteristics
async fn test_performance(client: &ClobClient, token_id: &str) -> Result<()> {
    let mut total_time = Duration::from_secs(0);
    let mut success_count = 0;
    let test_count = 5;

    info!("  Running {} performance tests...", test_count);

    for i in 1..=test_count {
        let start = std::time::Instant::now();

        // Test a mix of endpoints
        let results = tokio::join!(
            client.get_server_time(),
            client.get_midpoint(token_id),
            client.get_spread(token_id),
        );

        let duration = start.elapsed();
        total_time += duration;

        match results {
            (Ok(_), Ok(_), Ok(_)) => {
                success_count += 1;
                info!(
                    "    Test {}: PASSED {:.2}ms",
                    i,
                    duration.as_secs_f64() * 1000.0
                );
            },
            _ => {
                warn!(
                    "    Test {}: FAILED in {:.2}ms",
                    i,
                    duration.as_secs_f64() * 1000.0
                );
            },
        }

        // Small delay between tests
        sleep(Duration::from_millis(100)).await;
    }

    let avg_time = total_time / test_count as u32;
    let success_rate = (success_count as f64 / test_count as f64) * 100.0;

    info!("  Performance Summary:");
    info!("    Success rate: {:.1}%", success_rate);
    info!(
        "    Average response time: {:.2}ms",
        avg_time.as_secs_f64() * 1000.0
    );
    info!("    Total time: {:.2}s", total_time.as_secs_f64());

    // Performance thresholds
    if avg_time > Duration::from_secs(2) {
        warn!(
            "    Average response time is slow: {:.2}ms",
            avg_time.as_secs_f64() * 1000.0
        );
    } else {
        info!("    Response times are acceptable");
    }

    if success_rate < 80.0 {
        warn!("    Success rate is low: {:.1}%", success_rate);
    } else {
        info!("    Success rate is good");
    }

    Ok(())
}