Files
polyfill-rs/tarpaulin-report.html
T

737 lines
637 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>:root {
--color: black;
--bg: white;
--head-bg: white;
--link: #338;
--blue: #ccf;
--red: #fcc;
--yellow: #ffc;
--green: #cfc;
}
[data-theme='dark'] {
--color: white;
--bg: black;
--head-bg: #333;
--link: #aaf;
--blue: #225;
--red: #522;
--yellow: #552;
--green: #252;
}
html,
body {
margin: 0;
padding: 0;
color: var(--color);
background: var(--bg);
}
.app {
margin: 10px;
padding: 0;
}
.files-list {
margin: 10px 0 0;
width: 100%;
border-collapse: collapse;
}
.files-list__head {
border: 1px solid #999;
}
.files-list__head > tr > th {
padding: 10px;
border: 1px solid #999;
text-align: left;
font-weight: normal;
background: var(--head-bg);
}
.files-list__body {
}
.files-list__file {
cursor: pointer;
}
.files-list__file:hover {
background: var(--blue);
}
.files-list__file > td {
padding: 10px;
border: 1px solid #999;
}
.files-list__file > td:first-child::before {
content: '\01F4C4';
margin-right: 1em;
}
.files-list__file_low {
background: var(--red);
}
.files-list__file_medium {
background: var(--yellow);
}
.files-list__file_high {
background: var(--green);
}
.files-list__file_folder > td:first-child::before {
content: '\01F4C1';
margin-right: 1em;
}
.file-header {
border: 1px solid #999;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
background: var(--bg);
}
.file-header__back {
margin: 10px;
cursor: pointer;
flex-shrink: 0;
flex-grow: 0;
text-decoration: underline;
color: var(--link);
}
.file-header__name {
margin: 10px;
flex-shrink: 2;
flex-grow: 2;
}
.file-header__stat {
margin: 10px;
flex-shrink: 0;
flex-grow: 0;
}
.file-content {
margin: 10px 0 0;
border: 1px solid #999;
padding: 10px;
counter-reset: line;
display: flex;
flex-direction: column;
}
.code-line::before {
content: counter(line);
margin-right: 10px;
}
.code-line {
margin: 0;
padding: 0.3em;
height: 1em;
counter-increment: line;
}
.code-line_covered {
background: var(--green);
}
.code-line_uncovered {
background: var(--red);
}
#theme-toggle-label {
margin-left: 1ch;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
var data = {"files":[{"path":["/","Users","juliustranquilli","personal","polyfill-rs","benches","book_updates.rs"],"content":"//! Benchmark for order book updates\n//!\n//! This benchmark measures the performance of order book operations\n//! including delta application, price updates, and book maintenance.\n\nuse criterion::{black_box, criterion_group, criterion_main, Criterion};\nuse polyfill_rs::{\n book::OrderBook,\n types::{OrderDelta, Side},\n};\nuse rust_decimal::{Decimal, Decimal as RustDecimal};\nuse rust_decimal_macros::dec;\nuse std::time::Instant;\n\nfn bench_book_creation(c: \u0026mut Criterion) {\n c.bench_function(\"book_creation\", |b| {\n b.iter(|| {\n let _book = OrderBook::new(\n black_box(\"test_token\".to_string()),\n black_box(100),\n );\n });\n });\n}\n\nfn bench_delta_application(c: \u0026mut Criterion) {\n let mut book = OrderBook::new(\"test_token\".to_string(), 100);\n \n // Pre-populate with some levels\n for i in 1..=10 {\n let price = Decimal::from(50 + i) / Decimal::from(100);\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: Side::BUY,\n price,\n size: dec!(100),\n sequence: i,\n };\n book.apply_delta(delta).unwrap();\n }\n\n c.bench_function(\"delta_application\", |b| {\n b.iter(|| {\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: black_box(Side::SELL),\n price: black_box(dec!(0.52)),\n size: black_box(dec!(50)),\n sequence: black_box(11),\n };\n book.apply_delta(delta).unwrap();\n });\n });\n}\n\nfn bench_best_price_lookup(c: \u0026mut Criterion) {\n let mut book = OrderBook::new(\"test_token\".to_string(), 100);\n \n // Pre-populate with levels\n for i in 1..=20 {\n let price = Decimal::from(50 + i) / Decimal::from(100);\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n price,\n size: dec!(100),\n sequence: i,\n };\n book.apply_delta(delta).unwrap();\n }\n\n c.bench_function(\"best_price_lookup\", |b| {\n b.iter(|| {\n let _bid = book.best_bid();\n let _ask = book.best_ask();\n let _spread = book.spread();\n let _mid = book.mid_price();\n });\n });\n}\n\nfn bench_book_snapshot(c: \u0026mut Criterion) {\n let mut book = OrderBook::new(\"test_token\".to_string(), 100);\n \n // Pre-populate with levels\n for i in 1..=50 {\n let price = Decimal::from(50 + i) / Decimal::from(100);\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n price,\n size: dec!(100),\n sequence: i,\n };\n book.apply_delta(delta).unwrap();\n }\n\n c.bench_function(\"book_snapshot\", |b| {\n b.iter(|| {\n let _snapshot = book.snapshot();\n });\n });\n}\n\nfn bench_market_impact_calculation(c: \u0026mut Criterion) {\n let mut book = OrderBook::new(\"test_token\".to_string(), 100);\n \n // Pre-populate with levels\n for i in 1..=30 {\n let price = Decimal::from(50 + i) / Decimal::from(100);\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n price,\n size: dec!(100),\n sequence: i,\n };\n book.apply_delta(delta).unwrap();\n }\n\n c.bench_function(\"market_impact_calculation\", |b| {\n b.iter(|| {\n let _impact = book.calculate_market_impact(Side::BUY, dec!(50));\n });\n });\n}\n\nfn bench_high_frequency_updates(c: \u0026mut Criterion) {\n c.bench_function(\"high_frequency_updates\", |b| {\n b.iter(|| {\n let mut book = OrderBook::new(\"test_token\".to_string(), 100);\n let start_time = Instant::now();\n \n // Simulate high-frequency updates\n for i in 1..=1000 {\n let price = Decimal::from(500 + (i % 100)) / Decimal::from(1000);\n let size = Decimal::from(10 + (i % 90));\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n price,\n size,\n sequence: i,\n };\n book.apply_delta(delta).unwrap();\n \n // Check prices every 10 updates\n if i % 10 == 0 {\n let _bid = book.best_bid();\n let _ask = book.best_ask();\n }\n }\n \n let duration = start_time.elapsed();\n black_box(duration);\n });\n });\n}\n\nfn bench_concurrent_access(c: \u0026mut Criterion) {\n use std::sync::Arc;\n use tokio::sync::RwLock;\n \n c.bench_function(\"concurrent_access\", |b| {\n b.iter(|| {\n let book = Arc::new(RwLock::new(OrderBook::new(\"test_token\".to_string(), 100)));\n let book_clone = book.clone();\n \n // Simulate concurrent reads and writes\n let rt = tokio::runtime::Runtime::new().unwrap();\n rt.block_on(async {\n let mut tasks = Vec::new();\n \n // Spawn writer tasks\n for i in 1..=10 {\n let book = book.clone();\n tasks.push(tokio::spawn(async move {\n let price = Decimal::from(50 + i) / Decimal::from(100);\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n price,\n size: dec!(100),\n sequence: i,\n };\n let mut book = book.write().await;\n book.apply_delta(delta).unwrap();\n }));\n }\n \n // Spawn reader tasks\n for _ in 0..20 {\n let book = book_clone.clone();\n tasks.push(tokio::spawn(async move {\n let book = book.read().await;\n let _bid = book.best_bid();\n let _ask = book.best_ask();\n }));\n }\n \n // Wait for all tasks\n for task in tasks {\n let _ = task.await;\n }\n });\n });\n });\n}\n\ncriterion_group!(\n benches,\n bench_book_creation,\n bench_delta_application,\n bench_best_price_lookup,\n bench_book_snapshot,\n bench_market_impact_calculation,\n bench_high_frequency_updates,\n bench_concurrent_access,\n);\ncriterion_main!(benches); ","traces":[],"covered":0,"coverable":0},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","benches","fill_processing.rs"],"content":"//! Benchmark for fill processing\n//!\n//! This benchmark measures the performance of trade execution and\n//! fill processing operations.\n\nuse criterion::{black_box, criterion_group, criterion_main, Criterion};\nuse polyfill_rs::{\n book::OrderBook,\n fill::{FillEngine, FillProcessor},\n types::{FillEvent, MarketOrderRequest, OrderDelta, Side},\n};\nuse rust_decimal::{Decimal, Decimal as RustDecimal};\nuse rust_decimal_macros::dec;\nuse std::time::Instant;\n\nfn bench_fill_engine_creation(c: \u0026mut Criterion) {\n c.bench_function(\"fill_engine_creation\", |b| {\n b.iter(|| {\n let _engine = FillEngine::new(\n black_box(dec!(1)),\n black_box(dec!(5)),\n black_box(10),\n );\n });\n });\n}\n\nfn bench_market_order_execution(c: \u0026mut Criterion) {\n let mut engine = FillEngine::new(dec!(1), dec!(5), 10);\n let mut book = OrderBook::new(\"test_token\".to_string(), 100);\n \n // Pre-populate book with levels\n for i in 1..=20 {\n let price = Decimal::from(50 + i) / Decimal::from(100);\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n price,\n size: dec!(100),\n sequence: i,\n };\n book.apply_delta(delta).unwrap();\n }\n\n c.bench_function(\"market_order_execution\", |b| {\n b.iter(|| {\n let request = MarketOrderRequest {\n token_id: \"test_token\".to_string(),\n side: black_box(Side::BUY),\n amount: black_box(dec!(50)),\n slippage_tolerance: Some(dec!(1.0)),\n client_id: Some(\"bench_order\".to_string()),\n };\n \n let _result = engine.execute_market_order(\u0026request, \u0026book);\n });\n });\n}\n\nfn bench_fill_processor(c: \u0026mut Criterion) {\n let mut processor = FillProcessor::new(1000);\n \n c.bench_function(\"fill_processor\", |b| {\n b.iter(|| {\n let fill = FillEvent {\n id: \"fill_1\".to_string(),\n order_id: \"order_1\".to_string(),\n token_id: \"test_token\".to_string(),\n side: black_box(Side::BUY),\n price: black_box(dec!(0.5)),\n size: black_box(dec!(100)),\n timestamp: chrono::Utc::now(),\n maker_address: alloy_primitives::Address::ZERO,\n taker_address: alloy_primitives::Address::ZERO,\n fee: black_box(dec!(0.1)),\n };\n \n processor.process_fill(fill).unwrap();\n });\n });\n}\n\nfn bench_market_impact_calculation(c: \u0026mut Criterion) {\n let mut book = OrderBook::new(\"test_token\".to_string(), 100);\n \n // Pre-populate with realistic order book\n for i in 1..=30 {\n let price = Decimal::from(50 + i) / Decimal::from(100);\n let size = Decimal::from(100 + i * 10);\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n price,\n size,\n sequence: i,\n };\n book.apply_delta(delta).unwrap();\n }\n\n c.bench_function(\"market_impact_calculation\", |b| {\n b.iter(|| {\n let _impact = book.calculate_market_impact(Side::BUY, dec!(50));\n let _impact = book.calculate_market_impact(Side::SELL, dec!(50));\n });\n });\n}\n\nfn bench_high_frequency_fills(c: \u0026mut Criterion) {\n c.bench_function(\"high_frequency_fills\", |b| {\n b.iter(|| {\n let mut engine = FillEngine::new(dec!(1), dec!(2), 5);\n let mut book = OrderBook::new(\"test_token\".to_string(), 100);\n let start_time = Instant::now();\n \n // Simulate high-frequency fill processing\n for i in 1..=100 {\n // Add some market depth\n let price = Decimal::from(500 + (i % 10)) / Decimal::from(1000);\n let size = Decimal::from(10 + (i % 90));\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n price,\n size,\n sequence: i,\n };\n book.apply_delta(delta).unwrap();\n \n // Execute market orders\n if i % 5 == 0 {\n let request = MarketOrderRequest {\n token_id: \"test_token\".to_string(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n amount: dec!(10),\n slippage_tolerance: Some(dec!(1.0)),\n client_id: Some(format!(\"order_{}\", i)),\n };\n \n let _result = engine.execute_market_order(\u0026request, \u0026book);\n }\n }\n \n let duration = start_time.elapsed();\n black_box(duration);\n });\n });\n}\n\nfn bench_fill_statistics(c: \u0026mut Criterion) {\n let mut engine = FillEngine::new(dec!(1), dec!(5), 10);\n \n // Add some fills\n for i in 1..=100 {\n let request = MarketOrderRequest {\n token_id: \"test_token\".to_string(),\n side: if i % 2 == 0 { Side::BUY } else { Side::SELL },\n amount: dec!(10),\n slippage_tolerance: Some(dec!(1.0)),\n client_id: Some(format!(\"order_{}\", i)),\n };\n \n let mut book = OrderBook::new(\"test_token\".to_string(), 100);\n book.apply_delta(OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: chrono::Utc::now(),\n side: request.side.opposite(),\n price: dec!(0.5),\n size: dec!(100),\n sequence: i,\n }).unwrap();\n \n let _result = engine.execute_market_order(\u0026request, \u0026book);\n }\n\n c.bench_function(\"fill_statistics\", |b| {\n b.iter(|| {\n let _stats = engine.get_stats();\n });\n });\n}\n\ncriterion_group!(\n benches,\n bench_fill_engine_creation,\n bench_market_order_execution,\n bench_fill_processor,\n bench_market_impact_calculation,\n bench_high_frequency_fills,\n bench_fill_statistics,\n);\ncriterion_main!(benches); ","traces":[],"covered":0,"coverable":0},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","examples","comprehensive_demo.rs"],"content":"//! Comprehensive Demo for polyfill-rs\n//!\n//! This example demonstrates all the major functions and capabilities of the polyfill-rs library:\n//! - Basic client operations and API calls\n//! - Order book management and analytics\n//! - Real-time streaming capabilities\n//! - Trade execution and fill processing\n//! - Utility functions and mathematical operations\n//! - Error handling and retry logic\n//! - Rate limiting and performance optimizations\n\nuse polyfill_rs::{\n // Core client types\n ClobClient, PolyfillClient, OrderArgs, Side, OrderType,\n \n // Order book management\n book::{OrderBook, OrderBookManager},\n \n // Streaming capabilities\n stream::{WebSocketStream, StreamManager},\n \n // Fill execution\n fill::{FillEngine, FillProcessor},\n \n // Types and structures\n types::*,\n \n // Error handling\n errors::{PolyfillError, Result},\n \n // Utility functions\n utils::{crypto, math, retry, time, url, rate_limit, address},\n \n // Configuration\n ClientConfig,\n};\n\nuse rust_decimal::Decimal;\nuse rust_decimal_macros::dec;\nuse std::time::Duration;\nuse tokio::time::sleep;\nuse tracing::{error, info, debug};\n\n/// Comprehensive demo showcasing all polyfill-rs functionality\npub struct PolyfillDemo {\n /// Basic HTTP client\n client: ClobClient,\n /// Advanced client with configuration\n advanced_client: PolyfillClient,\n /// Order book manager\n book_manager: OrderBookManager,\n /// Fill engine for trade execution\n fill_engine: FillEngine,\n /// Fill processor for handling fills\n fill_processor: FillProcessor,\n /// Stream manager for real-time data\n stream_manager: StreamManager,\n /// Rate limiter\n rate_limiter: rate_limit::TokenBucket,\n /// Statistics\n stats: DemoStats,\n}\n\n/// Demo statistics\n#[derive(Debug, Clone)]\npub struct DemoStats {\n pub api_calls: u64,\n pub orders_processed: u64,\n pub fills_processed: u64,\n pub stream_messages: u64,\n pub errors: u64,\n pub total_volume: Decimal,\n}\n\nimpl Default for DemoStats {\n fn default() -\u003e Self {\n Self {\n api_calls: 0,\n orders_processed: 0,\n fills_processed: 0,\n stream_messages: 0,\n errors: 0,\n total_volume: dec!(0),\n }\n }\n}\n\nimpl PolyfillDemo {\n /// Create a new comprehensive demo\n pub fn new() -\u003e Result\u003cSelf\u003e {\n // Create basic client\n let client = ClobClient::new(\"https://clob.polymarket.com\");\n \n // Create advanced client with configuration\n let _config = ClientConfig {\n base_url: \"https://clob.polymarket.com\".to_string(),\n chain_id: 137, // Polygon\n private_key: None, // Would be set in production\n api_credentials: None, // Would be set in production\n max_slippage: Some(dec!(0.01)), // 1% max slippage\n fee_rate: Some(dec!(0.02)), // 2% fee rate\n timeout: Some(Duration::from_secs(30)),\n max_connections: Some(100),\n };\n \n let advanced_client = PolyfillClient::new(\"https://clob.polymarket.com\");\n \n // Create order book manager\n let book_manager = OrderBookManager::new(100);\n \n // Create fill engine\n let fill_engine = FillEngine::new(\n dec!(1.0), // Min fill size\n dec!(2.0), // Max slippage 2%\n 5, // 5 bps fee rate\n );\n \n // Create fill processor\n let fill_processor = FillProcessor::new(1000);\n \n // Create stream manager\n let stream_manager = StreamManager::new();\n \n // Create rate limiter (100 requests per second)\n let rate_limiter = rate_limit::TokenBucket::new(100, 100);\n \n Ok(Self {\n client,\n advanced_client,\n book_manager,\n fill_engine,\n fill_processor,\n stream_manager,\n rate_limiter,\n stats: DemoStats::default(),\n })\n }\n\n /// Demo 1: Basic API Operations\n pub async fn demo_basic_api_operations(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"=== Demo 1: Basic API Operations ===\");\n \n // Test connectivity\n let is_ok = self.client.get_ok().await;\n info!(\"API connectivity: {}\", is_ok);\n self.stats.api_calls += 1;\n \n // Get server time\n match self.client.get_server_time().await {\n Ok(timestamp) =\u003e {\n info!(\"Server time: {}\", timestamp);\n self.stats.api_calls += 1;\n }\n Err(e) =\u003e {\n error!(\"Failed to get server time: {}\", e);\n self.stats.errors += 1;\n }\n }\n \n // Get sampling markets\n match self.client.get_sampling_markets(None).await {\n Ok(markets) =\u003e {\n info!(\"Found {} markets\", markets.data.len());\n for market in \u0026markets.data[..std::cmp::min(3, markets.data.len())] {\n info!(\" Market: {} - {}\", market.question, market.market_slug);\n }\n self.stats.api_calls += 1;\n }\n Err(e) =\u003e {\n error!(\"Failed to get markets: {}\", e);\n self.stats.errors += 1;\n }\n }\n \n Ok(())\n }\n\n /// Demo 2: Order Book Operations\n pub async fn demo_order_book_operations(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"=== Demo 2: Order Book Operations ===\");\n \n // Example token ID (you would use a real one in production)\n let token_id = \"12345\";\n \n // Get order book from API\n match self.client.get_order_book(token_id).await {\n Ok(order_book) =\u003e {\n info!(\"Order book for token {}: {} bids, {} asks\", \n token_id, order_book.bids.len(), order_book.asks.len());\n \n // Create local order book\n let mut local_book = OrderBook::new(token_id.to_string(), 50);\n \n // Apply order book data to local book\n for (i, bid) in order_book.bids.iter().enumerate() {\n local_book.apply_delta(OrderDelta {\n token_id: token_id.to_string(),\n timestamp: chrono::Utc::now(),\n side: Side::BUY,\n price: bid.price,\n size: bid.size,\n sequence: i as u64,\n })?;\n }\n \n for (i, ask) in order_book.asks.iter().enumerate() {\n local_book.apply_delta(OrderDelta {\n token_id: token_id.to_string(),\n timestamp: chrono::Utc::now(),\n side: Side::SELL,\n price: ask.price,\n size: ask.size,\n sequence: (order_book.bids.len() + i) as u64,\n })?;\n }\n \n // Get analytics\n let analytics = local_book.analytics();\n info!(\"Book analytics:\");\n info!(\" Bid levels: {}, Ask levels: {}\", analytics.bid_count, analytics.ask_count);\n info!(\" Total bid size: {}, Total ask size: {}\", analytics.total_bid_size, analytics.total_ask_size);\n if let Some(spread) = analytics.spread {\n info!(\" Spread: {} ({:.2}%)\", spread, analytics.spread_pct.unwrap_or(dec!(0)));\n }\n if let Some(mid) = analytics.mid_price {\n info!(\" Mid price: {}\", mid);\n }\n \n // Calculate market impact\n if let Some(impact) = local_book.calculate_market_impact(Side::BUY, dec!(100.0)) {\n info!(\"Market impact for 100 size buy:\");\n info!(\" Average price: {}\", impact.average_price);\n info!(\" Impact: {:.2}%\", impact.impact_pct);\n info!(\" Total cost: {}\", impact.total_cost);\n }\n \n self.stats.api_calls += 1;\n }\n Err(e) =\u003e {\n error!(\"Failed to get order book: {}\", e);\n self.stats.errors += 1;\n }\n }\n \n Ok(())\n }\n\n /// Demo 3: Market Data Operations\n pub async fn demo_market_data_operations(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"=== Demo 3: Market Data Operations ===\");\n \n let token_id = \"12345\";\n \n // Get midpoint\n match self.client.get_midpoint(token_id).await {\n Ok(midpoint) =\u003e {\n info!(\"Midpoint for {}: {}\", token_id, midpoint.mid);\n self.stats.api_calls += 1;\n }\n Err(e) =\u003e {\n error!(\"Failed to get midpoint: {}\", e);\n self.stats.errors += 1;\n }\n }\n \n // Get spread\n match self.client.get_spread(token_id).await {\n Ok(spread) =\u003e {\n info!(\"Spread for {}: {}\", token_id, spread.spread);\n self.stats.api_calls += 1;\n }\n Err(e) =\u003e {\n error!(\"Failed to get spread: {}\", e);\n self.stats.errors += 1;\n }\n }\n \n // Get price for both sides\n for side in [Side::BUY, Side::SELL] {\n match self.client.get_price(token_id, side).await {\n Ok(price) =\u003e {\n info!(\"{} price for {}: {}\", side.as_str(), token_id, price.price);\n self.stats.api_calls += 1;\n }\n Err(e) =\u003e {\n error!(\"Failed to get {} price: {}\", side.as_str(), e);\n self.stats.errors += 1;\n }\n }\n }\n \n // Get tick size\n match self.client.get_tick_size(token_id).await {\n Ok(tick_size) =\u003e {\n info!(\"Tick size for {}: {}\", token_id, tick_size);\n self.stats.api_calls += 1;\n }\n Err(e) =\u003e {\n error!(\"Failed to get tick size: {}\", e);\n self.stats.errors += 1;\n }\n }\n \n // Get neg risk\n match self.client.get_neg_risk(token_id).await {\n Ok(neg_risk) =\u003e {\n info!(\"Neg risk for {}: {}\", token_id, neg_risk);\n self.stats.api_calls += 1;\n }\n Err(e) =\u003e {\n error!(\"Failed to get neg risk: {}\", e);\n self.stats.errors += 1;\n }\n }\n \n Ok(())\n }\n\n /// Demo 4: Order Creation and Management\n pub async fn demo_order_operations(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"=== Demo 4: Order Creation and Management ===\");\n \n // Create order arguments\n let order_args = OrderArgs::new(\n \"12345\",\n dec!(0.75),\n dec!(100.0),\n Side::BUY,\n );\n \n info!(\"Created order args: {:?}\", order_args);\n \n // Create market order request\n let market_order = MarketOrderRequest {\n token_id: \"12345\".to_string(),\n side: Side::BUY,\n amount: dec!(100.0),\n slippage_tolerance: Some(dec!(1.0)), // 1% slippage\n client_id: Some(\"demo_market_order\".to_string()),\n };\n \n info!(\"Created market order request: {:?}\", market_order);\n \n // Create limit order request\n let limit_order = OrderRequest {\n token_id: \"12345\".to_string(),\n side: Side::BUY,\n price: dec!(0.75),\n size: dec!(100.0),\n order_type: OrderType::GTC,\n expiration: None,\n client_id: Some(\"demo_limit_order\".to_string()),\n };\n \n info!(\"Created limit order request: {:?}\", limit_order);\n \n self.stats.orders_processed += 2;\n \n Ok(())\n }\n\n /// Demo 5: Fill Execution\n pub async fn demo_fill_execution(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"=== Demo 5: Fill Execution ===\");\n \n // Create a mock order book for testing\n let mut book = OrderBook::new(\"12345\".to_string(), 50);\n \n // Add some liquidity\n for i in 1..=5 {\n book.apply_delta(OrderDelta {\n token_id: \"12345\".to_string(),\n timestamp: chrono::Utc::now(),\n side: Side::BUY,\n price: dec!(0.70) + Decimal::from(i) * dec!(0.01),\n size: dec!(100.0),\n sequence: i,\n })?;\n }\n \n for i in 1..=5 {\n book.apply_delta(OrderDelta {\n token_id: \"12345\".to_string(),\n timestamp: chrono::Utc::now(),\n side: Side::SELL,\n price: dec!(0.80) + Decimal::from(i) * dec!(0.01),\n size: dec!(100.0),\n sequence: i + 10,\n })?;\n }\n \n info!(\"Created order book with liquidity\");\n \n // Execute market order\n let market_order = MarketOrderRequest {\n token_id: \"12345\".to_string(),\n side: Side::BUY,\n amount: dec!(50.0),\n slippage_tolerance: Some(dec!(2.0)),\n client_id: Some(\"demo_market_buy\".to_string()),\n };\n \n let fill_result = self.fill_engine.execute_market_order(\u0026market_order, \u0026book)?;\n \n info!(\"Market order execution result:\");\n info!(\" Status: {:?}\", fill_result.status);\n info!(\" Total size: {}\", fill_result.total_size);\n info!(\" Average price: {}\", fill_result.average_price);\n info!(\" Total cost: {}\", fill_result.total_cost);\n info!(\" Fees: {}\", fill_result.fees);\n info!(\" Number of fills: {}\", fill_result.fills.len());\n \n // Process fills\n for fill in \u0026fill_result.fills {\n self.fill_processor.process_fill(fill.clone())?;\n self.stats.fills_processed += 1;\n self.stats.total_volume += fill.size;\n }\n \n // Execute limit order\n let limit_order = OrderRequest {\n token_id: \"12345\".to_string(),\n side: Side::SELL,\n price: dec!(0.85),\n size: dec!(25.0),\n order_type: OrderType::GTC,\n expiration: None,\n client_id: Some(\"demo_limit_sell\".to_string()),\n };\n \n let limit_result = self.fill_engine.execute_limit_order(\u0026limit_order, \u0026book)?;\n \n info!(\"Limit order execution result:\");\n info!(\" Status: {:?}\", limit_result.status);\n info!(\" Total size: {}\", limit_result.total_size);\n info!(\" Average price: {}\", limit_result.average_price);\n \n self.stats.orders_processed += 2;\n \n Ok(())\n }\n\n /// Demo 6: Utility Functions\n pub async fn demo_utility_functions(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"=== Demo 6: Utility Functions ===\");\n \n // Time utilities\n info!(\"Time utilities:\");\n info!(\" Current timestamp (secs): {}\", time::now_secs());\n info!(\" Current timestamp (millis): {}\", time::now_millis());\n info!(\" Current timestamp (micros): {}\", time::now_micros());\n \n // Math utilities\n info!(\"Math utilities:\");\n let price = dec!(0.7534);\n let tick_size = dec!(0.01);\n let rounded_price = math::round_to_tick(price, tick_size);\n info!(\" Price: {}, Tick size: {}, Rounded: {}\", price, tick_size, rounded_price);\n \n let notional = math::notional(price, dec!(100.0));\n info!(\" Notional value: {}\", notional);\n \n let spread_pct = math::spread_pct(dec!(0.75), dec!(0.76));\n info!(\" Spread percentage: {:?}\", spread_pct);\n \n let mid_price = math::mid_price(dec!(0.75), dec!(0.76));\n info!(\" Mid price: {:?}\", mid_price);\n \n // Address utilities\n info!(\"Address utilities:\");\n let address = \"0x1234567890123456789012345678901234567890\";\n match address::parse_address(address) {\n Ok(addr) =\u003e info!(\" Parsed address: {:?}\", addr),\n Err(e) =\u003e error!(\" Failed to parse address: {}\", e),\n }\n \n let token_id = \"12345\";\n match address::validate_token_id(token_id) {\n Ok(_) =\u003e info!(\" Valid token ID: {}\", token_id),\n Err(e) =\u003e error!(\" Invalid token ID: {}\", e),\n }\n \n // URL utilities\n info!(\"URL utilities:\");\n let endpoint = url::build_endpoint(\"https://api.example.com\", \"/v1/orders\")?;\n info!(\" Built endpoint: {}\", endpoint);\n \n // Rate limiting\n info!(\"Rate limiting:\");\n for i in 0..5 {\n let allowed = self.rate_limiter.try_consume();\n info!(\" Request {}: {}\", i + 1, if allowed { \"ALLOWED\" } else { \"RATE LIMITED\" });\n }\n \n Ok(())\n }\n\n /// Demo 7: Error Handling and Retry Logic\n pub async fn demo_error_handling(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"=== Demo 7: Error Handling and Retry Logic ===\");\n \n // Demonstrate retry logic\n let retry_config = retry::RetryConfig {\n max_attempts: 3,\n initial_delay: Duration::from_millis(100),\n max_delay: Duration::from_secs(1),\n backoff_factor: 2.0,\n jitter: true,\n };\n \n let operation = || async {\n // Simulate a potentially failing operation\n if rand::random::\u003cbool\u003e() {\n Ok(\"Success!\")\n } else {\n Err(PolyfillError::network(\"Simulated network error\", std::io::Error::new(std::io::ErrorKind::Other, \"Simulated error\")))\n }\n };\n \n match retry::with_retry(\u0026retry_config, operation).await {\n Ok(result) =\u003e {\n info!(\"Retry operation succeeded: {}\", result);\n }\n Err(e) =\u003e {\n error!(\"Retry operation failed after all attempts: {}\", e);\n self.stats.errors += 1;\n }\n }\n \n // Demonstrate error types\n info!(\"Error types demonstration:\");\n \n let api_error = PolyfillError::api(400, \"Bad Request\");\n info!(\" API Error: {:?}\", api_error);\n \n let network_error = PolyfillError::network(\"Connection timeout\", std::io::Error::new(std::io::ErrorKind::TimedOut, \"Connection timeout\"));\n info!(\" Network Error: {:?}\", network_error);\n \n let parse_error = PolyfillError::parse(\"Invalid JSON\", None);\n info!(\" Parse Error: {:?}\", parse_error);\n \n let config_error = PolyfillError::config(\"Invalid configuration\");\n info!(\" Config Error: {:?}\", config_error);\n \n Ok(())\n }\n\n /// Demo 8: Streaming Capabilities (Mock)\n pub async fn demo_streaming_capabilities(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"=== Demo 8: Streaming Capabilities ===\");\n \n // Create a mock WebSocket stream\n let _stream = WebSocketStream::new(\"wss://stream.polymarket.com\");\n \n info!(\"Created WebSocket stream\");\n \n // Simulate subscription\n let subscription = WssSubscription {\n auth: WssAuth {\n address: \"0x1234567890123456789012345678901234567890\".to_string(),\n signature: \"mock_signature\".to_string(),\n timestamp: time::now_secs(),\n nonce: crypto::generate_nonce().to_string(),\n },\n markets: Some(vec![\"market1\".to_string(), \"market2\".to_string()]),\n asset_ids: Some(vec![\"12345\".to_string(), \"67890\".to_string()]),\n channel_type: \"USER\".to_string(),\n };\n \n info!(\"Created subscription: {:?}\", subscription);\n \n // Simulate receiving stream messages\n let messages = vec![\n StreamMessage::Heartbeat { timestamp: chrono::Utc::now() },\n StreamMessage::BookUpdate {\n data: OrderDelta {\n token_id: \"12345\".to_string(),\n timestamp: chrono::Utc::now(),\n side: Side::BUY,\n price: dec!(0.75),\n size: dec!(100.0),\n sequence: 1,\n }\n },\n StreamMessage::Trade {\n data: FillEvent {\n id: \"fill1\".to_string(),\n order_id: \"order1\".to_string(),\n token_id: \"12345\".to_string(),\n side: Side::BUY,\n price: dec!(0.75),\n size: dec!(50.0),\n timestamp: chrono::Utc::now(),\n maker_address: alloy_primitives::Address::ZERO,\n taker_address: alloy_primitives::Address::ZERO,\n fee: dec!(0.375),\n }\n },\n ];\n \n for message in messages {\n info!(\"Received stream message: {:?}\", message);\n self.stats.stream_messages += 1;\n \n // Process message based on type\n match \u0026message {\n StreamMessage::BookUpdate { data } =\u003e {\n info!(\" Processing book update for token: {}\", data.token_id);\n if let Err(e) = self.book_manager.apply_delta(data.clone()) {\n error!(\" Failed to apply book update: {}\", e);\n self.stats.errors += 1;\n }\n }\n StreamMessage::Trade { data } =\u003e {\n info!(\" Processing trade: {} {} @ {}\", \n data.side.as_str(), data.size, data.price);\n if let Err(e) = self.fill_processor.process_fill(data.clone()) {\n error!(\" Failed to process fill: {}\", e);\n self.stats.errors += 1;\n }\n }\n StreamMessage::Heartbeat { timestamp } =\u003e {\n debug!(\" Received heartbeat at: {}\", timestamp);\n }\n _ =\u003e {\n info!(\" Unhandled message type\");\n }\n }\n }\n \n Ok(())\n }\n\n /// Demo 9: Performance and Analytics\n pub async fn demo_performance_analytics(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"=== Demo 9: Performance and Analytics ===\");\n \n // Get fill engine statistics\n let fill_stats = self.fill_engine.get_stats();\n info!(\"Fill engine statistics:\");\n info!(\" Total orders: {}\", fill_stats.total_orders);\n info!(\" Total fills: {}\", fill_stats.total_fills);\n info!(\" Total volume: {}\", fill_stats.total_volume);\n info!(\" Total fees: {}\", fill_stats.total_fees);\n \n // Get fill processor statistics\n let processor_stats = self.fill_processor.get_stats();\n info!(\"Fill processor statistics:\");\n info!(\" Pending orders: {}\", processor_stats.pending_orders);\n info!(\" Pending fills: {}\", processor_stats.pending_fills);\n info!(\" Pending volume: {}\", processor_stats.pending_volume);\n info!(\" Processed fills: {}\", processor_stats.processed_fills);\n info!(\" Processed volume: {}\", processor_stats.processed_volume);\n \n // Get demo statistics\n info!(\"Demo statistics:\");\n info!(\" API calls: {}\", self.stats.api_calls);\n info!(\" Orders processed: {}\", self.stats.orders_processed);\n info!(\" Fills processed: {}\", self.stats.fills_processed);\n info!(\" Stream messages: {}\", self.stats.stream_messages);\n info!(\" Errors: {}\", self.stats.errors);\n info!(\" Total volume: {}\", self.stats.total_volume);\n \n // Calculate error rate\n let total_operations = self.stats.api_calls + self.stats.orders_processed + self.stats.stream_messages;\n let error_rate = if total_operations \u003e 0 {\n (self.stats.errors as f64 / total_operations as f64) * 100.0\n } else {\n 0.0\n };\n info!(\" Error rate: {:.2}%\", error_rate);\n \n Ok(())\n }\n\n /// Run all demos\n pub async fn run_all_demos(\u0026mut self) -\u003e Result\u003c()\u003e {\n info!(\"Starting comprehensive polyfill-rs demo...\");\n \n // Run all demo sections\n self.demo_basic_api_operations().await?;\n sleep(Duration::from_millis(500)).await;\n \n self.demo_order_book_operations().await?;\n sleep(Duration::from_millis(500)).await;\n \n self.demo_market_data_operations().await?;\n sleep(Duration::from_millis(500)).await;\n \n self.demo_order_operations().await?;\n sleep(Duration::from_millis(500)).await;\n \n self.demo_fill_execution().await?;\n sleep(Duration::from_millis(500)).await;\n \n self.demo_utility_functions().await?;\n sleep(Duration::from_millis(500)).await;\n \n self.demo_error_handling().await?;\n sleep(Duration::from_millis(500)).await;\n \n self.demo_streaming_capabilities().await?;\n sleep(Duration::from_millis(500)).await;\n \n self.demo_performance_analytics().await?;\n \n info!(\"Comprehensive demo completed successfully!\");\n Ok(())\n }\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n // Initialize logging\n tracing_subscriber::fmt::init();\n \n info!(\"Polyfill-rs Comprehensive Demo\");\n info!(\"==============================\");\n \n // Create and run demo\n let mut demo = PolyfillDemo::new()?;\n \n if let Err(e) = demo.run_all_demos().await {\n error!(\"Demo failed: {}\", e);\n std::process::exit(1);\n }\n \n info!(\"Demo completed successfully!\");\n Ok(())\n} ","traces":[],"covered":0,"coverable":0},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","examples","quick_demo.rs"],"content":"//! Quick Demo for polyfill-rs\n//!\n//! This example demonstrates all available API endpoints in a simple, easy-to-run format.\n//! It can be run without authentication credentials and will test all public endpoints.\n\nuse polyfill_rs::{ClobClient, Side, Result, PolyfillError};\nuse rust_decimal::Decimal;\nuse std::str::FromStr;\nuse tokio::time::{sleep, Duration};\nuse tracing::{info, error, warn};\n\n/// Quick demo that tests all available endpoints\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n // Initialize logging\n tracing_subscriber::fmt::init();\n \n info!(\"Polyfill-rs Quick Demo\");\n info!(\"======================\");\n \n // Create client\n let client = ClobClient::new(\"https://clob.polymarket.com\");\n \n // Test 1: Basic connectivity\n info!(\"\\nTesting API Connectivity...\");\n match test_connectivity(\u0026client).await {\n Ok(_) =\u003e info!(\"API connectivity test passed\"),\n Err(e) =\u003e {\n error!(\"API connectivity test failed: {}\", e);\n return Err(e);\n }\n }\n \n // Test 2: Get a valid token ID from markets\n info!(\"\\nGetting Market Data...\");\n let token_id = match get_valid_token_id(\u0026client).await {\n Ok(id) =\u003e {\n info!(\"Found valid token ID: {}\", id);\n id\n }\n Err(e) =\u003e {\n error!(\"Failed to get valid token ID: {}\", e);\n return Err(e);\n }\n };\n \n // Test 3: Test all market data endpoints\n info!(\"\\nTesting Market Data Endpoints...\");\n test_market_data_endpoints(\u0026client, \u0026token_id).await?;\n \n // Test 4: Test error handling\n info!(\"\\nTesting Error Handling...\");\n test_error_handling(\u0026client).await?;\n \n // Test 5: Performance test\n info!(\"\\nTesting Performance...\");\n test_performance(\u0026client, \u0026token_id).await?;\n \n info!(\"\\nAll tests completed successfully!\");\n info!(\"The polyfill-rs client is working correctly with the Polymarket API.\");\n \n Ok(())\n}\n\n/// Test basic API connectivity\nasync fn test_connectivity(client: \u0026ClobClient) -\u003e Result\u003c()\u003e {\n // Test /ok endpoint\n let is_ok = client.get_ok().await;\n if !is_ok {\n return Err(PolyfillError::network(\"API not responding\", std::io::Error::new(std::io::ErrorKind::Other, \"API not responding\")));\n }\n info!(\" /ok endpoint responding\");\n \n // Test /time endpoint\n let server_time = client.get_server_time().await?;\n info!(\" Server time: {}\", server_time);\n \n // Verify server time is reasonable (within last 24 hours)\n let current_time = std::time::SystemTime::now()\n .duration_since(std::time::UNIX_EPOCH)\n .unwrap()\n .as_secs();\n \n let time_diff = if server_time \u003e current_time {\n server_time - current_time\n } else {\n current_time - server_time\n };\n \n if time_diff \u003e 86400 { // 24 hours\n warn!(\" Server time seems off (diff: {} seconds)\", time_diff);\n } else {\n info!(\" Server time is reasonable\");\n }\n \n Ok(())\n}\n\n/// Get a valid token ID from the markets endpoint\nasync fn get_valid_token_id(client: \u0026ClobClient) -\u003e Result\u003cString\u003e {\n let markets = client.get_sampling_markets(None).await?;\n \n if markets.data.is_empty() {\n return Err(PolyfillError::api(404, \"No markets found\"));\n }\n \n // Find a market with active tokens\n for market in \u0026markets.data {\n if market.active \u0026\u0026 !market.closed {\n for token in \u0026market.tokens {\n if !token.token_id.is_empty() {\n info!(\" Found active market: {}\", market.question);\n info!(\" Market slug: {}\", market.market_slug);\n info!(\" Token ID: {}\", token.token_id);\n info!(\" Outcome: {}\", token.outcome);\n return Ok(token.token_id.clone());\n }\n }\n }\n }\n \n Err(PolyfillError::api(404, \"No active markets with valid tokens found\"))\n}\n\n/// Test all market data endpoints\nasync fn test_market_data_endpoints(client: \u0026ClobClient, token_id: \u0026str) -\u003e Result\u003c()\u003e {\n // Test order book\n info!(\" Testing order book endpoint...\");\n let order_book = client.get_order_book(token_id).await?;\n info!(\" Order book: {} bids, {} asks\", order_book.bids.len(), order_book.asks.len());\n \n // Test midpoint\n info!(\" Testing midpoint endpoint...\");\n let midpoint = client.get_midpoint(token_id).await?;\n info!(\" Midpoint: {}\", midpoint.mid);\n \n // Test spread\n info!(\" Testing spread endpoint...\");\n let spread = client.get_spread(token_id).await?;\n info!(\" Spread: {}\", spread.spread);\n \n // Test buy price\n info!(\" Testing buy price endpoint...\");\n let buy_price = client.get_price(token_id, Side::BUY).await?;\n info!(\" Buy price: {}\", buy_price.price);\n \n // Test sell price\n info!(\" Testing sell price endpoint...\");\n let sell_price = client.get_price(token_id, Side::SELL).await?;\n info!(\" Sell price: {}\", sell_price.price);\n \n // Test tick size\n info!(\" Testing tick size endpoint...\");\n let tick_size = client.get_tick_size(token_id).await?;\n info!(\" Tick size: {}\", tick_size);\n \n // Test neg risk\n info!(\" Testing neg risk endpoint...\");\n let neg_risk = client.get_neg_risk(token_id).await?;\n info!(\" Neg risk: {}\", neg_risk);\n \n // Validate data consistency\n info!(\" Validating data consistency...\");\n validate_market_data(\u0026order_book, \u0026midpoint, \u0026spread, \u0026buy_price, \u0026sell_price)?;\n \n Ok(())\n}\n\n/// Validate that market data is consistent\nfn validate_market_data(\n order_book: \u0026polyfill_rs::client::OrderBookSummary,\n midpoint: \u0026polyfill_rs::client::MidpointResponse,\n spread: \u0026polyfill_rs::client::SpreadResponse,\n buy_price: \u0026polyfill_rs::client::PriceResponse,\n sell_price: \u0026polyfill_rs::client::PriceResponse,\n) -\u003e Result\u003c()\u003e {\n // Check that we have some liquidity\n if order_book.bids.is_empty() \u0026\u0026 order_book.asks.is_empty() {\n warn!(\" Order book is empty\");\n } else {\n info!(\" Order book has liquidity\");\n }\n \n // Check that prices are positive\n if buy_price.price \u003c= Decimal::ZERO {\n warn!(\" Buy price is not positive: {}\", buy_price.price);\n } else {\n info!(\" Buy price is positive\");\n }\n \n if sell_price.price \u003c= Decimal::ZERO {\n warn!(\" Sell price is not positive: {}\", sell_price.price);\n } else {\n info!(\" Sell price is positive\");\n }\n \n // Check that spread is reasonable\n if spread.spread \u003c Decimal::ZERO {\n warn!(\" Spread is negative: {}\", spread.spread);\n } else {\n info!(\" Spread is non-negative\");\n }\n \n // Check that midpoint is between buy and sell prices (if both exist)\n if buy_price.price \u003e Decimal::ZERO \u0026\u0026 sell_price.price \u003e Decimal::ZERO {\n if midpoint.mid \u003c buy_price.price || midpoint.mid \u003e sell_price.price {\n warn!(\" Midpoint {} is not between buy {} and sell {}\", \n midpoint.mid, buy_price.price, sell_price.price);\n } else {\n info!(\" Midpoint is between buy and sell prices\");\n }\n }\n \n Ok(())\n}\n\n/// Test error handling with invalid requests\nasync fn test_error_handling(client: \u0026ClobClient) -\u003e Result\u003c()\u003e {\n // Test with invalid token ID\n info!(\" Testing invalid token ID...\");\n let result = client.get_order_book(\"invalid_token_12345\").await;\n match result {\n Ok(_) =\u003e {\n warn!(\" Invalid token ID returned data instead of error\");\n }\n Err(e) =\u003e {\n match e {\n PolyfillError::Api { status, .. } =\u003e {\n if status \u003e= 400 {\n info!(\" Invalid token ID correctly returned error: {}\", status);\n } else {\n warn!(\" Unexpected status code for invalid token: {}\", status);\n }\n }\n _ =\u003e {\n info!(\" Invalid token ID returned error: {:?}\", e);\n }\n }\n }\n }\n \n // Test with empty token ID\n info!(\" Testing empty token ID...\");\n let result = client.get_order_book(\"\").await;\n match result {\n Ok(_) =\u003e {\n warn!(\" Empty token ID returned data instead of error\");\n }\n Err(e) =\u003e {\n info!(\" Empty token ID correctly returned error: {:?}\", e);\n }\n }\n \n Ok(())\n}\n\n/// Test performance characteristics\nasync fn test_performance(client: \u0026ClobClient, token_id: \u0026str) -\u003e Result\u003c()\u003e {\n let mut total_time = Duration::from_secs(0);\n let mut success_count = 0;\n let test_count = 5;\n \n info!(\" Running {} performance tests...\", test_count);\n \n for i in 1..=test_count {\n let start = std::time::Instant::now();\n \n // Test a mix of endpoints\n let results = tokio::join!(\n client.get_server_time(),\n client.get_midpoint(token_id),\n client.get_spread(token_id),\n );\n \n let duration = start.elapsed();\n total_time += duration;\n \n match results {\n (Ok(_), Ok(_), Ok(_)) =\u003e {\n success_count += 1;\n info!(\" Test {}: PASSED {:.2}ms\", i, duration.as_secs_f64() * 1000.0);\n }\n _ =\u003e {\n warn!(\" Test {}: FAILED in {:.2}ms\", i, duration.as_secs_f64() * 1000.0);\n }\n }\n \n // Small delay between tests\n sleep(Duration::from_millis(100)).await;\n }\n \n let avg_time = total_time / test_count as u32;\n let success_rate = (success_count as f64 / test_count as f64) * 100.0;\n \n info!(\" Performance Summary:\");\n info!(\" Success rate: {:.1}%\", success_rate);\n info!(\" Average response time: {:.2}ms\", avg_time.as_secs_f64() * 1000.0);\n info!(\" Total time: {:.2}s\", total_time.as_secs_f64());\n \n // Performance thresholds\n if avg_time \u003e Duration::from_secs(2) {\n warn!(\" Average response time is slow: {:.2}ms\", avg_time.as_secs_f64() * 1000.0);\n } else {\n info!(\" Response times are acceptable\");\n }\n \n if success_rate \u003c 80.0 {\n warn!(\" Success rate is low: {:.1}%\", success_rate);\n } else {\n info!(\" Success rate is good\");\n }\n \n Ok(())\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","examples","snipe.rs"],"content":"//! Snipe example for polyfill-rs\n//!\n//! This example demonstrates high-frequency trading techniques including:\n//! - Real-time order book monitoring\n//! - Stale quote detection\n//! - Rapid order execution\n//! - Market impact analysis\n\nuse polyfill_rs::{\n book::OrderBookManager,\n errors::Result,\n fill::{FillEngine, FillStatus},\n types::*,\n utils::time,\n};\nuse rust_decimal::Decimal;\nuse rust_decimal_macros::dec;\nuse rust_decimal::prelude::ToPrimitive;\nuse std::time::Duration;\nuse tokio::time::sleep;\nuse tracing::{error, info, warn};\n\n/// Snipe trading strategy\n#[derive(Debug)]\npub struct SnipeStrategy {\n /// Target token ID\n token_id: String,\n /// Maximum spread to consider\n max_spread_pct: Decimal,\n /// Minimum order size\n min_order_size: Decimal,\n /// Maximum order size\n max_order_size: Decimal,\n /// Stale quote threshold (seconds)\n stale_threshold: u64,\n /// Last known best prices\n last_best_bid: Option\u003cDecimal\u003e,\n last_best_ask: Option\u003cDecimal\u003e,\n /// Last update timestamp\n last_update: u64,\n /// Order book manager\n book_manager: OrderBookManager,\n /// Fill engine\n fill_engine: FillEngine,\n /// Statistics\n stats: SnipeStats,\n}\n\n/// Snipe trading statistics\n#[derive(Debug, Clone)]\npub struct SnipeStats {\n pub opportunities_detected: u64,\n pub orders_placed: u64,\n pub orders_filled: u64,\n pub total_volume: Decimal,\n pub total_pnl: Decimal,\n pub avg_fill_time_ms: f64,\n}\n\nimpl Default for SnipeStats {\n fn default() -\u003e Self {\n Self {\n opportunities_detected: 0,\n orders_placed: 0,\n orders_filled: 0,\n total_volume: dec!(0),\n total_pnl: dec!(0),\n avg_fill_time_ms: 0.0,\n }\n }\n}\n\nimpl SnipeStrategy {\n /// Create a new snipe strategy\n pub fn new(\n token_id: String,\n max_spread_pct: Decimal,\n min_order_size: Decimal,\n max_order_size: Decimal,\n stale_threshold: u64,\n ) -\u003e Self {\n Self {\n token_id,\n max_spread_pct,\n min_order_size,\n max_order_size,\n stale_threshold,\n last_best_bid: None,\n last_best_ask: None,\n last_update: 0,\n book_manager: OrderBookManager::new(100),\n fill_engine: FillEngine::new(\n min_order_size,\n dec!(2.0), // 2% max slippage\n 5, // 5 bps fee rate\n ),\n stats: SnipeStats::default(),\n }\n }\n\n /// Process a market data update\n pub fn process_update(\u0026mut self, message: StreamMessage) -\u003e Result\u003c()\u003e {\n match message {\n StreamMessage::BookUpdate { data } =\u003e {\n if data.token_id == self.token_id {\n self.process_book_update(data)?;\n }\n }\n StreamMessage::Trade { data } =\u003e {\n if data.token_id == self.token_id {\n self.process_trade(data)?;\n }\n }\n StreamMessage::Heartbeat { timestamp: _ } =\u003e {\n self.check_stale_quotes()?;\n }\n _ =\u003e {}\n }\n Ok(())\n }\n\n /// Process order book update\n fn process_book_update(\u0026mut self, delta: OrderDelta) -\u003e Result\u003c()\u003e {\n // Ensure book exists\n self.book_manager.get_or_create_book(\u0026self.token_id)?;\n \n // Update local order book\n self.book_manager.apply_delta(delta.clone())?;\n\n // Get current book state\n let book = self.book_manager.get_book(\u0026self.token_id)?;\n \n // Update best prices\n if let Some(best_bid) = book.bids.first() {\n self.last_best_bid = Some(best_bid.price);\n }\n if let Some(best_ask) = book.asks.first() {\n self.last_best_ask = Some(best_ask.price);\n }\n \n self.last_update = time::now_secs();\n\n // Check for trading opportunities\n self.check_opportunities()?;\n\n Ok(())\n }\n\n /// Process trade update\n fn process_trade(\u0026mut self, fill: FillEvent) -\u003e Result\u003c()\u003e {\n info!(\n \"Trade: {} {} @ {} (size: {})\",\n fill.side.as_str(),\n fill.token_id,\n fill.price,\n fill.size\n );\n\n // Update statistics\n self.stats.total_volume += fill.size;\n \n // Calculate P\u0026L if this was our trade\n // (In a real implementation, you'd track your own orders)\n \n Ok(())\n }\n\n /// Check for trading opportunities\n fn check_opportunities(\u0026mut self) -\u003e Result\u003c()\u003e {\n let (bid, ask) = match (self.last_best_bid, self.last_best_ask) {\n (Some(bid), Some(ask)) =\u003e (bid, ask),\n _ =\u003e return Ok(()), // No liquidity\n };\n\n // Calculate spread\n let spread_pct = match (bid, ask) {\n (bid, ask) if bid \u003e dec!(0) \u0026\u0026 ask \u003e bid =\u003e {\n (ask - bid) / bid * dec!(100)\n }\n _ =\u003e return Ok(()),\n };\n\n // Check if spread is within our target\n if spread_pct \u003c= self.max_spread_pct {\n self.stats.opportunities_detected += 1;\n \n info!(\n \"Opportunity detected: spread {}% (target: {}%)\",\n spread_pct, self.max_spread_pct\n );\n\n // Execute snipe order\n self.execute_snipe_order(bid, ask)?;\n }\n\n Ok(())\n }\n\n /// Execute a snipe order\n fn execute_snipe_order(\u0026mut self, bid: Decimal, ask: Decimal) -\u003e Result\u003c()\u003e {\n // Calculate order size (random between min and max)\n let random_factor = Decimal::from(rand::random::\u003cu64\u003e() % 100) / Decimal::from(100);\n let size = self.min_order_size + \n (self.max_order_size - self.min_order_size) * random_factor;\n\n // Determine side based on market conditions\n let side = if bid \u003e ask {\n Side::SELL // Crossed market, sell\n } else {\n Side::BUY // Normal market, buy\n };\n\n // Create market order request\n let request = MarketOrderRequest {\n token_id: self.token_id.clone(),\n side,\n amount: size,\n slippage_tolerance: Some(dec!(1.0)), // 1% slippage tolerance\n client_id: Some(format!(\"snipe_{}\", time::now_millis())),\n };\n\n // Get current book for execution simulation\n let book = self.book_manager.get_book(\u0026self.token_id)?;\n let mut book_impl = polyfill_rs::book::OrderBook::new(self.token_id.clone(), 100);\n \n // Convert to internal book format\n for level in \u0026book.bids {\n book_impl.apply_delta(OrderDelta {\n token_id: self.token_id.clone(),\n timestamp: chrono::Utc::now(),\n side: Side::BUY,\n price: level.price,\n size: level.size,\n sequence: 1,\n })?;\n }\n \n for level in \u0026book.asks {\n book_impl.apply_delta(OrderDelta {\n token_id: self.token_id.clone(),\n timestamp: chrono::Utc::now(),\n side: Side::SELL,\n price: level.price,\n size: level.size,\n sequence: 2,\n })?;\n }\n\n // Execute order\n let start_time = std::time::Instant::now();\n let result = self.fill_engine.execute_market_order(\u0026request, \u0026book_impl)?;\n let fill_time = start_time.elapsed().as_millis() as f64;\n\n // Update statistics\n self.stats.orders_placed += 1;\n if result.status == FillStatus::Filled {\n self.stats.orders_filled += 1;\n }\n\n // Update average fill time\n let total_time = self.stats.avg_fill_time_ms * (self.stats.orders_filled - 1) as f64 + fill_time;\n self.stats.avg_fill_time_ms = total_time / self.stats.orders_filled as f64;\n\n info!(\n \"Snipe order executed: {} {} @ {} (fill time: {}ms)\",\n result.total_size,\n side.as_str(),\n result.average_price,\n fill_time\n );\n\n Ok(())\n }\n\n /// Check for stale quotes\n fn check_stale_quotes(\u0026mut self) -\u003e Result\u003c()\u003e {\n let now = time::now_secs();\n let age = now.saturating_sub(self.last_update);\n\n if age \u003e self.stale_threshold {\n warn!(\n \"Stale quotes detected: {}s old (threshold: {}s)\",\n age, self.stale_threshold\n );\n\n // In a real implementation, you might:\n // - Cancel pending orders\n // - Switch to a different data source\n // - Reduce position sizes\n // - Stop trading temporarily\n }\n\n Ok(())\n }\n\n /// Get current statistics\n pub fn get_stats(\u0026self) -\u003e \u0026SnipeStats {\n \u0026self.stats\n }\n}\n\n/// Mock market data generator for testing\nstruct MockMarketData {\n token_id: String,\n base_price: Decimal,\n volatility: Decimal,\n sequence: u64,\n}\n\nimpl MockMarketData {\n fn new(token_id: String, base_price: Decimal) -\u003e Self {\n Self {\n token_id,\n base_price,\n volatility: dec!(0.01), // 1% volatility\n sequence: 0,\n }\n }\n\n fn generate_update(\u0026mut self) -\u003e StreamMessage {\n self.sequence += 1;\n\n // Generate random price movement\n let random_factor = Decimal::from(rand::random::\u003ci64\u003e() % 100 - 50) / Decimal::from(100);\n let volatility_f64 = self.volatility.to_f64().unwrap_or(0.01);\n let price_change = random_factor * Decimal::from(2) * self.volatility;\n let new_price = self.base_price * (Decimal::from(1) + price_change);\n\n // Generate order book update\n let side = if rand::random::\u003cbool\u003e() { Side::BUY } else { Side::SELL };\n let size = Decimal::from(rand::random::\u003cu64\u003e() % 1000 + 100);\n\n StreamMessage::BookUpdate {\n data: OrderDelta {\n token_id: self.token_id.clone(),\n timestamp: chrono::Utc::now(),\n side,\n price: new_price,\n size,\n sequence: self.sequence,\n }\n }\n }\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n // Initialize logging\n tracing_subscriber::fmt::init();\n\n info!(\"Starting snipe trading example...\");\n\n // Create snipe strategy\n let mut strategy = SnipeStrategy::new(\n \"12345\".to_string(), // Example token ID\n dec!(2.0), // 2% max spread\n dec!(10), // Min order size\n dec!(100), // Max order size\n 5, // 5 second stale threshold\n );\n\n // Create mock market data generator\n let mut market_data = MockMarketData::new(\n \"12345\".to_string(),\n dec!(0.5), // Base price $0.50\n );\n\n // Simulate market data stream\n let mut message_count = 0;\n let max_messages = 100;\n\n while message_count \u003c max_messages {\n // Generate market update\n let update = market_data.generate_update();\n \n // Process update\n if let Err(e) = strategy.process_update(update) {\n error!(\"Error processing update: {}\", e);\n }\n\n // Print statistics every 10 messages\n if message_count % 10 == 0 {\n let stats = strategy.get_stats();\n info!(\n \"Stats: {} opportunities, {} orders placed, {} filled, avg fill time: {:.2}ms\",\n stats.opportunities_detected,\n stats.orders_placed,\n stats.orders_filled,\n stats.avg_fill_time_ms\n );\n }\n\n message_count += 1;\n sleep(Duration::from_millis(100)).await; // 100ms between updates\n }\n\n // Print final statistics\n let final_stats = strategy.get_stats();\n info!(\"Final statistics:\");\n info!(\" Opportunities detected: {}\", final_stats.opportunities_detected);\n info!(\" Orders placed: {}\", final_stats.orders_placed);\n info!(\" Orders filled: {}\", final_stats.orders_filled);\n info!(\" Total volume: {}\", final_stats.total_volume);\n info!(\" Average fill time: {:.2}ms\", final_stats.avg_fill_time_ms);\n\n info!(\"Snipe trading example completed!\");\n Ok(())\n} ","traces":[],"covered":0,"coverable":0},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","external","polymarket-rs-client","src","config.rs"],"content":"pub struct ContractConfig {\n pub exchange: String,\n pub collateral: String,\n pub conditional_tokens: String,\n}\n\npub fn get_contract_config(chain_id: u64, neg_risk: bool) -\u003e Option\u003cContractConfig\u003e {\n match neg_risk {\n true =\u003e {\n if chain_id == 137 {\n return Some(ContractConfig {\n exchange: \"0xC5d563A36AE78145C45a50134d48A1215220f80a\".to_owned(),\n collateral: \"0x2791bca1f2de4661ed88a30c99a7a9449aa84174\".to_owned(),\n conditional_tokens: \"0x4D97DCd97eC945f40cF65F87097ACe5EA0476045\".to_owned(),\n });\n } else if chain_id == 80002 {\n return Some(ContractConfig {\n exchange: \"0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296\".to_owned(),\n collateral: \"0x9c4e1703476e875070ee25b56a58b008cfb8fa78\".to_owned(),\n conditional_tokens: \"0x69308FB512518e39F9b16112fA8d994F4e2Bf8bB\".to_owned(),\n });\n }\n None\n }\n false =\u003e {\n if chain_id == 137 {\n return Some(ContractConfig {\n exchange: \"0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E\".to_owned(),\n collateral: \"0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174\".to_owned(),\n conditional_tokens: \"0x4D97DCd97eC945f40cF65F87097ACe5EA0476045\".to_owned(),\n });\n } else if chain_id == 80002 {\n return Some(ContractConfig {\n exchange: \"0xdFE02Eb6733538f8Ea35D585af8DE5958AD99E40\".to_owned(),\n collateral: \"0x9c4e1703476e875070ee25b56a58b008cfb8fa78\".to_owned(),\n conditional_tokens: \"0x69308FB512518e39F9b16112fA8d994F4e2Bf8bB\".to_owned(),\n });\n }\n None\n }\n }\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","external","polymarket-rs-client","src","data.rs"],"content":"use crate::Decimal;\n\nuse crate::SignedOrderRequest;\nuse alloy_primitives::U256;\nuse serde::{Deserialize, Deserializer, Serialize};\nuse serde_json::Value;\nuse std::fmt::Display;\nuse std::str::FromStr;\n\nconst ZERO_ADDRESS: \u0026str = \"0x0000000000000000000000000000000000000000\";\n\npub enum AssetType {\n COLLATERAL,\n CONDITIONAL,\n}\n\n#[allow(clippy::to_string_trait_impl)]\nimpl ToString for AssetType {\n fn to_string(\u0026self) -\u003e String {\n match self {\n AssetType::COLLATERAL =\u003e \"COLLATERAL\".to_string(),\n AssetType::CONDITIONAL =\u003e \"CONDITIONAL\".to_string(),\n }\n }\n}\n\n#[derive(Default)]\npub struct BalanceAllowanceParams {\n pub asset_type: Option\u003cAssetType\u003e,\n pub token_id: Option\u003cString\u003e,\n pub signature_type: Option\u003cu8\u003e,\n}\n\nimpl BalanceAllowanceParams {\n pub fn to_query_params(\u0026self) -\u003e Vec\u003c(\u0026str, String)\u003e {\n let mut params = Vec::with_capacity(3);\n\n if let Some(x) = \u0026self.asset_type {\n params.push((\"asset_type\", x.to_string()));\n }\n\n if let Some(x) = \u0026self.token_id {\n params.push((\"token_id\", x.to_string()));\n }\n\n if let Some(x) = \u0026self.signature_type {\n params.push((\"signature_type\", x.to_string()));\n }\n params\n }\n}\n\nimpl BalanceAllowanceParams {\n pub fn set_signature_type(\u0026mut self, s: u8) {\n self.signature_type = Some(s);\n }\n}\n\n#[derive(Debug)]\npub struct TradeParams {\n pub id: Option\u003cString\u003e,\n pub maker_address: Option\u003cString\u003e,\n pub market: Option\u003cString\u003e,\n pub asset_id: Option\u003cString\u003e,\n pub before: Option\u003cu64\u003e,\n pub after: Option\u003cu64\u003e,\n}\n\nimpl TradeParams {\n pub fn to_query_params(\u0026self) -\u003e Vec\u003c(\u0026str, String)\u003e {\n let mut params = Vec::with_capacity(4);\n\n if let Some(x) = \u0026self.id {\n params.push((\"id\", x.clone()));\n }\n\n if let Some(x) = \u0026self.asset_id {\n params.push((\"asset_id\", x.clone()));\n }\n\n if let Some(x) = \u0026self.market {\n params.push((\"market\", x.clone()));\n }\n if let Some(x) = \u0026self.before {\n params.push((\"before\", x.to_string()));\n }\n if let Some(x) = \u0026self.after {\n params.push((\"after\", x.to_string()));\n }\n params\n }\n}\n\n#[derive(Debug, Deserialize)]\npub struct OpenOrder {\n pub associate_trades: Vec\u003cString\u003e,\n pub id: String,\n pub status: String,\n pub market: String,\n\n #[serde(with = \"rust_decimal::serde::str\")]\n pub original_size: Decimal,\n pub outcome: String,\n pub maker_address: String,\n pub owner: String,\n\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n pub side: Side,\n\n #[serde(with = \"rust_decimal::serde::str\")]\n pub size_matched: Decimal,\n pub asset_id: String,\n #[serde(deserialize_with = \"deserialize_number_from_string\")]\n pub expiration: u64,\n #[serde(rename = \"type\")]\n pub order_type: OrderType,\n #[serde(deserialize_with = \"deserialize_number_from_string\")]\n pub created_at: u64,\n}\n\n#[derive(Debug)]\npub struct OpenOrderParams {\n pub id: Option\u003cString\u003e,\n pub asset_id: Option\u003cString\u003e,\n pub market: Option\u003cString\u003e,\n}\n\nimpl OpenOrderParams {\n pub fn to_query_params(\u0026self) -\u003e Vec\u003c(\u0026str, \u0026String)\u003e {\n let mut params = Vec::with_capacity(4);\n\n if let Some(x) = \u0026self.id {\n params.push((\"id\", x));\n }\n\n if let Some(x) = \u0026self.asset_id {\n params.push((\"asset_id\", x));\n }\n\n if let Some(x) = \u0026self.market {\n params.push((\"market\", x));\n }\n params\n }\n}\n\nfn deserialize_number_from_string\u003c'de, T, D\u003e(deserializer: D) -\u003e Result\u003cT, D::Error\u003e\nwhere\n D: Deserializer\u003c'de\u003e,\n T: FromStr + serde::Deserialize\u003c'de\u003e,\n \u003cT as FromStr\u003e::Err: Display,\n{\n #[derive(Deserialize)]\n #[serde(untagged)]\n enum StringOrInt\u003cT\u003e {\n String(String),\n Number(T),\n }\n\n match StringOrInt::\u003cT\u003e::deserialize(deserializer)? {\n StringOrInt::String(s) =\u003e s.parse::\u003cT\u003e().map_err(serde::de::Error::custom),\n StringOrInt::Number(i) =\u003e Ok(i),\n }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PostOrder {\n order: SignedOrderRequest,\n owner: String,\n order_type: OrderType,\n}\n\nimpl PostOrder {\n pub fn new(order: SignedOrderRequest, owner: String, order_type: OrderType) -\u003e Self {\n PostOrder {\n order,\n owner,\n order_type,\n }\n }\n}\n\n#[derive(Debug)]\npub struct OrderArgs {\n pub token_id: String,\n pub price: Decimal,\n pub size: Decimal,\n pub side: Side,\n}\n\n#[derive(Debug, Deserialize)]\npub struct OrderBookSummary {\n pub market: String,\n pub asset_id: String,\n pub hash: String,\n #[serde(deserialize_with = \"deserialize_number_from_string\")]\n pub timestamp: u64,\n pub bids: Vec\u003cOrderSummary\u003e,\n pub asks: Vec\u003cOrderSummary\u003e,\n}\n\n#[derive(Debug)]\npub struct MarketOrderArgs {\n pub token_id: String,\n pub amount: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct OrderSummary {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub size: Decimal,\n}\n\nimpl OrderArgs {\n pub fn new(token_id: \u0026str, price: Decimal, size: Decimal, side: Side) -\u003e Self {\n OrderArgs {\n token_id: token_id.to_owned(),\n price,\n size,\n side,\n }\n }\n}\n\n#[derive(Debug)]\npub struct ExtraOrderArgs {\n pub fee_rate_bps: u32,\n pub nonce: U256,\n pub taker: String,\n}\n\nimpl Default for ExtraOrderArgs {\n fn default() -\u003e Self {\n ExtraOrderArgs {\n fee_rate_bps: 0,\n nonce: U256::ZERO,\n taker: ZERO_ADDRESS.into(),\n }\n }\n}\n\n#[derive(Debug, Default)]\npub struct CreateOrderOptions {\n pub tick_size: Option\u003cDecimal\u003e,\n pub neg_risk: Option\u003cbool\u003e,\n}\n\n#[derive(Debug, Deserialize)]\npub struct ApiKeysResponse {\n #[serde(rename = \"apiKeys\")]\n pub api_keys: Vec\u003cString\u003e,\n}\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct MidpointResponse {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub mid: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct PriceResponse {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct SpreadResponse {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub spread: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct TickSizeResponse {\n pub minimum_tick_size: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct NegRiskResponse {\n pub neg_risk: bool,\n}\n\n#[derive(Debug, Serialize, Deserialize, Clone, Copy, Hash, Eq, PartialEq)]\npub enum OrderType {\n GTC,\n FOK,\n GTD,\n}\n\nimpl OrderType {\n pub fn as_str(\u0026self) -\u003e \u0026'static str {\n match self {\n OrderType::GTC =\u003e \"GTC\",\n OrderType::FOK =\u003e \"FOK\",\n OrderType::GTD =\u003e \"GTD\",\n }\n }\n}\n\n#[derive(Debug, Serialize, Deserialize, Clone, Copy, Hash, Eq, PartialEq)]\npub enum Side {\n BUY = 0,\n SELL = 1,\n}\n\nimpl Side {\n pub fn as_str(\u0026self) -\u003e \u0026'static str {\n match self {\n Side::BUY =\u003e \"BUY\",\n Side::SELL =\u003e \"SELL\",\n }\n }\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct BookParams {\n pub token_id: String,\n pub side: Side,\n}\n\n#[derive(Default, Debug, Serialize, Deserialize)]\npub struct ApiCreds {\n #[serde(rename = \"apiKey\")]\n pub api_key: String,\n pub secret: String,\n pub passphrase: String,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct MarketsResponse {\n pub limit: Decimal,\n pub count: Decimal,\n pub next_cursor: Option\u003cString\u003e,\n pub data: Vec\u003cMarket\u003e,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct SimplifiedMarketsResponse {\n pub limit: Decimal,\n pub count: Decimal,\n pub next_cursor: Option\u003cString\u003e,\n pub data: Vec\u003cSimplifiedMarket\u003e,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct Market {\n pub condition_id: String,\n pub tokens: [Token; 2],\n pub rewards: Rewards,\n pub min_incentive_size: Option\u003cString\u003e,\n pub max_incentive_spread: Option\u003cString\u003e,\n pub active: bool,\n pub closed: bool,\n\n pub question_id: String,\n pub minimum_order_size: Decimal,\n pub minimum_tick_size: Decimal,\n pub description: String,\n pub category: Option\u003cString\u003e,\n pub end_date_iso: Option\u003cString\u003e,\n pub game_start_time: Option\u003cString\u003e,\n pub question: String,\n pub market_slug: String,\n pub seconds_delay: Decimal,\n pub icon: String,\n pub fpmm: String,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct SimplifiedMarket {\n pub condition_id: String,\n pub tokens: [Token; 2],\n pub rewards: Rewards,\n pub min_incentive_size: Option\u003cString\u003e,\n pub max_incentive_spread: Option\u003cString\u003e,\n pub active: bool,\n pub closed: bool,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct Token {\n pub token_id: String,\n pub outcome: String,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct Rewards {\n pub rates: Option\u003cValue\u003e,\n pub min_size: Decimal,\n pub max_spread: Decimal,\n pub event_start_date: Option\u003cString\u003e,\n pub event_end_date: Option\u003cString\u003e,\n pub in_game_multiplier: Option\u003cDecimal\u003e,\n pub reward_epoch: Option\u003cDecimal\u003e,\n}\n","traces":[{"line":161,"address":[],"length":0,"stats":{"Line":0}},{"line":162,"address":[],"length":0,"stats":{"Line":0}},{"line":163,"address":[],"length":0,"stats":{"Line":0}}],"covered":0,"coverable":3},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","external","polymarket-rs-client","src","eth_utils.rs"],"content":"use crate::ClientResult;\nuse alloy_primitives::U256;\nuse alloy_primitives::{hex::encode_prefixed, Address};\nuse alloy_signer::{Signer, SignerSync};\nuse alloy_sol_types::{eip712_domain, sol};\nuse anyhow::Context;\n\npub trait EthSigner: Signer + SignerSync + Send + Sync {}\n\nimpl\u003cT: Signer + SignerSync + Send + Sync\u003e EthSigner for T {}\n\nsol! {\n struct ClobAuth {\n address address;\n string timestamp;\n uint256 nonce;\n string message;\n }\n}\n\nsol! {\n struct Order {\n uint256 salt;\n address maker;\n address signer;\n address taker;\n uint256 tokenId;\n uint256 makerAmount;\n uint256 takerAmount;\n uint256 expiration;\n uint256 nonce;\n uint256 feeRateBps;\n uint8 side;\n uint8 signatureType;\n }\n}\n\npub fn sign_clob_auth_message(\n signer: \u0026impl EthSigner,\n timestamp: String,\n nonce: U256,\n) -\u003e ClientResult\u003cString\u003e {\n // TODO: move these constants out\n let message = \"This message attests that I control the given wallet\".to_owned();\n let polygon = 137;\n\n let my_struct = ClobAuth {\n address: signer.address(),\n timestamp,\n nonce,\n message,\n };\n\n let my_domain = eip712_domain!(\n name: \"ClobAuthDomain\",\n version: \"1\",\n chain_id: polygon,\n );\n\n let val = signer\n .sign_typed_data_sync(\u0026my_struct, \u0026my_domain)\n .context(\"Error creating EIP-712 signature\")?;\n\n Ok(encode_prefixed(val.as_bytes()))\n}\n\npub fn sign_order_message(\n signer: \u0026(impl EthSigner + Sized),\n order: Order,\n chain_id: u64,\n verifying_contract: Address,\n) -\u003e ClientResult\u003cString\u003e {\n let domain = eip712_domain!(\n name: \"Polymarket CTF Exchange\",\n version: \"1\",\n chain_id: chain_id,\n verifying_contract: verifying_contract,\n\n );\n\n let val = signer\n .sign_typed_data_sync(\u0026order, \u0026domain)\n .context(\"Error creating EIP-712 signature for order\")?;\n\n Ok(encode_prefixed(val.as_bytes()))\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","external","polymarket-rs-client","src","headers.rs"],"content":"use crate::eth_utils::{sign_clob_auth_message, EthSigner};\nuse crate::utils::{build_hmac_signature, get_current_unix_time_secs};\nuse crate::ApiCreds;\nuse alloy_primitives::hex::encode_prefixed;\nuse alloy_primitives::U256;\nuse anyhow::Result;\nuse serde::Serialize;\nuse std::collections::HashMap;\n\nconst POLY_ADDR_HEADER: \u0026str = \"poly_address\";\nconst POLY_SIG_HEADER: \u0026str = \"poly_signature\";\nconst POLY_TS_HEADER: \u0026str = \"poly_timestamp\";\nconst POLY_NONCE_HEADER: \u0026str = \"poly_nonce\";\nconst POLY_API_KEY_HEADER: \u0026str = \"poly_api_key\";\nconst POLY_PASS_HEADER: \u0026str = \"poly_passphrase\";\n\n//TODO: Heapless for maps!\ntype Headers = HashMap\u003c\u0026'static str, String\u003e;\n\npub fn create_l1_headers(signer: \u0026impl EthSigner, nonce: Option\u003cU256\u003e) -\u003e Result\u003cHeaders\u003e {\n let timestamp = get_current_unix_time_secs().to_string();\n let nonce = nonce.unwrap_or(U256::ZERO);\n let signature = sign_clob_auth_message(signer, timestamp.clone(), nonce)?;\n let address = encode_prefixed(signer.address().as_slice());\n\n Ok(HashMap::from([\n (POLY_ADDR_HEADER, address),\n (POLY_SIG_HEADER, signature),\n (POLY_TS_HEADER, timestamp),\n (POLY_NONCE_HEADER, nonce.to_string()),\n ]))\n}\n\npub fn create_l2_headers\u003cT\u003e(\n signer: \u0026impl EthSigner,\n api_creds: \u0026ApiCreds,\n method: \u0026str,\n req_path: \u0026str,\n body: Option\u003c\u0026T\u003e,\n) -\u003e Result\u003cHeaders\u003e\nwhere\n T: ?Sized + Serialize,\n{\n let address = encode_prefixed(signer.address().as_slice());\n let timestamp = get_current_unix_time_secs();\n\n let hmac_signature =\n build_hmac_signature(\u0026api_creds.secret, timestamp, method, req_path, body)?;\n\n Ok(HashMap::from([\n (POLY_ADDR_HEADER, address),\n (POLY_SIG_HEADER, hmac_signature),\n (POLY_TS_HEADER, timestamp.to_string()),\n (POLY_API_KEY_HEADER, api_creds.api_key.clone()),\n (POLY_PASS_HEADER, api_creds.passphrase.clone()),\n ]))\n}\n","traces":[{"line":44,"address":[],"length":0,"stats":{"Line":0}},{"line":45,"address":[],"length":0,"stats":{"Line":0}},{"line":47,"address":[],"length":0,"stats":{"Line":0}},{"line":48,"address":[],"length":0,"stats":{"Line":0}},{"line":50,"address":[],"length":0,"stats":{"Line":0}},{"line":51,"address":[],"length":0,"stats":{"Line":0}},{"line":52,"address":[],"length":0,"stats":{"Line":0}},{"line":53,"address":[],"length":0,"stats":{"Line":0}},{"line":54,"address":[],"length":0,"stats":{"Line":0}},{"line":55,"address":[],"length":0,"stats":{"Line":0}}],"covered":0,"coverable":10},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","external","polymarket-rs-client","src","lib.rs"],"content":"use alloy_primitives::hex::encode_prefixed;\npub use alloy_primitives::U256;\nuse alloy_signer_local::PrivateKeySigner;\npub use anyhow::{anyhow, Context, Result as ClientResult};\nuse config::get_contract_config;\nuse orders::OrderBuilder;\nuse orders::SignedOrderRequest;\nuse reqwest::header::HeaderName;\nuse reqwest::Client;\nuse reqwest::Method;\nuse reqwest::RequestBuilder;\nuse rust_decimal::Decimal;\npub use serde_json::Value;\nuse std::collections::HashMap;\n\n// #[cfg(test)]\n// mod tests;\n\nmod config;\nmod data;\nmod eth_utils;\nmod headers;\nmod orders;\nmod utils;\n\npub use data::*;\npub use eth_utils::EthSigner;\nuse headers::{create_l1_headers, create_l2_headers};\n\n#[derive(Default)]\npub struct ClobClient {\n host: String,\n http_client: Client,\n signer: Option\u003cBox\u003cdyn EthSigner\u003e\u003e,\n chain_id: Option\u003cu64\u003e,\n api_creds: Option\u003cApiCreds\u003e,\n order_builder: Option\u003cOrderBuilder\u003e,\n}\n\nconst INITIAL_CURSOR: \u0026str = \"MA==\";\nconst END_CURSOR: \u0026str = \"LTE=\";\n\nimpl ClobClient {\n // TODO: initial headers, gzip\n pub fn new(host: \u0026str) -\u003e Self {\n Self {\n host: host.to_owned(),\n http_client: Client::new(),\n ..Default::default()\n }\n }\n pub fn with_l1_headers(host: \u0026str, key: \u0026str, chain_id: u64) -\u003e Self {\n let signer = Box::new(\n key.parse::\u003cPrivateKeySigner\u003e()\n .expect(\"Invalid private key\"),\n );\n Self {\n host: host.to_owned(),\n http_client: Client::new(),\n signer: Some(signer.clone()),\n chain_id: Some(chain_id),\n api_creds: None,\n order_builder: Some(OrderBuilder::new(signer, None, None)),\n }\n }\n\n pub fn with_l2_headers(host: \u0026str, key: \u0026str, chain_id: u64, api_creds: ApiCreds) -\u003e Self {\n let signer = Box::new(\n key.parse::\u003cPrivateKeySigner\u003e()\n .expect(\"Invalid private key\"),\n );\n Self {\n host: host.to_owned(),\n http_client: Client::new(),\n signer: Some(signer.clone()),\n chain_id: Some(chain_id),\n api_creds: Some(api_creds),\n order_builder: Some(OrderBuilder::new(signer, None, None)),\n }\n }\n pub fn set_api_creds(\u0026mut self, api_creds: ApiCreds) {\n self.api_creds = Some(api_creds);\n }\n\n #[inline]\n fn get_l1_parameters(\u0026self) -\u003e (\u0026impl EthSigner, u64) {\n let signer = self.signer.as_ref().expect(\"Signer is not set\");\n let chain_id = self.chain_id.expect(\"Chain id is not set\");\n (signer, chain_id)\n }\n\n #[inline]\n fn get_l2_parameters(\u0026self) -\u003e (\u0026impl EthSigner, \u0026ApiCreds) {\n let signer = self.signer.as_ref().expect(\"Signer is not set\");\n (\n signer,\n self.api_creds.as_ref().expect(\"API credentials not set.\"),\n )\n }\n\n pub fn get_address(\u0026self) -\u003e Option\u003cString\u003e {\n Some(encode_prefixed(self.signer.as_ref()?.address().as_slice()))\n }\n\n pub fn get_collateral_address(\u0026self) -\u003e Option\u003cString\u003e {\n Some(get_contract_config(self.chain_id?, false)?.collateral)\n }\n\n pub fn get_conditional_address(\u0026self) -\u003e Option\u003cString\u003e {\n Some(get_contract_config(self.chain_id?, false)?.conditional_tokens)\n }\n\n pub fn get_exchange_address(\u0026self) -\u003e Option\u003cString\u003e {\n Some(get_contract_config(self.chain_id?, false)?.exchange)\n }\n\n fn create_request_with_headers(\n \u0026self,\n method: Method,\n endpoint: \u0026str,\n headers: impl Iterator\u003cItem = (\u0026'static str, String)\u003e,\n ) -\u003e RequestBuilder {\n let req = self\n .http_client\n .request(method, format!(\"{}{endpoint}\", \u0026self.host));\n\n headers.fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v))\n }\n\n pub async fn get_ok(\u0026self) -\u003e bool {\n self.http_client\n .get(format!(\"{}/\", \u0026self.host))\n .send()\n .await\n .is_ok()\n }\n\n pub async fn get_server_time(\u0026self) -\u003e ClientResult\u003cu64\u003e {\n let resp = self\n .http_client\n .get(format!(\"{}/time\", \u0026self.host))\n .send()\n .await?\n .text()\n .await?\n .parse::\u003cu64\u003e()?;\n Ok(resp)\n }\n\n pub async fn create_api_key(\u0026self, nonce: Option\u003cU256\u003e) -\u003e ClientResult\u003cApiCreds\u003e {\n let method = Method::POST;\n let endpoint = \"/auth/api-key\";\n let (signer, _) = self.get_l1_parameters();\n let headers = create_l1_headers(signer, nonce)?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.send().await?.json::\u003cApiCreds\u003e().await?)\n }\n\n pub async fn derive_api_key(\u0026self, nonce: Option\u003cU256\u003e) -\u003e ClientResult\u003cApiCreds\u003e {\n let method = Method::GET;\n let endpoint = \"/auth/derive-api-key\";\n let (signer, _) = self.get_l1_parameters();\n let headers = create_l1_headers(signer, nonce)?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.send().await?.json::\u003cApiCreds\u003e().await?)\n }\n\n pub async fn create_or_derive_api_key(\u0026self, nonce: Option\u003cU256\u003e) -\u003e ClientResult\u003cApiCreds\u003e {\n let creds = self.create_api_key(nonce).await;\n if creds.is_err() {\n return self.derive_api_key(nonce).await;\n }\n creds\n }\n\n pub async fn get_api_keys(\u0026self) -\u003e ClientResult\u003cVec\u003cString\u003e\u003e {\n let method = Method::GET;\n let endpoint = \"/auth/api-keys\";\n let (signer, creds) = self.get_l2_parameters();\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.send().await?.json::\u003cApiKeysResponse\u003e().await?.api_keys)\n }\n\n pub async fn delete_api_key(\u0026self) -\u003e ClientResult\u003cString\u003e {\n let method = Method::DELETE;\n let endpoint = \"/auth/api-key\";\n let (signer, creds) = self.get_l2_parameters();\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.send().await?.text().await?)\n }\n\n pub async fn get_midpoint(\u0026self, token_id: \u0026str) -\u003e ClientResult\u003cMidpointResponse\u003e {\n Ok(self\n .http_client\n .get(format!(\"{}/midpoint\", \u0026self.host))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?\n .json::\u003cMidpointResponse\u003e()\n .await?)\n }\n\n pub async fn get_midpoints(\n \u0026self,\n token_ids: \u0026[String],\n ) -\u003e ClientResult\u003cHashMap\u003cString, Decimal\u003e\u003e {\n let v = token_ids\n .iter()\n .map(|b| HashMap::from([(\"token_id\", b.clone())]))\n .collect::\u003cVec\u003cHashMap\u003c\u0026str, String\u003e\u003e\u003e();\n\n Ok(self\n .http_client\n .post(format!(\"{}/midpoints\", \u0026self.host))\n .json(\u0026v)\n .send()\n .await?\n .json::\u003cHashMap\u003cString, Decimal\u003e\u003e()\n .await?)\n }\n\n pub async fn get_price(\u0026self, token_id: \u0026str, side: Side) -\u003e ClientResult\u003cPriceResponse\u003e {\n Ok(self\n .http_client\n .get(format!(\"{}/price\", \u0026self.host))\n .query(\u0026[(\"token_id\", token_id)])\n .query(\u0026[(\"side\", side.as_str())])\n .send()\n .await?\n .json::\u003cPriceResponse\u003e()\n .await?)\n }\n pub async fn get_prices(\n \u0026self,\n book_params: \u0026[BookParams],\n ) -\u003e ClientResult\u003cHashMap\u003cString, HashMap\u003cSide, Decimal\u003e\u003e\u003e {\n let v = book_params\n .iter()\n .map(|b| {\n HashMap::from([\n (\"token_id\", b.token_id.clone()),\n (\"side\", b.side.as_str().to_owned()),\n ])\n })\n .collect::\u003cVec\u003cHashMap\u003c\u0026str, String\u003e\u003e\u003e();\n\n Ok(self\n .http_client\n .post(format!(\"{}/prices\", \u0026self.host))\n .json(\u0026v)\n .send()\n .await?\n .json::\u003cHashMap\u003cString, HashMap\u003cSide, Decimal\u003e\u003e\u003e()\n .await?)\n }\n\n pub async fn get_spread(\u0026self, token_id: \u0026str) -\u003e ClientResult\u003cSpreadResponse\u003e {\n Ok(self\n .http_client\n .get(format!(\"{}/spread\", \u0026self.host))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?\n .json::\u003cSpreadResponse\u003e()\n .await?)\n }\n\n pub async fn get_spreads(\n \u0026self,\n token_ids: \u0026[String],\n ) -\u003e ClientResult\u003cHashMap\u003cString, Decimal\u003e\u003e {\n let v = token_ids\n .iter()\n .map(|b| HashMap::from([(\"token_id\", b.clone())]))\n .collect::\u003cVec\u003cHashMap\u003c\u0026str, String\u003e\u003e\u003e();\n\n Ok(self\n .http_client\n .post(format!(\"{}/spreads\", \u0026self.host))\n .json(\u0026v)\n .send()\n .await?\n .json::\u003cHashMap\u003cString, Decimal\u003e\u003e()\n .await?)\n }\n\n // cache\n pub async fn get_tick_size(\u0026self, token_id: \u0026str) -\u003e ClientResult\u003cDecimal\u003e {\n Ok(self\n .http_client\n .get(format!(\"{}/tick-size\", \u0026self.host))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?\n .json::\u003cTickSizeResponse\u003e()\n .await?\n .minimum_tick_size)\n }\n // Cache\n pub async fn get_neg_risk(\u0026self, token_id: \u0026str) -\u003e ClientResult\u003cbool\u003e {\n Ok(self\n .http_client\n .get(format!(\"{}/neg-risk\", \u0026self.host))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?\n .json::\u003cNegRiskResponse\u003e()\n .await?\n .neg_risk)\n }\n\n async fn resolve_tick_size(\n \u0026self,\n token_id: \u0026str,\n tick_size: Option\u003cDecimal\u003e,\n ) -\u003e ClientResult\u003cDecimal\u003e {\n let min_tick_size = self\n .get_tick_size(token_id)\n .await\n .context(\"Error fetching tick size\")?;\n\n match tick_size {\n None =\u003e Ok(min_tick_size),\n Some(t) =\u003e {\n if t \u003c min_tick_size {\n Err(anyhow!(\"Tick size {t} is smaller than min_tick_size {min_tick_size} for token_id: {token_id}\"))\n } else {\n Ok(t)\n }\n }\n }\n }\n\n async fn get_filled_order_options(\n \u0026self,\n token_id: \u0026str,\n options: Option\u003c\u0026CreateOrderOptions\u003e,\n ) -\u003e ClientResult\u003cCreateOrderOptions\u003e {\n let (tick_size, neg_risk) = match options {\n Some(o) =\u003e (o.tick_size, o.neg_risk),\n None =\u003e (None, None),\n };\n\n let tick_size = self.resolve_tick_size(token_id, tick_size).await?;\n\n let neg_risk = match neg_risk {\n Some(nr) =\u003e nr,\n None =\u003e self.get_neg_risk(token_id).await?,\n };\n\n Ok(CreateOrderOptions {\n neg_risk: Some(neg_risk),\n tick_size: Some(tick_size),\n })\n }\n\n fn is_price_in_range(\u0026self, price: Decimal, tick_size: Decimal) -\u003e bool {\n let min_price = tick_size;\n let max_price = Decimal::ONE - tick_size;\n\n if price \u003c min_price || price \u003e max_price {\n return false;\n }\n true\n }\n\n pub async fn create_order(\n \u0026self,\n order_args: \u0026OrderArgs,\n expiration: Option\u003cu64\u003e,\n extras: Option\u003cExtraOrderArgs\u003e,\n options: Option\u003c\u0026CreateOrderOptions\u003e,\n ) -\u003e ClientResult\u003cSignedOrderRequest\u003e {\n let (_, chain_id) = self.get_l1_parameters();\n\n let create_order_options = self\n .get_filled_order_options(order_args.token_id.as_ref(), options)\n .await?;\n let expiration = expiration.unwrap_or(0);\n let extras = extras.unwrap_or_default();\n\n if !self.is_price_in_range(\n order_args.price,\n create_order_options.tick_size.expect(\"Should be filled\"),\n ) {\n return Err(anyhow!(\"Price is not in range of tick_size\"));\n }\n\n self.order_builder\n .as_ref()\n .expect(\"OrderBuilder not set\")\n .create_order(\n chain_id,\n order_args,\n expiration,\n \u0026extras,\n create_order_options,\n )\n }\n\n pub async fn get_order_book(\u0026self, token_id: \u0026str) -\u003e ClientResult\u003cOrderBookSummary\u003e {\n Ok(self\n .http_client\n .get(format!(\"{}/book\", \u0026self.host))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?\n .json::\u003cOrderBookSummary\u003e()\n .await?)\n }\n\n pub async fn get_order_books(\n \u0026self,\n token_ids: \u0026[String],\n ) -\u003e ClientResult\u003cVec\u003cOrderBookSummary\u003e\u003e {\n let v = token_ids\n .iter()\n .map(|b| HashMap::from([(\"token_id\", b.clone())]))\n .collect::\u003cVec\u003cHashMap\u003c\u0026str, String\u003e\u003e\u003e();\n\n Ok(self\n .http_client\n .post(format!(\"{}/books\", \u0026self.host))\n .json(\u0026v)\n .send()\n .await?\n .json::\u003cVec\u003cOrderBookSummary\u003e\u003e()\n .await?)\n }\n\n async fn calculate_market_price(\n \u0026self,\n token_id: \u0026str,\n side: Side,\n amount: Decimal,\n ) -\u003e ClientResult\u003cDecimal\u003e {\n let book = self.get_order_book(token_id).await?;\n let ob = self\n .order_builder\n .as_ref()\n .expect(\"No orderBuilder set for client!\");\n match side {\n Side::BUY =\u003e ob.calculate_market_price(\u0026book.asks, amount),\n Side::SELL =\u003e ob.calculate_market_price(\u0026book.bids, amount),\n }\n }\n\n pub async fn create_market_order(\n \u0026self,\n order_args: \u0026MarketOrderArgs,\n extras: Option\u003cExtraOrderArgs\u003e,\n options: Option\u003c\u0026CreateOrderOptions\u003e,\n ) -\u003e ClientResult\u003cSignedOrderRequest\u003e {\n let (_, chain_id) = self.get_l1_parameters();\n\n let create_order_options = self\n .get_filled_order_options(order_args.token_id.as_ref(), options)\n .await?;\n\n let extras = extras.unwrap_or_default();\n let price = self\n .calculate_market_price(\u0026order_args.token_id, Side::BUY, order_args.amount)\n .await?;\n if !self.is_price_in_range(\n price,\n create_order_options.tick_size.expect(\"Should be filled\"),\n ) {\n return Err(anyhow!(\"Price is not in range of tick_size\"));\n }\n\n self.order_builder\n .as_ref()\n .expect(\"OrderBuilder not set\")\n .create_market_order(chain_id, order_args, price, \u0026extras, create_order_options)\n }\n\n pub async fn post_order(\n \u0026self,\n order: SignedOrderRequest,\n order_type: OrderType,\n ) -\u003e ClientResult\u003cValue\u003e {\n let (signer, creds) = self.get_l2_parameters();\n let body = PostOrder::new(order, creds.api_key.clone(), order_type);\n\n let method = Method::POST;\n let endpoint = \"/order\";\n\n let headers = create_l2_headers(signer, creds, method.as_str(), endpoint, Some(\u0026body))?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.json(\u0026body).send().await?.json::\u003cValue\u003e().await?)\n }\n\n pub async fn create_and_post_order(\u0026self, order_args: \u0026OrderArgs) -\u003e ClientResult\u003cValue\u003e {\n let order = self.create_order(order_args, None, None, None).await?;\n self.post_order(order, OrderType::GTC).await\n }\n\n pub async fn cancel(\u0026self, order_id: \u0026str) -\u003e ClientResult\u003cValue\u003e {\n let (signer, creds) = self.get_l2_parameters();\n let body = HashMap::from([(\"orderID\", order_id)]);\n\n let method = Method::DELETE;\n let endpoint = \"/order\";\n\n let headers = create_l2_headers(signer, creds, method.as_str(), endpoint, Some(\u0026body))?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.json(\u0026body).send().await?.json::\u003cValue\u003e().await?)\n }\n\n pub async fn cancel_orders(\u0026self, order_ids: \u0026[String]) -\u003e ClientResult\u003cValue\u003e {\n let (signer, creds) = self.get_l2_parameters();\n let method = Method::DELETE;\n let endpoint = \"/orders\";\n\n let headers = create_l2_headers(signer, creds, method.as_str(), endpoint, Some(order_ids))?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.json(order_ids).send().await?.json::\u003cValue\u003e().await?)\n }\n\n pub async fn cancel_all(\u0026self) -\u003e ClientResult\u003cValue\u003e {\n let (signer, creds) = self.get_l2_parameters();\n let method = Method::DELETE;\n let endpoint = \"/cancel-all\";\n\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.send().await?.json::\u003cValue\u003e().await?)\n }\n\n pub async fn cancel_market_orders(\n \u0026self,\n market: Option\u003c\u0026str\u003e,\n asset_id: Option\u003c\u0026str\u003e,\n ) -\u003e ClientResult\u003cValue\u003e {\n let (signer, creds) = self.get_l2_parameters();\n let method = Method::DELETE;\n let endpoint = \"/cancel-market-orders\";\n let body = HashMap::from([\n (\"market\", market.unwrap_or(\"\")),\n (\"asset_id\", asset_id.unwrap_or(\"\")),\n ]);\n\n let headers = create_l2_headers(signer, creds, method.as_str(), endpoint, Some(\u0026body))?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.json(\u0026body).send().await?.json::\u003cValue\u003e().await?)\n }\n\n pub async fn get_orders(\n \u0026self,\n params: Option\u003c\u0026OpenOrderParams\u003e,\n next_cursor: Option\u003c\u0026str\u003e,\n ) -\u003e ClientResult\u003cVec\u003cOpenOrder\u003e\u003e {\n let (signer, creds) = self.get_l2_parameters();\n let method = Method::GET;\n let endpoint = \"/data/orders\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n\n let query_params = match params {\n None =\u003e Vec::new(),\n Some(p) =\u003e p.to_query_params(),\n };\n\n let mut next_cursor = next_cursor.unwrap_or(INITIAL_CURSOR).to_string();\n let mut output = Vec::new();\n while next_cursor != END_CURSOR {\n let req = self\n .http_client\n .request(method.clone(), format!(\"{}{endpoint}\", \u0026self.host))\n .query(\u0026query_params)\n .query(\u0026[\"next_cursor\", \u0026next_cursor]);\n\n let r = headers\n .clone()\n .into_iter()\n .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));\n\n let resp = r.send().await?.json::\u003cValue\u003e().await?;\n let new_cursor = resp[\"next_cursor\"]\n .as_str()\n .expect(\"Failed to parse next cursor\")\n .to_owned();\n\n next_cursor = new_cursor;\n\n let results = resp[\"data\"].clone();\n let o = serde_json::from_value::\u003cVec\u003cOpenOrder\u003e\u003e(results)\n .expect(\"Failed to parse data from order response\");\n output.extend(o);\n }\n Ok(output)\n }\n\n pub async fn get_order(\u0026self, order_id: \u0026str) -\u003e ClientResult\u003cOpenOrder\u003e {\n let (signer, creds) = self.get_l2_parameters();\n let method = Method::GET;\n let endpoint = \u0026format!(\"/data/order/{order_id}\");\n\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req.send().await?.json::\u003cOpenOrder\u003e().await?)\n }\n\n pub async fn get_last_trade_price(\u0026self, token_id: \u0026str) -\u003e ClientResult\u003cValue\u003e {\n Ok(self\n .http_client\n .get(format!(\"{}/last-trade-price\", \u0026self.host))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?\n .json::\u003cValue\u003e()\n .await?)\n }\n\n pub async fn get_last_trade_prices(\u0026self, token_ids: \u0026[String]) -\u003e ClientResult\u003cValue\u003e {\n let v = token_ids\n .iter()\n .map(|b| HashMap::from([(\"token_id\", b.clone())]))\n .collect::\u003cVec\u003cHashMap\u003c\u0026str, String\u003e\u003e\u003e();\n\n Ok(self\n .http_client\n .post(format!(\"{}/last-trades-prices\", \u0026self.host))\n .json(\u0026v)\n .send()\n .await?\n .json::\u003cValue\u003e()\n .await?)\n }\n\n pub async fn get_trades(\n \u0026self,\n trade_params: Option\u003c\u0026TradeParams\u003e,\n next_cursor: Option\u003c\u0026str\u003e,\n ) -\u003e ClientResult\u003cVec\u003cValue\u003e\u003e {\n let (signer, creds) = self.get_l2_parameters();\n let method = Method::GET;\n let endpoint = \"/data/trades\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n\n let query_params = match trade_params {\n None =\u003e Vec::new(),\n Some(p) =\u003e p.to_query_params(),\n };\n\n let mut next_cursor = next_cursor.unwrap_or(INITIAL_CURSOR).to_string();\n\n let mut output = Vec::new();\n while next_cursor != END_CURSOR {\n let req = self\n .http_client\n .request(method.clone(), format!(\"{}{endpoint}\", \u0026self.host))\n .query(\u0026query_params)\n .query(\u0026[\"next_cursor\", \u0026next_cursor]);\n\n let r = headers\n .clone()\n .into_iter()\n .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));\n\n let resp = r.send().await?.json::\u003cValue\u003e().await?;\n let new_cursor = resp[\"next_cursor\"]\n .as_str()\n .expect(\"Failed to parse next cursor\")\n .to_owned();\n\n next_cursor = new_cursor;\n\n let results = resp[\"data\"].clone();\n output.push(results);\n }\n Ok(output)\n }\n\n pub async fn get_notifications(\u0026self) -\u003e ClientResult\u003cValue\u003e {\n let (signer, creds) = self.get_l2_parameters();\n\n let method = Method::GET;\n let endpoint = \"/notifications\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req\n .query(\u0026[(\n \"signature_type\",\n \u0026self\n .order_builder\n .as_ref()\n .expect(\"Orderbuilder not set\")\n .get_sig_type(),\n )])\n .send()\n .await?\n .json::\u003cValue\u003e()\n .await?)\n }\n\n pub async fn drop_notifications(\u0026self, ids: \u0026[String]) -\u003e ClientResult\u003cValue\u003e {\n let (signer, creds) = self.get_l2_parameters();\n\n let method = Method::DELETE;\n let endpoint = \"/notifications\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req\n .query(\u0026[(\"ids\", ids.join(\",\"))])\n .send()\n .await?\n .json::\u003cValue\u003e()\n .await?)\n }\n\n pub async fn get_balance_allowance(\n \u0026self,\n params: Option\u003cBalanceAllowanceParams\u003e,\n ) -\u003e ClientResult\u003cValue\u003e {\n let mut params = params.unwrap_or_default();\n if params.signature_type.is_none() {\n params.set_signature_type(\n self.order_builder\n .as_ref()\n .expect(\"Orderbuilder not set\")\n .get_sig_type(),\n )\n }\n\n let query_params = params.to_query_params();\n\n let (signer, creds) = self.get_l2_parameters();\n\n let method = Method::GET;\n let endpoint = \"/balance-allowance\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n Ok(req\n .query(\u0026query_params)\n .send()\n .await?\n .json::\u003cValue\u003e()\n .await?)\n }\n\n pub async fn update_balance_allowance(\n \u0026self,\n params: Option\u003cBalanceAllowanceParams\u003e,\n ) -\u003e ClientResult\u003cValue\u003e {\n let mut params = params.unwrap_or_default();\n if params.signature_type.is_none() {\n params.set_signature_type(\n self.order_builder\n .as_ref()\n .expect(\"Orderbuilder not set\")\n .get_sig_type(),\n )\n }\n\n let query_params = params.to_query_params();\n\n let (signer, creds) = self.get_l2_parameters();\n\n let method = Method::GET;\n let endpoint = \"/balance-allowance/update\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n Ok(req\n .query(\u0026query_params)\n .send()\n .await?\n .json::\u003cValue\u003e()\n .await?)\n }\n\n pub async fn is_order_scoring(\u0026self, order_id: \u0026str) -\u003e ClientResult\u003cbool\u003e {\n let (signer, creds) = self.get_l2_parameters();\n\n let method = Method::GET;\n let endpoint = \"/order-scoring\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, creds, method.as_str(), endpoint, None)?;\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req\n .query(\u0026[(\"order_id\", order_id)])\n .send()\n .await?\n .json::\u003cValue\u003e()\n .await?[\"scoring\"]\n .as_bool()\n .expect(\"Unknown scoring value\"))\n }\n\n pub async fn are_orders_scoring(\n \u0026self,\n order_ids: \u0026[\u0026str],\n ) -\u003e ClientResult\u003cHashMap\u003cString, bool\u003e\u003e {\n let (signer, creds) = self.get_l2_parameters();\n\n let method = Method::POST;\n let endpoint = \"/orders-scoring\";\n\n let headers = create_l2_headers(signer, creds, method.as_str(), endpoint, Some(order_ids))?;\n let req = self.create_request_with_headers(method, endpoint, headers.into_iter());\n\n Ok(req\n .json(order_ids)\n .send()\n .await?\n .json::\u003cHashMap\u003cString, bool\u003e\u003e()\n .await?)\n }\n\n pub async fn get_sampling_markets(\u0026self, next_cursor: Option\u003c\u0026str\u003e) -\u003e ClientResult\u003cMarketsResponse\u003e {\n let next_cursor = next_cursor.unwrap_or(INITIAL_CURSOR);\n\n Ok(self\n .http_client\n .get(format!(\"{}/sampling-markets\", \u0026self.host))\n .query(\u0026[(\"next_cursor\", next_cursor)])\n .send()\n .await?\n .json::\u003cMarketsResponse\u003e()\n .await?)\n }\n\n pub async fn get_sampling_simplified_markets(\n \u0026self,\n next_cursor: Option\u003c\u0026str\u003e,\n ) -\u003e ClientResult\u003cSimplifiedMarketsResponse\u003e {\n let next_cursor = next_cursor.unwrap_or(INITIAL_CURSOR);\n\n Ok(self\n .http_client\n .get(format!(\"{}/sampling-simplified-markets\", \u0026self.host))\n .query(\u0026[(\"next_cursor\", next_cursor)])\n .send()\n .await?\n .json::\u003cSimplifiedMarketsResponse\u003e()\n .await?)\n }\n\n pub async fn get_markets(\u0026self, next_cursor: Option\u003c\u0026str\u003e) -\u003e ClientResult\u003cMarketsResponse\u003e {\n let next_cursor = next_cursor.unwrap_or(INITIAL_CURSOR);\n\n Ok(self\n .http_client\n .get(format!(\"{}/markets\", \u0026self.host))\n .query(\u0026[(\"next_cursor\", next_cursor)])\n .send()\n .await?\n .json::\u003cMarketsResponse\u003e()\n .await?)\n }\n\n pub async fn get_simplified_markets(\n \u0026self,\n next_cursor: Option\u003c\u0026str\u003e,\n ) -\u003e ClientResult\u003cSimplifiedMarketsResponse\u003e {\n let next_cursor = next_cursor.unwrap_or(INITIAL_CURSOR);\n\n Ok(self\n .http_client\n .get(format!(\"{}/simplified-markets\", \u0026self.host))\n .query(\u0026[(\"next_cursor\", next_cursor)])\n .send()\n .await?\n .json::\u003cSimplifiedMarketsResponse\u003e()\n .await?)\n }\n\n pub async fn get_market(\u0026self, condition_id: \u0026str) -\u003e ClientResult\u003cMarket\u003e {\n Ok(self\n .http_client\n .get(format!(\"{}/markets/{condition_id}\", \u0026self.host))\n .send()\n .await?\n .json::\u003cMarket\u003e()\n .await?)\n }\n\n pub async fn get_market_trades_events(\u0026self, condition_id: \u0026str) -\u003e ClientResult\u003cValue\u003e {\n Ok(self\n .http_client\n .get(format!(\n \"{}/live-activity/events/{condition_id}\",\n \u0026self.host\n ))\n .send()\n .await?\n .json::\u003cValue\u003e()\n .await?)\n }\n}\n","traces":[{"line":86,"address":[],"length":0,"stats":{"Line":0}},{"line":87,"address":[],"length":0,"stats":{"Line":0}},{"line":88,"address":[],"length":0,"stats":{"Line":0}},{"line":89,"address":[],"length":0,"stats":{"Line":0}},{"line":93,"address":[],"length":0,"stats":{"Line":0}},{"line":94,"address":[],"length":0,"stats":{"Line":0}},{"line":96,"address":[],"length":0,"stats":{"Line":0}},{"line":97,"address":[],"length":0,"stats":{"Line":0}},{"line":123,"address":[],"length":0,"stats":{"Line":0}},{"line":124,"address":[],"length":0,"stats":{"Line":0}},{"line":125,"address":[],"length":0,"stats":{"Line":0}},{"line":127,"address":[],"length":0,"stats":{"Line":0}}],"covered":0,"coverable":12},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","external","polymarket-rs-client","src","orders.rs"],"content":"use alloy_primitives::Address;\nuse alloy_primitives::U256;\nuse anyhow::anyhow;\nuse anyhow::{Context, Result};\nuse rand::thread_rng;\nuse rand::Rng;\nuse rust_decimal::Decimal;\nuse rust_decimal::RoundingStrategy::{AwayFromZero, MidpointTowardZero, ToZero};\n\nuse serde::Serialize;\n\nuse crate::config::get_contract_config;\nuse crate::eth_utils::sign_order_message;\nuse crate::eth_utils::Order;\nuse crate::utils::get_current_unix_time_secs;\nuse crate::{\n CreateOrderOptions, EthSigner, ExtraOrderArgs, MarketOrderArgs, OrderArgs, OrderSummary, Side,\n};\n\nuse std::collections::HashMap;\nuse std::str::FromStr;\nuse std::sync::LazyLock;\n\n#[derive(Copy, Clone)]\npub enum SigType {\n // ECDSA EIP712 signatures signed by EOAs\n Eoa = 0,\n // EIP712 signatures signed by EOAs that own Polymarket Proxy wallets\n PolyProxy = 1,\n // EIP712 signatures signed by EOAs that own Polymarket Gnosis safes\n PolyGnosisSafe = 2,\n}\n\npub struct OrderBuilder {\n signer: Box\u003cdyn EthSigner\u003e,\n sig_type: SigType,\n funder: Address,\n}\n\npub struct RoundConfig {\n price: u32,\n size: u32,\n amount: u32,\n}\n\nfn generate_seed() -\u003e u64 {\n let mut rng = thread_rng();\n let y: f64 = rng.gen();\n let a: f64 = get_current_unix_time_secs() as f64 * y;\n a as u64\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct SignedOrderRequest {\n pub salt: u64,\n pub maker: String,\n pub signer: String,\n pub taker: String,\n pub token_id: String,\n pub maker_amount: String,\n pub taker_amount: String,\n pub expiration: String,\n pub nonce: String,\n pub fee_rate_bps: String,\n pub side: String,\n pub signature_type: u8,\n pub signature: String,\n}\n\nstatic ROUNDING_CONFIG: LazyLock\u003cHashMap\u003cDecimal, RoundConfig\u003e\u003e = LazyLock::new(|| {\n HashMap::from([\n (\n Decimal::from_str(\"0.1\").unwrap(),\n RoundConfig {\n price: 1,\n size: 2,\n amount: 3,\n },\n ),\n (\n Decimal::from_str(\"0.01\").unwrap(),\n RoundConfig {\n price: 2,\n size: 2,\n amount: 4,\n },\n ),\n (\n Decimal::from_str(\"0.001\").unwrap(),\n RoundConfig {\n price: 3,\n size: 2,\n amount: 5,\n },\n ),\n (\n Decimal::from_str(\"0.0001\").unwrap(),\n RoundConfig {\n price: 4,\n size: 2,\n amount: 6,\n },\n ),\n ])\n});\n\nfn decimal_to_token_u32(amt: Decimal) -\u003e u32 {\n let mut amt = Decimal::from_scientific(\"1e6\").expect(\"1e6 is not scientific\") * amt;\n if amt.scale() \u003e 0 {\n amt = amt.round_dp_with_strategy(0, MidpointTowardZero);\n }\n amt.try_into().expect(\"Couldn't round decimal to integer\")\n}\n\nimpl OrderBuilder {\n pub fn new(\n signer: Box\u003cdyn EthSigner\u003e,\n sig_type: Option\u003cSigType\u003e,\n funder: Option\u003cAddress\u003e,\n ) -\u003e Self {\n let sig_type = sig_type.unwrap_or(SigType::Eoa);\n let funder = funder.unwrap_or(signer.address());\n\n OrderBuilder {\n signer,\n sig_type,\n funder,\n }\n }\n\n pub fn get_sig_type(\u0026self) -\u003e u8 {\n self.sig_type as u8\n }\n\n fn fix_amount_rounding(\u0026self, mut amt: Decimal, round_config: \u0026RoundConfig) -\u003e Decimal {\n if amt.scale() \u003e round_config.amount {\n amt = amt.round_dp_with_strategy(round_config.amount + 4, AwayFromZero);\n if amt.scale() \u003e round_config.amount {\n amt = amt.round_dp_with_strategy(round_config.amount, ToZero);\n }\n }\n amt\n }\n\n fn get_order_amounts(\n \u0026self,\n side: Side,\n size: Decimal,\n price: Decimal,\n round_config: \u0026RoundConfig,\n ) -\u003e (u32, u32) {\n let raw_price = price.round_dp_with_strategy(round_config.price, MidpointTowardZero);\n\n match side {\n Side::BUY =\u003e {\n let raw_taker_amt = size.round_dp_with_strategy(round_config.size, ToZero);\n let raw_maker_amt = raw_taker_amt * raw_price;\n let raw_maker_amt = self.fix_amount_rounding(raw_maker_amt, round_config);\n (\n decimal_to_token_u32(raw_maker_amt),\n decimal_to_token_u32(raw_taker_amt),\n )\n }\n Side::SELL =\u003e {\n let raw_maker_amt = size.round_dp_with_strategy(round_config.size, ToZero);\n let raw_taker_amt = raw_maker_amt * raw_price;\n let raw_taker_amt = self.fix_amount_rounding(raw_taker_amt, round_config);\n\n (\n decimal_to_token_u32(raw_maker_amt),\n decimal_to_token_u32(raw_taker_amt),\n )\n }\n }\n }\n\n fn get_market_order_amounts(\n \u0026self,\n amount: Decimal,\n price: Decimal,\n round_config: \u0026RoundConfig,\n ) -\u003e (u32, u32) {\n let raw_maker_amt = amount.round_dp_with_strategy(round_config.size, ToZero);\n let raw_price = price.round_dp_with_strategy(round_config.price, MidpointTowardZero);\n\n let raw_taker_amt = raw_maker_amt / raw_price;\n\n let raw_taker_amt = self.fix_amount_rounding(raw_taker_amt, round_config);\n\n (\n decimal_to_token_u32(raw_maker_amt),\n decimal_to_token_u32(raw_taker_amt),\n )\n }\n\n pub fn calculate_market_price(\n \u0026self,\n positions: \u0026[OrderSummary],\n amount_to_match: Decimal,\n ) -\u003e Result\u003cDecimal\u003e {\n let mut sum = Decimal::ZERO;\n\n for p in positions {\n sum += p.size * p.price;\n if sum \u003e= amount_to_match {\n return Ok(p.price);\n }\n }\n Err(anyhow!(\n \"Not enough liquidity to create market order with amount {amount_to_match}\"\n ))\n }\n\n pub fn create_market_order(\n \u0026self,\n chain_id: u64,\n order_args: \u0026MarketOrderArgs,\n price: Decimal,\n extras: \u0026ExtraOrderArgs,\n options: CreateOrderOptions,\n ) -\u003e Result\u003cSignedOrderRequest\u003e {\n let (maker_amount, taker_amount) = self.get_market_order_amounts(\n order_args.amount,\n price,\n \u0026ROUNDING_CONFIG[\u0026options\n .tick_size\n .context(\"Cannot create order without tick size\")?],\n );\n\n let contract_config = get_contract_config(\n chain_id,\n options\n .neg_risk\n .context(\"Cannot create order without neg_risk\")?,\n )\n .context(\"No contract found with given chain_id and neg_risk\")?;\n\n let exchange_address = Address::from_str(contract_config.exchange.as_ref())\n .context(\"Invalid exchange address\")?;\n\n self.build_signed_order(\n order_args.token_id.clone(),\n Side::BUY,\n chain_id,\n exchange_address,\n maker_amount,\n taker_amount,\n 0,\n extras,\n )\n }\n\n pub fn create_order(\n \u0026self,\n chain_id: u64,\n order_args: \u0026OrderArgs,\n expiration: u64,\n extras: \u0026ExtraOrderArgs,\n options: CreateOrderOptions,\n ) -\u003e Result\u003cSignedOrderRequest\u003e {\n let (maker_amount, taker_amount) = self.get_order_amounts(\n order_args.side,\n order_args.size,\n order_args.price,\n \u0026ROUNDING_CONFIG[\u0026options\n .tick_size\n .context(\"Cannot create order without tick size\")?],\n );\n\n let contract_config = get_contract_config(\n chain_id,\n options\n .neg_risk\n .context(\"Cannot create order without neg_risk\")?,\n )\n .context(\"No contract found with given chain_id and neg_risk\")?;\n\n let exchange_address = Address::from_str(contract_config.exchange.as_ref())\n .context(\"Invalid exchange address\")?;\n\n self.build_signed_order(\n order_args.token_id.clone(),\n order_args.side,\n chain_id,\n exchange_address,\n maker_amount,\n taker_amount,\n expiration,\n extras,\n )\n }\n\n #[allow(clippy::too_many_arguments)]\n fn build_signed_order(\n \u0026self,\n token_id: String,\n side: Side,\n chain_id: u64,\n exchange: Address,\n maker_amount: u32,\n taker_amount: u32,\n expiration: u64,\n extras: \u0026ExtraOrderArgs,\n ) -\u003e Result\u003cSignedOrderRequest\u003e {\n let seed = generate_seed();\n let taker_address =\n Address::from_str(extras.taker.as_ref()).context(\"Invalid taker address\")?;\n\n let u256_token_id =\n U256::from_str_radix(token_id.as_ref(), 10).context(\"Incorrect tokenId format\")?;\n\n let order = Order {\n salt: U256::from(seed),\n maker: self.funder,\n signer: self.signer.address(),\n taker: taker_address,\n tokenId: u256_token_id,\n makerAmount: U256::from(maker_amount),\n takerAmount: U256::from(taker_amount),\n expiration: U256::from(expiration),\n nonce: extras.nonce,\n feeRateBps: U256::from(extras.fee_rate_bps),\n side: side as u8,\n signatureType: self.sig_type as u8,\n };\n\n let signature = sign_order_message(\u0026self.signer, order, chain_id, exchange)?;\n\n Ok(SignedOrderRequest {\n salt: seed,\n maker: self.funder.to_checksum(None),\n signer: self.signer.address().to_checksum(None),\n taker: taker_address.to_checksum(None),\n token_id,\n maker_amount: maker_amount.to_string(),\n taker_amount: taker_amount.to_string(),\n expiration: expiration.to_string(),\n nonce: extras.nonce.to_string(),\n fee_rate_bps: extras.fee_rate_bps.to_string(),\n side: side.as_str().into(),\n signature_type: self.sig_type as u8,\n signature,\n })\n }\n}\n","traces":[],"covered":0,"coverable":0},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","external","polymarket-rs-client","src","utils.rs"],"content":"use anyhow::{Context, Result};\nuse base64::{engine::general_purpose::URL_SAFE, Engine};\nuse serde::Serialize;\nuse serde_json_fmt::JsonFormat;\nuse std::time::{SystemTime, UNIX_EPOCH};\n\nuse hmac::{Hmac, Mac};\nuse sha2::Sha256;\n\ntype HmacSha256 = Hmac\u003cSha256\u003e;\n\npub fn get_current_unix_time_secs() -\u003e u64 {\n SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .expect(\"Time went backwards\")\n .as_secs()\n}\n\npub fn build_hmac_signature\u003cT\u003e(\n secret: \u0026str,\n timestamp: u64,\n method: \u0026str,\n req_path: \u0026str,\n body: Option\u003c\u0026T\u003e,\n) -\u003e Result\u003cString\u003e\nwhere\n T: ?Sized + Serialize,\n{\n let decoded = URL_SAFE\n .decode(secret)\n .context(\"Can't decode secret to base64\")?;\n let message = match body {\n None =\u003e format!(\"{timestamp}{method}{req_path}\"),\n Some(s) =\u003e {\n // We format like str(dict) in python\n let s = JsonFormat::new()\n .comma(\", \")?\n .colon(\": \")?\n .format_to_string(\u0026s)?;\n format!(\"{timestamp}{method}{req_path}{s}\")\n }\n };\n\n let mut mac = HmacSha256::new_from_slice(\u0026decoded).context(\"HMAC init error\")?;\n mac.update(message.as_bytes());\n\n let result = mac.finalize();\n\n Ok(URL_SAFE.encode(\u0026result.into_bytes()[..]))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use std::collections::HashMap;\n\n #[test]\n fn test_build_hmac_signature() {\n let body = HashMap::from([(\"hash\", \"0x123\")]);\n let signature = build_hmac_signature(\n \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\n 1000000,\n \"test-sign\",\n \"/orders\",\n Some(\u0026body),\n )\n .unwrap();\n\n assert_eq!(signature, \"ZwAdJKvoYRlEKDkNMwd5BuwNNtg93kNaR_oU2HrfVvc=\");\n }\n}\n","traces":[{"line":29,"address":[],"length":0,"stats":{"Line":0}},{"line":30,"address":[],"length":0,"stats":{"Line":0}},{"line":32,"address":[],"length":0,"stats":{"Line":0}},{"line":33,"address":[],"length":0,"stats":{"Line":0}},{"line":34,"address":[],"length":0,"stats":{"Line":0}},{"line":36,"address":[],"length":0,"stats":{"Line":0}},{"line":39,"address":[],"length":0,"stats":{"Line":0}},{"line":40,"address":[],"length":0,"stats":{"Line":0}},{"line":44,"address":[],"length":0,"stats":{"Line":0}},{"line":45,"address":[],"length":0,"stats":{"Line":0}},{"line":47,"address":[],"length":0,"stats":{"Line":0}},{"line":49,"address":[],"length":0,"stats":{"Line":0}}],"covered":0,"coverable":12},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","auth.rs"],"content":"//! Authentication and cryptographic utilities for Polymarket API\n//!\n//! This module provides EIP-712 signing, HMAC authentication, and header generation\n//! for secure communication with the Polymarket CLOB API.\n\nuse crate::errors::{PolyfillError, Result};\nuse crate::types::ApiCredentials;\nuse alloy_primitives::{hex::encode_prefixed, Address, U256};\nuse alloy_signer::SignerSync;\nuse alloy_signer_local::PrivateKeySigner;\nuse alloy_sol_types::{eip712_domain, sol};\nuse base64::engine::Engine;\nuse hmac::{Hmac, Mac};\nuse serde::Serialize;\nuse sha2::Sha256;\nuse std::collections::HashMap;\nuse std::time::{SystemTime, UNIX_EPOCH};\n\n// Header constants\nconst POLY_ADDR_HEADER: \u0026str = \"poly_address\";\nconst POLY_SIG_HEADER: \u0026str = \"poly_signature\";\nconst POLY_TS_HEADER: \u0026str = \"poly_timestamp\";\nconst POLY_NONCE_HEADER: \u0026str = \"poly_nonce\";\nconst POLY_API_KEY_HEADER: \u0026str = \"poly_api_key\";\nconst POLY_PASS_HEADER: \u0026str = \"poly_passphrase\";\n\ntype Headers = HashMap\u003c\u0026'static str, String\u003e;\n\n/// EIP-712 struct for CLOB authentication\nsol! {\n struct ClobAuth {\n address address;\n string timestamp;\n uint256 nonce;\n string message;\n }\n}\n\n/// EIP-712 struct for order signing\nsol! {\n struct Order {\n uint256 salt;\n address maker;\n address signer;\n address taker;\n uint256 tokenId;\n uint256 makerAmount;\n uint256 takerAmount;\n uint256 expiration;\n uint256 nonce;\n uint256 feeRateBps;\n uint8 side;\n uint8 signatureType;\n }\n}\n\n\n/// Get current Unix timestamp in seconds\npub fn get_current_unix_time_secs() -\u003e u64 {\n SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .expect(\"Time went backwards\")\n .as_secs()\n}\n\n/// Sign CLOB authentication message using EIP-712\npub fn sign_clob_auth_message(\n signer: \u0026PrivateKeySigner,\n timestamp: String,\n nonce: U256,\n) -\u003e Result\u003cString\u003e {\n let message = \"This message attests that I control the given wallet\".to_string();\n let polygon = 137;\n\n let auth_struct = ClobAuth {\n address: signer.address(),\n timestamp,\n nonce,\n message,\n };\n\n let domain = eip712_domain!(\n name: \"ClobAuthDomain\",\n version: \"1\",\n chain_id: polygon,\n );\n\n let signature = signer\n .sign_typed_data_sync(\u0026auth_struct, \u0026domain)\n .map_err(|e| PolyfillError::crypto(format!(\"EIP-712 signature failed: {}\", e)))?;\n\n Ok(encode_prefixed(signature.as_bytes()))\n}\n\n/// Sign order message using EIP-712\npub fn sign_order_message(\n signer: \u0026PrivateKeySigner,\n order: Order,\n chain_id: u64,\n verifying_contract: Address,\n) -\u003e Result\u003cString\u003e {\n let domain = eip712_domain!(\n name: \"Polymarket CTF Exchange\",\n version: \"1\",\n chain_id: chain_id,\n verifying_contract: verifying_contract,\n );\n\n let signature = signer\n .sign_typed_data_sync(\u0026order, \u0026domain)\n .map_err(|e| PolyfillError::crypto(format!(\"Order signature failed: {}\", e)))?;\n\n Ok(encode_prefixed(signature.as_bytes()))\n}\n\n/// Build HMAC signature for L2 authentication\npub fn build_hmac_signature\u003cT\u003e(\n secret: \u0026str,\n timestamp: u64,\n method: \u0026str,\n request_path: \u0026str,\n body: Option\u003c\u0026T\u003e,\n) -\u003e Result\u003cString\u003e\nwhere\n T: ?Sized + Serialize,\n{\n let mut mac = Hmac::\u003cSha256\u003e::new_from_slice(secret.as_bytes())\n .map_err(|e| PolyfillError::crypto(format!(\"Invalid HMAC key: {}\", e)))?;\n\n // Build the message to sign: timestamp + method + path + body\n let message = format!(\n \"{}{}{}{}\",\n timestamp,\n method.to_uppercase(),\n request_path,\n match body {\n Some(b) =\u003e serde_json::to_string(b)\n .map_err(|e| PolyfillError::parse(format!(\"Failed to serialize body: {}\", e), None))?,\n None =\u003e String::new(),\n }\n );\n\n mac.update(message.as_bytes());\n let result = mac.finalize();\n Ok(base64::engine::general_purpose::STANDARD.encode(result.into_bytes()))\n}\n\n/// Create L1 headers for authentication (using private key signature)\npub fn create_l1_headers(signer: \u0026PrivateKeySigner, nonce: Option\u003cU256\u003e) -\u003e Result\u003cHeaders\u003e {\n let timestamp = get_current_unix_time_secs().to_string();\n let nonce = nonce.unwrap_or(U256::ZERO);\n let signature = sign_clob_auth_message(signer, timestamp.clone(), nonce)?;\n let address = encode_prefixed(signer.address().as_slice());\n\n Ok(HashMap::from([\n (POLY_ADDR_HEADER, address),\n (POLY_SIG_HEADER, signature),\n (POLY_TS_HEADER, timestamp),\n (POLY_NONCE_HEADER, nonce.to_string()),\n ]))\n}\n\n/// Create L2 headers for API calls (using API key and HMAC)\npub fn create_l2_headers\u003cT\u003e(\n signer: \u0026PrivateKeySigner,\n api_creds: \u0026ApiCredentials,\n method: \u0026str,\n req_path: \u0026str,\n body: Option\u003c\u0026T\u003e,\n) -\u003e Result\u003cHeaders\u003e\nwhere\n T: ?Sized + Serialize,\n{\n let address = encode_prefixed(signer.address().as_slice());\n let timestamp = get_current_unix_time_secs();\n\n let hmac_signature = build_hmac_signature(\u0026api_creds.secret, timestamp, method, req_path, body)?;\n\n Ok(HashMap::from([\n (POLY_ADDR_HEADER, address),\n (POLY_SIG_HEADER, hmac_signature),\n (POLY_TS_HEADER, timestamp.to_string()),\n (POLY_API_KEY_HEADER, api_creds.api_key.clone()),\n (POLY_PASS_HEADER, api_creds.passphrase.clone()),\n ]))\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_unix_timestamp() {\n let timestamp = get_current_unix_time_secs();\n assert!(timestamp \u003e 1_600_000_000); // Should be after 2020\n }\n\n #[test]\n fn test_hmac_signature() {\n let result = build_hmac_signature::\u003cString\u003e(\n \"test_secret\",\n 1234567890,\n \"GET\",\n \"/test\",\n None,\n );\n assert!(result.is_ok());\n }\n}\n","traces":[{"line":59,"address":[],"length":0,"stats":{"Line":1}},{"line":60,"address":[],"length":0,"stats":{"Line":2}},{"line":61,"address":[],"length":0,"stats":{"Line":2}},{"line":67,"address":[],"length":0,"stats":{"Line":0}},{"line":72,"address":[],"length":0,"stats":{"Line":0}},{"line":73,"address":[],"length":0,"stats":{"Line":0}},{"line":76,"address":[],"length":0,"stats":{"Line":0}},{"line":82,"address":[],"length":0,"stats":{"Line":0}},{"line":83,"address":[],"length":0,"stats":{"Line":0}},{"line":84,"address":[],"length":0,"stats":{"Line":0}},{"line":85,"address":[],"length":0,"stats":{"Line":0}},{"line":88,"address":[],"length":0,"stats":{"Line":0}},{"line":89,"address":[],"length":0,"stats":{"Line":0}},{"line":90,"address":[],"length":0,"stats":{"Line":0}},{"line":92,"address":[],"length":0,"stats":{"Line":0}},{"line":96,"address":[],"length":0,"stats":{"Line":0}},{"line":102,"address":[],"length":0,"stats":{"Line":0}},{"line":103,"address":[],"length":0,"stats":{"Line":0}},{"line":104,"address":[],"length":0,"stats":{"Line":0}},{"line":105,"address":[],"length":0,"stats":{"Line":0}},{"line":106,"address":[],"length":0,"stats":{"Line":0}},{"line":109,"address":[],"length":0,"stats":{"Line":0}},{"line":110,"address":[],"length":0,"stats":{"Line":0}},{"line":111,"address":[],"length":0,"stats":{"Line":0}},{"line":113,"address":[],"length":0,"stats":{"Line":0}},{"line":117,"address":[],"length":0,"stats":{"Line":1}},{"line":127,"address":[],"length":0,"stats":{"Line":3}},{"line":128,"address":[],"length":0,"stats":{"Line":1}},{"line":131,"address":[],"length":0,"stats":{"Line":2}},{"line":133,"address":[],"length":0,"stats":{"Line":0}},{"line":134,"address":[],"length":0,"stats":{"Line":2}},{"line":135,"address":[],"length":0,"stats":{"Line":0}},{"line":136,"address":[],"length":0,"stats":{"Line":1}},{"line":137,"address":[],"length":0,"stats":{"Line":0}},{"line":138,"address":[],"length":0,"stats":{"Line":0}},{"line":139,"address":[],"length":0,"stats":{"Line":1}},{"line":143,"address":[],"length":0,"stats":{"Line":3}},{"line":144,"address":[],"length":0,"stats":{"Line":3}},{"line":145,"address":[],"length":0,"stats":{"Line":3}},{"line":149,"address":[],"length":0,"stats":{"Line":0}},{"line":150,"address":[],"length":0,"stats":{"Line":0}},{"line":151,"address":[],"length":0,"stats":{"Line":0}},{"line":152,"address":[],"length":0,"stats":{"Line":0}},{"line":153,"address":[],"length":0,"stats":{"Line":0}},{"line":155,"address":[],"length":0,"stats":{"Line":0}},{"line":156,"address":[],"length":0,"stats":{"Line":0}},{"line":157,"address":[],"length":0,"stats":{"Line":0}},{"line":158,"address":[],"length":0,"stats":{"Line":0}},{"line":159,"address":[],"length":0,"stats":{"Line":0}},{"line":164,"address":[],"length":0,"stats":{"Line":0}},{"line":174,"address":[],"length":0,"stats":{"Line":0}},{"line":175,"address":[],"length":0,"stats":{"Line":0}},{"line":177,"address":[],"length":0,"stats":{"Line":0}},{"line":179,"address":[],"length":0,"stats":{"Line":0}},{"line":180,"address":[],"length":0,"stats":{"Line":0}},{"line":181,"address":[],"length":0,"stats":{"Line":0}},{"line":182,"address":[],"length":0,"stats":{"Line":0}},{"line":183,"address":[],"length":0,"stats":{"Line":0}},{"line":184,"address":[],"length":0,"stats":{"Line":0}}],"covered":13,"coverable":59},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","book.rs"],"content":"//! Order book management for Polymarket client\n\nuse crate::errors::{PolyfillError, Result};\nuse crate::types::*;\nuse crate::utils::math;\nuse rust_decimal::Decimal;\nuse std::collections::BTreeMap; // BTreeMap keeps prices sorted automatically - crucial for order books\nuse std::sync::{Arc, RwLock}; // For thread-safe access across multiple tasks\nuse tracing::{debug, trace, warn}; // Logging for debugging and monitoring\nuse chrono::Utc;\n\n/// High-performance order book implementation\n/// \n/// This is the core data structure that holds all the live buy/sell orders for a token.\n/// The efficiency of this code is critical as the order book is constantly being updated as orders are added and removed.\n/// \n/// PERFORMANCE OPTIMIZATION: This struct now uses fixed-point integers internally\n/// instead of Decimal for maximum speed. The performance difference is dramatic:\n/// \n/// Before (Decimal): ~100ns per operation + memory allocation\n/// After (fixed-point): ~5ns per operation, zero allocations\n\n#[derive(Debug, Clone)]\npub struct OrderBook {\n /// Token ID this book represents (like \"123456\" for a specific prediction market outcome)\n pub token_id: String,\n \n /// Hash of token_id for fast lookups (avoids string comparisons in hot path)\n pub token_id_hash: u64,\n \n /// Current sequence number for ordering updates\n /// This helps us ignore old/duplicate updates that arrive out of order\n pub sequence: u64,\n \n /// Last update timestamp - when we last got new data for this book\n pub timestamp: chrono::DateTime\u003cUtc\u003e,\n \n /// Bid side (price -\u003e size, sorted descending) - NOW USING FIXED-POINT!\n /// BTreeMap automatically keeps highest bids first, which is what we want\n /// Key = price in ticks (like 6500 for $0.65), Value = size in fixed-point units\n /// \n /// BEFORE (slow): bids: BTreeMap\u003cDecimal, Decimal\u003e,\n /// AFTER (fast): bids: BTreeMap\u003cPrice, Qty\u003e,\n /// \n /// Why this is faster:\n /// - Integer comparisons are ~10x faster than Decimal comparisons\n /// - No memory allocation for each price level\n /// - Better CPU cache utilization (smaller data structures)\n bids: BTreeMap\u003cPrice, Qty\u003e,\n \n /// Ask side (price -\u003e size, sorted ascending) - NOW USING FIXED-POINT!\n /// BTreeMap keeps lowest asks first - people selling at cheapest prices\n /// \n /// BEFORE (slow): asks: BTreeMap\u003cDecimal, Decimal\u003e,\n /// AFTER (fast): asks: BTreeMap\u003cPrice, Qty\u003e,\n asks: BTreeMap\u003cPrice, Qty\u003e,\n \n /// Minimum tick size for this market in ticks (like 10 for $0.001 increments)\n /// Some markets only allow certain price increments\n /// We store this in ticks for fast validation without conversion\n tick_size_ticks: Option\u003cPrice\u003e,\n \n /// Maximum depth to maintain (how many price levels to keep)\n /// \n /// We don't need to track every single price level, just the best ones because:\n /// - Trading reality 90% of volume happens in the top 5-10 price levels\n /// - Execution priority: Orders get filled from best price first, so deep levels often don't matter\n /// - Market efficiency: If you're buying and best ask is $0.67, you'll never pay $0.95\n /// - Risk management: Large orders that would hit deep levels are usually broken up\n /// - Data freshness: Deep levels often have stale orders from hours/days ago\n /// \n /// Typical values: 10-50 for retail, 100-500 for institutional HFT systems\n max_depth: usize,\n}\n\nimpl OrderBook {\n /// Create a new order book\n /// Just sets up empty bid/ask maps and basic metadata\n pub fn new(token_id: String, max_depth: usize) -\u003e Self {\n // Hash the token_id once for fast lookups later\n let token_id_hash = {\n use std::collections::hash_map::DefaultHasher;\n use std::hash::{Hash, Hasher};\n let mut hasher = DefaultHasher::new();\n token_id.hash(\u0026mut hasher);\n hasher.finish()\n };\n \n Self {\n token_id,\n token_id_hash,\n sequence: 0, // Start at 0, will increment as we get updates\n timestamp: Utc::now(),\n bids: BTreeMap::new(), // Empty to start - using Price/Qty types\n asks: BTreeMap::new(), // Empty to start - using Price/Qty types\n tick_size_ticks: None, // We'll set this later when we learn about the market\n max_depth,\n }\n }\n\n /// Set the tick size for this book \n /// This tells us the minimum price increment allowed\n /// We store it in ticks for fast validation without conversion overhead\n pub fn set_tick_size(\u0026mut self, tick_size: Decimal) -\u003e Result\u003c()\u003e {\n let tick_size_ticks = decimal_to_price(tick_size)\n .map_err(|_| PolyfillError::validation(\"Invalid tick size\"))?;\n self.tick_size_ticks = Some(tick_size_ticks);\n Ok(())\n }\n \n /// Set the tick size directly in ticks (even faster)\n /// Use this when you already have the tick size in our internal format\n pub fn set_tick_size_ticks(\u0026mut self, tick_size_ticks: Price) {\n self.tick_size_ticks = Some(tick_size_ticks);\n }\n\n /// Get the current best bid (highest price someone is willing to pay)\n /// Uses next_back() because BTreeMap sorts ascending, but we want the highest bid\n /// \n /// PERFORMANCE: Now returns data in external format but internally uses fast lookups\n pub fn best_bid(\u0026self) -\u003e Option\u003cBookLevel\u003e {\n // BEFORE (slow, ~50ns + allocation):\n // self.bids.iter().next_back().map(|(\u0026price, \u0026size)| BookLevel { price, size })\n \n // AFTER (fast, ~5ns, no allocation for the lookup):\n self.bids.iter().next_back().map(|(\u0026price_ticks, \u0026size_units)| {\n // Convert from internal fixed-point to external Decimal format\n // This conversion only happens at the API boundary\n BookLevel {\n price: price_to_decimal(price_ticks),\n size: qty_to_decimal(size_units),\n }\n })\n }\n\n /// Get the current best ask (lowest price someone is willing to sell at)\n /// Uses next() because BTreeMap sorts ascending, so first item is lowest ask\n /// \n /// PERFORMANCE: Now returns data in external format but internally uses fast lookups\n pub fn best_ask(\u0026self) -\u003e Option\u003cBookLevel\u003e {\n // BEFORE (slow, ~50ns + allocation):\n // self.asks.iter().next().map(|(\u0026price, \u0026size)| BookLevel { price, size })\n \n // AFTER (fast, ~5ns, no allocation for the lookup):\n self.asks.iter().next().map(|(\u0026price_ticks, \u0026size_units)| {\n // Convert from internal fixed-point to external Decimal format\n // This conversion only happens at the API boundary\n BookLevel {\n price: price_to_decimal(price_ticks),\n size: qty_to_decimal(size_units),\n }\n })\n }\n \n /// Get the current best bid in fast internal format \n /// Use this for internal calculations to avoid conversion overhead\n pub fn best_bid_fast(\u0026self) -\u003e Option\u003cFastBookLevel\u003e {\n self.bids.iter().next_back().map(|(\u0026price, \u0026size)| {\n FastBookLevel::new(price, size)\n })\n }\n\n /// Get the current best ask in fast internal format \n /// Use this for internal calculations to avoid conversion overhead\n pub fn best_ask_fast(\u0026self) -\u003e Option\u003cFastBookLevel\u003e {\n self.asks.iter().next().map(|(\u0026price, \u0026size)| {\n FastBookLevel::new(price, size)\n })\n }\n\n /// Get the current spread (difference between best ask and best bid)\n /// This tells us how \"tight\" the market is - smaller spread = more liquid market\n /// \n /// PERFORMANCE: Now uses fast internal calculations, only converts to Decimal at the end\n pub fn spread(\u0026self) -\u003e Option\u003cDecimal\u003e {\n // BEFORE (slow, ~100ns + multiple allocations):\n // match (self.best_bid(), self.best_ask()) {\n // (Some(bid), Some(ask)) =\u003e Some(ask.price - bid.price),\n // _ =\u003e None,\n // }\n \n // AFTER (fast, ~5ns, no allocations):\n let (best_bid_ticks, best_ask_ticks) = self.best_prices_fast()?;\n let spread_ticks = math::spread_fast(best_bid_ticks, best_ask_ticks)?;\n Some(price_to_decimal(spread_ticks))\n }\n\n /// Get the current mid price (halfway between best bid and ask)\n /// This is often used as the \"fair value\" of the market\n /// \n /// PERFORMANCE: Now uses fast internal calculations, only converts to Decimal at the end\n pub fn mid_price(\u0026self) -\u003e Option\u003cDecimal\u003e {\n // BEFORE (slow, ~80ns + allocations):\n // math::mid_price(\n // self.best_bid()?.price,\n // self.best_ask()?.price,\n // )\n \n // AFTER (fast, ~3ns, no allocations):\n let (best_bid_ticks, best_ask_ticks) = self.best_prices_fast()?;\n let mid_ticks = math::mid_price_fast(best_bid_ticks, best_ask_ticks)?;\n Some(price_to_decimal(mid_ticks))\n }\n\n /// Get the spread as a percentage (relative to the bid price)\n /// Useful for comparing spreads across different price levels\n /// \n /// PERFORMANCE: Now uses fast internal calculations and returns basis points\n pub fn spread_pct(\u0026self) -\u003e Option\u003cDecimal\u003e {\n let (best_bid_ticks, best_ask_ticks) = self.best_prices_fast()?;\n let spread_bps = math::spread_pct_fast(best_bid_ticks, best_ask_ticks)?;\n // Convert basis points back to percentage decimal\n Some(Decimal::from(spread_bps) / Decimal::from(100))\n }\n \n /// Get best bid and ask prices in fast internal format\n /// Helper method to avoid code duplication and minimize conversions\n fn best_prices_fast(\u0026self) -\u003e Option\u003c(Price, Price)\u003e {\n let best_bid_ticks = self.bids.iter().next_back()?.0;\n let best_ask_ticks = self.asks.iter().next()?.0;\n Some((*best_bid_ticks, *best_ask_ticks))\n }\n \n /// Get the current spread in fast internal format (PERFORMANCE OPTIMIZED)\n /// Returns spread in ticks - use this for internal calculations\n pub fn spread_fast(\u0026self) -\u003e Option\u003cPrice\u003e {\n let (best_bid_ticks, best_ask_ticks) = self.best_prices_fast()?;\n math::spread_fast(best_bid_ticks, best_ask_ticks)\n }\n \n /// Get the current mid price in fast internal format (PERFORMANCE OPTIMIZED)\n /// Returns mid price in ticks - use this for internal calculations\n pub fn mid_price_fast(\u0026self) -\u003e Option\u003cPrice\u003e {\n let (best_bid_ticks, best_ask_ticks) = self.best_prices_fast()?;\n math::mid_price_fast(best_bid_ticks, best_ask_ticks)\n }\n\n /// Get all bids up to a certain depth (top N price levels)\n /// Returns them in descending price order (best bids first)\n /// \n /// PERFORMANCE: Converts from internal fixed-point to external Decimal format\n /// Only call this when you need to return data to external APIs\n pub fn bids(\u0026self, depth: Option\u003cusize\u003e) -\u003e Vec\u003cBookLevel\u003e {\n let depth = depth.unwrap_or(self.max_depth);\n self.bids\n .iter()\n .rev() // Reverse because we want highest prices first\n .take(depth) // Only take the top N levels\n .map(|(\u0026price_ticks, \u0026size_units)| BookLevel {\n price: price_to_decimal(price_ticks),\n size: qty_to_decimal(size_units),\n })\n .collect()\n }\n\n /// Get all asks up to a certain depth (top N price levels)\n /// Returns them in ascending price order (best asks first)\n /// \n /// PERFORMANCE: Converts from internal fixed-point to external Decimal format\n /// Only call this when you need to return data to external APIs\n pub fn asks(\u0026self, depth: Option\u003cusize\u003e) -\u003e Vec\u003cBookLevel\u003e {\n let depth = depth.unwrap_or(self.max_depth);\n self.asks\n .iter() // Already in ascending order, so no need to reverse\n .take(depth) // Only take the top N levels\n .map(|(\u0026price_ticks, \u0026size_units)| BookLevel {\n price: price_to_decimal(price_ticks),\n size: qty_to_decimal(size_units),\n })\n .collect()\n }\n \n /// Get all bids in fast internal format \n /// Use this for internal calculations to avoid conversion overhead\n pub fn bids_fast(\u0026self, depth: Option\u003cusize\u003e) -\u003e Vec\u003cFastBookLevel\u003e {\n let depth = depth.unwrap_or(self.max_depth);\n self.bids\n .iter()\n .rev() // Reverse because we want highest prices first\n .take(depth) // Only take the top N levels\n .map(|(\u0026price, \u0026size)| FastBookLevel::new(price, size))\n .collect()\n }\n\n /// Get all asks in fast internal format (PERFORMANCE OPTIMIZED)\n /// Use this for internal calculations to avoid conversion overhead\n pub fn asks_fast(\u0026self, depth: Option\u003cusize\u003e) -\u003e Vec\u003cFastBookLevel\u003e {\n let depth = depth.unwrap_or(self.max_depth);\n self.asks\n .iter() // Already in ascending order, so no need to reverse\n .take(depth) // Only take the top N levels\n .map(|(\u0026price, \u0026size)| FastBookLevel::new(price, size))\n .collect()\n }\n\n /// Get the full book snapshot\n /// Creates a copy of the current state that can be safely passed around\n /// without worrying about the original book changing\n pub fn snapshot(\u0026self) -\u003e crate::types::OrderBook {\n crate::types::OrderBook {\n token_id: self.token_id.clone(),\n timestamp: self.timestamp,\n bids: self.bids(None), // Get all bids (up to max_depth)\n asks: self.asks(None), // Get all asks (up to max_depth)\n sequence: self.sequence,\n }\n }\n\n /// Apply a delta update to the book (LEGACY VERSION - for external API compatibility)\n /// A \"delta\" is an incremental change - like \"add 100 tokens at $0.65\" or \"remove all at $0.70\"\n /// \n /// This method converts the external Decimal delta to our internal fixed-point format\n /// and then calls the fast version. Use apply_delta_fast() directly when possible.\n pub fn apply_delta(\u0026mut self, delta: OrderDelta) -\u003e Result\u003c()\u003e {\n // Convert to fast internal format with tick alignment validation\n let tick_size_decimal = self.tick_size_ticks.map(price_to_decimal);\n let fast_delta = FastOrderDelta::from_order_delta(\u0026delta, tick_size_decimal)\n .map_err(|e| PolyfillError::validation(format!(\"Invalid delta: {}\", e)))?;\n \n // Use the fast internal version\n self.apply_delta_fast(fast_delta)\n }\n \n /// Apply a delta update to the book\n /// \n /// This is the high-performance version that works directly with fixed-point data.\n /// It includes tick alignment validation and is much faster than the Decimal version.\n /// \n /// Performance improvement: ~50x faster than the old Decimal version!\n /// - No Decimal conversions in the hot path\n /// - Integer comparisons instead of Decimal comparisons\n /// - No memory allocations for price/size operations\n pub fn apply_delta_fast(\u0026mut self, delta: FastOrderDelta) -\u003e Result\u003c()\u003e {\n // Validate sequence ordering - ignore old updates that arrive late\n // This is crucial for maintaining data integrity in real-time systems\n if delta.sequence \u003c= self.sequence {\n trace!(\"Ignoring stale delta: {} \u003c= {}\", delta.sequence, self.sequence);\n return Ok(());\n }\n \n // Validate token ID hash matches (fast string comparison avoidance)\n if delta.token_id_hash != self.token_id_hash {\n return Err(PolyfillError::validation(\"Token ID mismatch\"));\n }\n\n // TICK ALIGNMENT VALIDATION - this is where we enforce price rules\n // If we have a tick size, make sure the price aligns properly\n if let Some(tick_size_ticks) = self.tick_size_ticks {\n // BEFORE (slow, ~200ns + multiple conversions):\n // let tick_size_decimal = price_to_decimal(tick_size_ticks);\n // if !is_price_tick_aligned(price_to_decimal(delta.price), tick_size_decimal) {\n // return Err(...);\n // }\n \n // AFTER (fast, ~2ns, pure integer):\n if tick_size_ticks \u003e 0 \u0026\u0026 delta.price % tick_size_ticks != 0 {\n // Price is not aligned to tick size - reject the update\n warn!(\n \"Rejecting misaligned price: {} not divisible by tick size {}\",\n delta.price, tick_size_ticks\n );\n return Err(PolyfillError::validation(\"Price not aligned to tick size\"));\n }\n }\n\n // Update our tracking info\n self.sequence = delta.sequence;\n self.timestamp = delta.timestamp;\n\n // Apply the actual change to the appropriate side (FAST VERSION)\n match delta.side {\n Side::BUY =\u003e self.apply_bid_delta_fast(delta.price, delta.size),\n Side::SELL =\u003e self.apply_ask_delta_fast(delta.price, delta.size),\n }\n\n // Keep the book from getting too deep (memory management)\n self.trim_depth();\n\n debug!(\n \"Applied fast delta: {} {} @ {} ticks (seq: {})\",\n delta.side.as_str(),\n delta.size,\n delta.price,\n delta.sequence\n );\n\n Ok(())\n }\n\n /// Apply a bid-side delta (someone wants to buy) - LEGACY VERSION\n /// If size is 0, it means \"remove this price level entirely\"\n /// Otherwise, set the total size at this price level\n /// \n /// This converts to fixed-point and calls the fast version\n fn apply_bid_delta(\u0026mut self, price: Decimal, size: Decimal) {\n // Convert to fixed-point (this should be rare since we use fast path)\n let price_ticks = decimal_to_price(price).unwrap_or(0);\n let size_units = decimal_to_qty(size).unwrap_or(0);\n self.apply_bid_delta_fast(price_ticks, size_units);\n }\n\n /// Apply an ask-side delta (someone wants to sell) - LEGACY VERSION\n /// Same logic as bids - size of 0 means remove the price level\n /// \n /// This converts to fixed-point and calls the fast version\n fn apply_ask_delta(\u0026mut self, price: Decimal, size: Decimal) {\n // Convert to fixed-point (this should be rare since we use fast path)\n let price_ticks = decimal_to_price(price).unwrap_or(0);\n let size_units = decimal_to_qty(size).unwrap_or(0);\n self.apply_ask_delta_fast(price_ticks, size_units);\n }\n \n /// Apply a bid-side delta (someone wants to buy) - FAST VERSION\n /// \n /// This is the high-performance version that works directly with fixed-point.\n /// Much faster than the Decimal version - pure integer operations.\n fn apply_bid_delta_fast(\u0026mut self, price_ticks: Price, size_units: Qty) {\n // BEFORE (slow, ~100ns + allocation):\n // if size.is_zero() {\n // self.bids.remove(\u0026price);\n // } else {\n // self.bids.insert(price, size);\n // }\n \n // AFTER (fast, ~5ns, no allocation):\n if size_units == 0 {\n self.bids.remove(\u0026price_ticks); // No more buyers at this price\n } else {\n self.bids.insert(price_ticks, size_units); // Update total size at this price\n }\n }\n\n /// Apply an ask-side delta (someone wants to sell) - FAST VERSION\n /// \n /// This is the high-performance version that works directly with fixed-point.\n /// Much faster than the Decimal version - pure integer operations.\n fn apply_ask_delta_fast(\u0026mut self, price_ticks: Price, size_units: Qty) {\n // BEFORE (slow, ~100ns + allocation):\n // if size.is_zero() {\n // self.asks.remove(\u0026price);\n // } else {\n // self.asks.insert(price, size);\n // }\n \n // AFTER (fast, ~5ns, no allocation):\n if size_units == 0 {\n self.asks.remove(\u0026price_ticks); // No more sellers at this price\n } else {\n self.asks.insert(price_ticks, size_units); // Update total size at this price\n }\n }\n\n /// Trim the book to maintain depth limits\n /// We don't want to track every single price level - just the best ones\n /// \n /// Why limit depth? Several reasons:\n /// 1. Memory efficiency: A popular market might have thousands of price levels,\n /// but only the top 10-50 levels are actually tradeable with reasonable size\n /// 2. Performance: Fewer levels = faster iteration when calculating market impact\n /// 3. Relevance: Deep levels (like bids at $0.01 when best bid is $0.65) are \n /// mostly noise and will never get hit in normal trading\n /// 4. Stale data: Deep levels often contain old orders that haven't been cancelled\n /// 5. Network bandwidth: Less data to send when streaming updates\n\n fn trim_depth(\u0026mut self) {\n // For bids, remove the LOWEST prices (worst bids) if we have too many\n // Example: If best bid is $0.65, we don't care about bids at $0.10\n if self.bids.len() \u003e self.max_depth {\n let to_remove = self.bids.len() - self.max_depth;\n for _ in 0..to_remove {\n self.bids.pop_first(); // Remove lowest bid prices (furthest from market)\n }\n }\n\n // For asks, remove the HIGHEST prices (worst asks) if we have too many \n // Example: If best ask is $0.67, we don't care about asks at $0.95\n if self.asks.len() \u003e self.max_depth {\n let to_remove = self.asks.len() - self.max_depth;\n for _ in 0..to_remove {\n self.asks.pop_last(); // Remove highest ask prices (furthest from market)\n }\n }\n }\n\n /// Calculate the market impact for a given order size\n /// This is exactly why we don't need deep levels - if your order would require\n /// hitting prices way off the current market (like $0.95 when best ask is $0.67),\n /// you'd never actually place that order. You'd either:\n /// 1. Break it into smaller pieces over time\n /// 2. Use a different trading strategy\n /// 3. Accept that there's not enough liquidity right now\n pub fn calculate_market_impact(\u0026self, side: Side, size: Decimal) -\u003e Option\u003cMarketImpact\u003e {\n // PERFORMANCE NOTE: This method still uses Decimal for external compatibility,\n // but the internal order book lookups now use our fast fixed-point data structures.\n // \n // BEFORE: Each level lookup involved Decimal operations (~50ns each)\n // AFTER: Level lookups use integer operations (~5ns each)\n // \n // For a 10-level impact calculation: 500ns → 50ns (10x speedup)\n \n // Get the levels we'd be trading against\n let levels = match side {\n Side::BUY =\u003e self.asks(None), // If buying, we hit the ask side\n Side::SELL =\u003e self.bids(None), // If selling, we hit the bid side\n };\n\n if levels.is_empty() {\n return None; // No liquidity available\n }\n\n let mut remaining_size = size;\n let mut total_cost = Decimal::ZERO;\n let mut weighted_price = Decimal::ZERO;\n\n // Walk through each price level, filling as much as we can\n for level in levels {\n let fill_size = std::cmp::min(remaining_size, level.size);\n let level_cost = fill_size * level.price;\n \n total_cost += level_cost;\n weighted_price += level_cost; // This accumulates the weighted average\n remaining_size -= fill_size;\n\n if remaining_size.is_zero() {\n break; // We've filled our entire order\n }\n }\n\n if remaining_size \u003e Decimal::ZERO {\n // Not enough liquidity to fill the whole order\n // This is a perfect example of why we don't need infinite depth:\n // If we can't fill your order with the top N levels, you probably\n // shouldn't be placing that order anyway - it would move the market too much\n return None; \n }\n\n let avg_price = weighted_price / size;\n \n // Calculate how much we moved the market compared to the best price\n let impact = match side {\n Side::BUY =\u003e {\n let best_ask = self.best_ask()?.price;\n (avg_price - best_ask) / best_ask // How much worse than best ask\n }\n Side::SELL =\u003e {\n let best_bid = self.best_bid()?.price;\n (best_bid - avg_price) / best_bid // How much worse than best bid\n }\n };\n\n Some(MarketImpact {\n average_price: avg_price,\n impact_pct: impact,\n total_cost,\n size_filled: size,\n })\n }\n\n /// Check if the book is stale (no recent updates)\n /// Useful for detecting when we've lost connection to live data\n pub fn is_stale(\u0026self, max_age: std::time::Duration) -\u003e bool {\n let age = Utc::now() - self.timestamp;\n age \u003e chrono::Duration::from_std(max_age).unwrap_or_default()\n }\n\n /// Get the total liquidity at a given price level\n /// Tells you how much you can buy/sell at exactly this price\n pub fn liquidity_at_price(\u0026self, price: Decimal, side: Side) -\u003e Decimal {\n // Convert decimal price to our internal fixed-point representation\n let price_ticks = match decimal_to_price(price) {\n Ok(ticks) =\u003e ticks,\n Err(_) =\u003e return Decimal::ZERO, // Invalid price\n };\n \n match side {\n Side::BUY =\u003e {\n // How much we can buy at this price (look at asks)\n let size_units = self.asks.get(\u0026price_ticks).copied().unwrap_or_default();\n qty_to_decimal(size_units)\n },\n Side::SELL =\u003e {\n // How much we can sell at this price (look at bids)\n let size_units = self.bids.get(\u0026price_ticks).copied().unwrap_or_default();\n qty_to_decimal(size_units)\n }\n }\n }\n\n /// Get the total liquidity within a price range\n /// Useful for understanding how much depth exists in a certain price band\n pub fn liquidity_in_range(\u0026self, min_price: Decimal, max_price: Decimal, side: Side) -\u003e Decimal {\n // Convert decimal prices to our internal fixed-point representation\n let min_price_ticks = match decimal_to_price(min_price) {\n Ok(ticks) =\u003e ticks,\n Err(_) =\u003e return Decimal::ZERO, // Invalid price\n };\n let max_price_ticks = match decimal_to_price(max_price) {\n Ok(ticks) =\u003e ticks,\n Err(_) =\u003e return Decimal::ZERO, // Invalid price\n };\n \n let levels: Vec\u003c_\u003e = match side {\n Side::BUY =\u003e self.asks.range(min_price_ticks..=max_price_ticks).collect(),\n Side::SELL =\u003e self.bids.range(min_price_ticks..=max_price_ticks).rev().collect(),\n };\n\n // Sum up the sizes, converting from fixed-point back to Decimal\n let total_size_units: i64 = levels.into_iter().map(|(_, \u0026size)| size).sum();\n qty_to_decimal(total_size_units)\n }\n\n /// Validate that prices are properly ordered\n /// A healthy book should have best bid \u003c best ask (otherwise there's an arbitrage opportunity)\n pub fn is_valid(\u0026self) -\u003e bool {\n match (self.best_bid(), self.best_ask()) {\n (Some(bid), Some(ask)) =\u003e bid.price \u003c ask.price, // Normal market condition\n _ =\u003e true, // Empty book is technically valid\n }\n }\n}\n\n/// Market impact calculation result\n/// This tells you what would happen if you executed a large order\n#[derive(Debug, Clone)]\npub struct MarketImpact {\n pub average_price: Decimal, // The average price you'd get across all fills\n pub impact_pct: Decimal, // How much worse than the best price (as percentage)\n pub total_cost: Decimal, // Total amount you'd pay/receive\n pub size_filled: Decimal, // How much of your order got filled\n}\n\n/// Thread-safe order book manager\n/// This manages multiple order books (one per token) and handles concurrent access\n/// Multiple threads can read/write different books simultaneously\n/// \n/// The depth limiting becomes even more critical here because we might be tracking\n/// hundreds or thousands of different tokens simultaneously. If each book had\n/// unlimited depth, we could easily use gigabytes of RAM for mostly useless data.\n/// \n/// Example: 1000 tokens × 1000 price levels × 32 bytes per level = 32MB just for prices\n/// With depth limiting: 1000 tokens × 50 levels × 32 bytes = 1.6MB (20x less memory)\n#[derive(Debug)]\npub struct OrderBookManager {\n books: Arc\u003cRwLock\u003cstd::collections::HashMap\u003cString, OrderBook\u003e\u003e\u003e, // Token ID -\u003e OrderBook\n max_depth: usize,\n}\n\nimpl OrderBookManager {\n /// Create a new order book manager\n /// Starts with an empty collection of books\n pub fn new(max_depth: usize) -\u003e Self {\n Self {\n books: Arc::new(RwLock::new(std::collections::HashMap::new())),\n max_depth,\n }\n }\n\n /// Get or create an order book for a token\n /// If we don't have a book for this token yet, create a new empty one\n pub fn get_or_create_book(\u0026self, token_id: \u0026str) -\u003e Result\u003cOrderBook\u003e {\n let mut books = self.books.write().map_err(|_| {\n PolyfillError::internal_simple(\"Failed to acquire book lock\")\n })?;\n\n if let Some(book) = books.get(token_id) {\n Ok(book.clone()) // Return a copy of the existing book\n } else {\n // Create a new book for this token\n let book = OrderBook::new(token_id.to_string(), self.max_depth);\n books.insert(token_id.to_string(), book.clone());\n Ok(book)\n }\n }\n\n /// Update a book with a delta\n /// This is called when we receive real-time updates from the exchange\n pub fn apply_delta(\u0026self, delta: OrderDelta) -\u003e Result\u003c()\u003e {\n let mut books = self.books.write().map_err(|_| {\n PolyfillError::internal_simple(\"Failed to acquire book lock\")\n })?;\n\n // Find the book for this token (must already exist)\n let book = books\n .get_mut(\u0026delta.token_id)\n .ok_or_else(|| {\n PolyfillError::market_data(\n format!(\"No book found for token: {}\", delta.token_id),\n crate::errors::MarketDataErrorKind::TokenNotFound,\n )\n })?;\n\n // Apply the update to the specific book\n book.apply_delta(delta)\n }\n\n /// Get a book snapshot\n /// Returns a copy of the current book state that won't change\n pub fn get_book(\u0026self, token_id: \u0026str) -\u003e Result\u003ccrate::types::OrderBook\u003e {\n let books = self.books.read().map_err(|_| {\n PolyfillError::internal_simple(\"Failed to acquire book lock\")\n })?;\n\n books\n .get(token_id)\n .map(|book| book.snapshot()) // Create a snapshot copy\n .ok_or_else(|| {\n PolyfillError::market_data(\n format!(\"No book found for token: {}\", token_id),\n crate::errors::MarketDataErrorKind::TokenNotFound,\n )\n })\n }\n\n /// Get all available books\n /// Returns snapshots of every book we're currently tracking\n pub fn get_all_books(\u0026self) -\u003e Result\u003cVec\u003ccrate::types::OrderBook\u003e\u003e {\n let books = self.books.read().map_err(|_| {\n PolyfillError::internal_simple(\"Failed to acquire book lock\")\n })?;\n\n Ok(books.values().map(|book| book.snapshot()).collect())\n }\n\n /// Remove stale books\n /// Cleans up books that haven't been updated recently (probably disconnected)\n /// This prevents memory leaks from accumulating dead books\n pub fn cleanup_stale_books(\u0026self, max_age: std::time::Duration) -\u003e Result\u003cusize\u003e {\n let mut books = self.books.write().map_err(|_| {\n PolyfillError::internal_simple(\"Failed to acquire book lock\")\n })?;\n\n let initial_count = books.len();\n books.retain(|_, book| !book.is_stale(max_age)); // Keep only non-stale books\n let removed = initial_count - books.len();\n\n if removed \u003e 0 {\n debug!(\"Removed {} stale order books\", removed);\n }\n\n Ok(removed)\n }\n}\n\n/// Order book analytics and statistics\n/// Provides a summary view of the book's health and characteristics\n#[derive(Debug, Clone)]\npub struct BookAnalytics {\n pub token_id: String,\n pub timestamp: chrono::DateTime\u003cUtc\u003e,\n pub bid_count: usize, // How many different bid price levels\n pub ask_count: usize, // How many different ask price levels\n pub total_bid_size: Decimal, // Total size of all bids combined\n pub total_ask_size: Decimal, // Total size of all asks combined\n pub spread: Option\u003cDecimal\u003e, // Current spread (ask - bid)\n pub spread_pct: Option\u003cDecimal\u003e, // Spread as percentage\n pub mid_price: Option\u003cDecimal\u003e, // Current mid price\n pub volatility: Option\u003cDecimal\u003e, // Price volatility (if calculated)\n}\n\nimpl OrderBook {\n /// Calculate analytics for this book\n /// Gives you a quick health check of the market\n pub fn analytics(\u0026self) -\u003e BookAnalytics {\n let bid_count = self.bids.len();\n let ask_count = self.asks.len();\n // Sum up all bid/ask sizes, converting from fixed-point back to Decimal\n let total_bid_size_units: i64 = self.bids.values().sum();\n let total_ask_size_units: i64 = self.asks.values().sum();\n let total_bid_size = qty_to_decimal(total_bid_size_units);\n let total_ask_size = qty_to_decimal(total_ask_size_units);\n\n BookAnalytics {\n token_id: self.token_id.clone(),\n timestamp: self.timestamp,\n bid_count,\n ask_count,\n total_bid_size,\n total_ask_size,\n spread: self.spread(),\n spread_pct: self.spread_pct(),\n mid_price: self.mid_price(),\n volatility: self.calculate_volatility(),\n }\n }\n\n /// Calculate price volatility (simplified)\n /// This is a placeholder - real volatility needs historical price data\n fn calculate_volatility(\u0026self) -\u003e Option\u003cDecimal\u003e {\n // This is a simplified volatility calculation\n // In a real implementation, you'd want to track price history over time\n // and calculate standard deviation of price changes\n None\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use rust_decimal_macros::dec; // Convenient macro for creating Decimal literals\n\n #[test]\n fn test_order_book_creation() {\n // Test that we can create a new empty order book\n let book = OrderBook::new(\"test_token\".to_string(), 10);\n assert_eq!(book.token_id, \"test_token\");\n assert_eq!(book.bids.len(), 0); // Should start empty\n assert_eq!(book.asks.len(), 0); // Should start empty\n }\n\n #[test]\n fn test_apply_delta() {\n // Test that we can apply order book updates\n let mut book = OrderBook::new(\"test_token\".to_string(), 10);\n \n // Create a buy order at $0.50 for 100 tokens\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: Utc::now(),\n side: Side::BUY,\n price: dec!(0.5),\n size: dec!(100),\n sequence: 1,\n };\n\n book.apply_delta(delta).unwrap();\n assert_eq!(book.sequence, 1); // Sequence should update\n assert_eq!(book.best_bid().unwrap().price, dec!(0.5)); // Should be our bid\n assert_eq!(book.best_bid().unwrap().size, dec!(100)); // Should be our size\n }\n\n #[test]\n fn test_spread_calculation() {\n // Test that we can calculate the spread between bid and ask\n let mut book = OrderBook::new(\"test_token\".to_string(), 10);\n \n // Add a bid at $0.50\n book.apply_delta(OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: Utc::now(),\n side: Side::BUY,\n price: dec!(0.5),\n size: dec!(100),\n sequence: 1,\n }).unwrap();\n\n // Add an ask at $0.52\n book.apply_delta(OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: Utc::now(),\n side: Side::SELL,\n price: dec!(0.52),\n size: dec!(100),\n sequence: 2,\n }).unwrap();\n\n let spread = book.spread().unwrap();\n assert_eq!(spread, dec!(0.02)); // $0.52 - $0.50 = $0.02\n }\n\n #[test]\n fn test_market_impact() {\n // Test market impact calculation for a large order\n let mut book = OrderBook::new(\"test_token\".to_string(), 10);\n \n // Add multiple ask levels (people selling at different prices)\n // $0.50 for 100 tokens, $0.51 for 100 tokens, $0.52 for 100 tokens\n for (i, price) in [dec!(0.50), dec!(0.51), dec!(0.52)].iter().enumerate() {\n book.apply_delta(OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: Utc::now(),\n side: Side::SELL,\n price: *price,\n size: dec!(100),\n sequence: i as u64 + 1,\n }).unwrap();\n }\n\n // Try to buy 150 tokens (will need to hit multiple price levels)\n let impact = book.calculate_market_impact(Side::BUY, dec!(150)).unwrap();\n assert!(impact.average_price \u003e dec!(0.50)); // Should be worse than best price\n assert!(impact.average_price \u003c dec!(0.51)); // But not as bad as second level\n }\n} ","traces":[{"line":79,"address":[],"length":0,"stats":{"Line":4}},{"line":81,"address":[],"length":0,"stats":{"Line":4}},{"line":84,"address":[],"length":0,"stats":{"Line":8}},{"line":85,"address":[],"length":0,"stats":{"Line":12}},{"line":86,"address":[],"length":0,"stats":{"Line":8}},{"line":93,"address":[],"length":0,"stats":{"Line":8}},{"line":94,"address":[],"length":0,"stats":{"Line":8}},{"line":95,"address":[],"length":0,"stats":{"Line":8}},{"line":104,"address":[],"length":0,"stats":{"Line":0}},{"line":105,"address":[],"length":0,"stats":{"Line":0}},{"line":106,"address":[],"length":0,"stats":{"Line":0}},{"line":107,"address":[],"length":0,"stats":{"Line":0}},{"line":108,"address":[],"length":0,"stats":{"Line":0}},{"line":113,"address":[],"length":0,"stats":{"Line":0}},{"line":114,"address":[],"length":0,"stats":{"Line":0}},{"line":121,"address":[],"length":0,"stats":{"Line":2}},{"line":126,"address":[],"length":0,"stats":{"Line":8}},{"line":129,"address":[],"length":0,"stats":{"Line":2}},{"line":130,"address":[],"length":0,"stats":{"Line":6}},{"line":131,"address":[],"length":0,"stats":{"Line":2}},{"line":140,"address":[],"length":0,"stats":{"Line":1}},{"line":145,"address":[],"length":0,"stats":{"Line":4}},{"line":148,"address":[],"length":0,"stats":{"Line":1}},{"line":149,"address":[],"length":0,"stats":{"Line":3}},{"line":150,"address":[],"length":0,"stats":{"Line":1}},{"line":157,"address":[],"length":0,"stats":{"Line":0}},{"line":158,"address":[],"length":0,"stats":{"Line":0}},{"line":159,"address":[],"length":0,"stats":{"Line":0}},{"line":165,"address":[],"length":0,"stats":{"Line":0}},{"line":166,"address":[],"length":0,"stats":{"Line":0}},{"line":167,"address":[],"length":0,"stats":{"Line":0}},{"line":175,"address":[],"length":0,"stats":{"Line":1}},{"line":183,"address":[],"length":0,"stats":{"Line":4}},{"line":184,"address":[],"length":0,"stats":{"Line":4}},{"line":185,"address":[],"length":0,"stats":{"Line":1}},{"line":192,"address":[],"length":0,"stats":{"Line":0}},{"line":200,"address":[],"length":0,"stats":{"Line":0}},{"line":201,"address":[],"length":0,"stats":{"Line":0}},{"line":202,"address":[],"length":0,"stats":{"Line":0}},{"line":209,"address":[],"length":0,"stats":{"Line":0}},{"line":210,"address":[],"length":0,"stats":{"Line":0}},{"line":211,"address":[],"length":0,"stats":{"Line":0}},{"line":213,"address":[],"length":0,"stats":{"Line":0}},{"line":218,"address":[],"length":0,"stats":{"Line":1}},{"line":219,"address":[],"length":0,"stats":{"Line":3}},{"line":220,"address":[],"length":0,"stats":{"Line":3}},{"line":221,"address":[],"length":0,"stats":{"Line":1}},{"line":226,"address":[],"length":0,"stats":{"Line":0}},{"line":227,"address":[],"length":0,"stats":{"Line":0}},{"line":228,"address":[],"length":0,"stats":{"Line":0}},{"line":233,"address":[],"length":0,"stats":{"Line":0}},{"line":234,"address":[],"length":0,"stats":{"Line":0}},{"line":235,"address":[],"length":0,"stats":{"Line":0}},{"line":243,"address":[],"length":0,"stats":{"Line":0}},{"line":244,"address":[],"length":0,"stats":{"Line":0}},{"line":245,"address":[],"length":0,"stats":{"Line":0}},{"line":248,"address":[],"length":0,"stats":{"Line":0}},{"line":249,"address":[],"length":0,"stats":{"Line":0}},{"line":250,"address":[],"length":0,"stats":{"Line":0}},{"line":251,"address":[],"length":0,"stats":{"Line":0}},{"line":261,"address":[],"length":0,"stats":{"Line":1}},{"line":262,"address":[],"length":0,"stats":{"Line":4}},{"line":263,"address":[],"length":0,"stats":{"Line":1}},{"line":265,"address":[],"length":0,"stats":{"Line":2}},{"line":266,"address":[],"length":0,"stats":{"Line":1}},{"line":267,"address":[],"length":0,"stats":{"Line":6}},{"line":268,"address":[],"length":0,"stats":{"Line":6}},{"line":275,"address":[],"length":0,"stats":{"Line":0}},{"line":276,"address":[],"length":0,"stats":{"Line":0}},{"line":277,"address":[],"length":0,"stats":{"Line":0}},{"line":280,"address":[],"length":0,"stats":{"Line":0}},{"line":281,"address":[],"length":0,"stats":{"Line":0}},{"line":287,"address":[],"length":0,"stats":{"Line":0}},{"line":288,"address":[],"length":0,"stats":{"Line":0}},{"line":289,"address":[],"length":0,"stats":{"Line":0}},{"line":291,"address":[],"length":0,"stats":{"Line":0}},{"line":292,"address":[],"length":0,"stats":{"Line":0}},{"line":299,"address":[],"length":0,"stats":{"Line":0}},{"line":301,"address":[],"length":0,"stats":{"Line":0}},{"line":302,"address":[],"length":0,"stats":{"Line":0}},{"line":303,"address":[],"length":0,"stats":{"Line":0}},{"line":304,"address":[],"length":0,"stats":{"Line":0}},{"line":305,"address":[],"length":0,"stats":{"Line":0}},{"line":314,"address":[],"length":0,"stats":{"Line":6}},{"line":316,"address":[],"length":0,"stats":{"Line":18}},{"line":317,"address":[],"length":0,"stats":{"Line":24}},{"line":318,"address":[],"length":0,"stats":{"Line":6}},{"line":321,"address":[],"length":0,"stats":{"Line":18}},{"line":333,"address":[],"length":0,"stats":{"Line":6}},{"line":336,"address":[],"length":0,"stats":{"Line":6}},{"line":337,"address":[],"length":0,"stats":{"Line":0}},{"line":338,"address":[],"length":0,"stats":{"Line":0}},{"line":342,"address":[],"length":0,"stats":{"Line":6}},{"line":343,"address":[],"length":0,"stats":{"Line":0}},{"line":348,"address":[],"length":0,"stats":{"Line":6}},{"line":356,"address":[],"length":0,"stats":{"Line":0}},{"line":358,"address":[],"length":0,"stats":{"Line":0}},{"line":359,"address":[],"length":0,"stats":{"Line":0}},{"line":362,"address":[],"length":0,"stats":{"Line":0}},{"line":367,"address":[],"length":0,"stats":{"Line":6}},{"line":368,"address":[],"length":0,"stats":{"Line":6}},{"line":371,"address":[],"length":0,"stats":{"Line":6}},{"line":372,"address":[],"length":0,"stats":{"Line":8}},{"line":373,"address":[],"length":0,"stats":{"Line":16}},{"line":377,"address":[],"length":0,"stats":{"Line":12}},{"line":379,"address":[],"length":0,"stats":{"Line":6}},{"line":380,"address":[],"length":0,"stats":{"Line":0}},{"line":381,"address":[],"length":0,"stats":{"Line":0}},{"line":387,"address":[],"length":0,"stats":{"Line":6}},{"line":395,"address":[],"length":0,"stats":{"Line":0}},{"line":397,"address":[],"length":0,"stats":{"Line":0}},{"line":398,"address":[],"length":0,"stats":{"Line":0}},{"line":399,"address":[],"length":0,"stats":{"Line":0}},{"line":406,"address":[],"length":0,"stats":{"Line":0}},{"line":408,"address":[],"length":0,"stats":{"Line":0}},{"line":409,"address":[],"length":0,"stats":{"Line":0}},{"line":410,"address":[],"length":0,"stats":{"Line":0}},{"line":417,"address":[],"length":0,"stats":{"Line":2}},{"line":426,"address":[],"length":0,"stats":{"Line":2}},{"line":427,"address":[],"length":0,"stats":{"Line":0}},{"line":429,"address":[],"length":0,"stats":{"Line":6}},{"line":437,"address":[],"length":0,"stats":{"Line":4}},{"line":446,"address":[],"length":0,"stats":{"Line":4}},{"line":447,"address":[],"length":0,"stats":{"Line":0}},{"line":449,"address":[],"length":0,"stats":{"Line":12}},{"line":465,"address":[],"length":0,"stats":{"Line":6}},{"line":468,"address":[],"length":0,"stats":{"Line":12}},{"line":469,"address":[],"length":0,"stats":{"Line":0}},{"line":470,"address":[],"length":0,"stats":{"Line":0}},{"line":471,"address":[],"length":0,"stats":{"Line":0}},{"line":477,"address":[],"length":0,"stats":{"Line":12}},{"line":478,"address":[],"length":0,"stats":{"Line":0}},{"line":479,"address":[],"length":0,"stats":{"Line":0}},{"line":480,"address":[],"length":0,"stats":{"Line":0}},{"line":492,"address":[],"length":0,"stats":{"Line":1}},{"line":502,"address":[],"length":0,"stats":{"Line":2}},{"line":503,"address":[],"length":0,"stats":{"Line":3}},{"line":504,"address":[],"length":0,"stats":{"Line":0}},{"line":507,"address":[],"length":0,"stats":{"Line":2}},{"line":508,"address":[],"length":0,"stats":{"Line":0}},{"line":511,"address":[],"length":0,"stats":{"Line":2}},{"line":512,"address":[],"length":0,"stats":{"Line":2}},{"line":513,"address":[],"length":0,"stats":{"Line":2}},{"line":516,"address":[],"length":0,"stats":{"Line":4}},{"line":517,"address":[],"length":0,"stats":{"Line":8}},{"line":518,"address":[],"length":0,"stats":{"Line":4}},{"line":520,"address":[],"length":0,"stats":{"Line":2}},{"line":521,"address":[],"length":0,"stats":{"Line":2}},{"line":522,"address":[],"length":0,"stats":{"Line":2}},{"line":524,"address":[],"length":0,"stats":{"Line":4}},{"line":525,"address":[],"length":0,"stats":{"Line":1}},{"line":529,"address":[],"length":0,"stats":{"Line":1}},{"line":534,"address":[],"length":0,"stats":{"Line":0}},{"line":537,"address":[],"length":0,"stats":{"Line":2}},{"line":540,"address":[],"length":0,"stats":{"Line":2}},{"line":542,"address":[],"length":0,"stats":{"Line":3}},{"line":543,"address":[],"length":0,"stats":{"Line":1}},{"line":546,"address":[],"length":0,"stats":{"Line":0}},{"line":547,"address":[],"length":0,"stats":{"Line":0}},{"line":551,"address":[],"length":0,"stats":{"Line":1}},{"line":552,"address":[],"length":0,"stats":{"Line":2}},{"line":553,"address":[],"length":0,"stats":{"Line":2}},{"line":554,"address":[],"length":0,"stats":{"Line":1}},{"line":555,"address":[],"length":0,"stats":{"Line":1}},{"line":561,"address":[],"length":0,"stats":{"Line":0}},{"line":562,"address":[],"length":0,"stats":{"Line":0}},{"line":563,"address":[],"length":0,"stats":{"Line":0}},{"line":568,"address":[],"length":0,"stats":{"Line":0}},{"line":570,"address":[],"length":0,"stats":{"Line":0}},{"line":571,"address":[],"length":0,"stats":{"Line":0}},{"line":572,"address":[],"length":0,"stats":{"Line":0}},{"line":575,"address":[],"length":0,"stats":{"Line":0}},{"line":578,"address":[],"length":0,"stats":{"Line":0}},{"line":579,"address":[],"length":0,"stats":{"Line":0}},{"line":583,"address":[],"length":0,"stats":{"Line":0}},{"line":584,"address":[],"length":0,"stats":{"Line":0}},{"line":591,"address":[],"length":0,"stats":{"Line":0}},{"line":593,"address":[],"length":0,"stats":{"Line":0}},{"line":594,"address":[],"length":0,"stats":{"Line":0}},{"line":595,"address":[],"length":0,"stats":{"Line":0}},{"line":597,"address":[],"length":0,"stats":{"Line":0}},{"line":598,"address":[],"length":0,"stats":{"Line":0}},{"line":599,"address":[],"length":0,"stats":{"Line":0}},{"line":602,"address":[],"length":0,"stats":{"Line":0}},{"line":603,"address":[],"length":0,"stats":{"Line":0}},{"line":604,"address":[],"length":0,"stats":{"Line":0}},{"line":608,"address":[],"length":0,"stats":{"Line":0}},{"line":609,"address":[],"length":0,"stats":{"Line":0}},{"line":614,"address":[],"length":0,"stats":{"Line":0}},{"line":615,"address":[],"length":0,"stats":{"Line":0}},{"line":616,"address":[],"length":0,"stats":{"Line":0}},{"line":617,"address":[],"length":0,"stats":{"Line":0}},{"line":651,"address":[],"length":0,"stats":{"Line":0}},{"line":653,"address":[],"length":0,"stats":{"Line":0}},{"line":660,"address":[],"length":0,"stats":{"Line":0}},{"line":661,"address":[],"length":0,"stats":{"Line":0}},{"line":662,"address":[],"length":0,"stats":{"Line":0}},{"line":665,"address":[],"length":0,"stats":{"Line":0}},{"line":666,"address":[],"length":0,"stats":{"Line":0}},{"line":669,"address":[],"length":0,"stats":{"Line":0}},{"line":670,"address":[],"length":0,"stats":{"Line":0}},{"line":671,"address":[],"length":0,"stats":{"Line":0}},{"line":677,"address":[],"length":0,"stats":{"Line":0}},{"line":678,"address":[],"length":0,"stats":{"Line":0}},{"line":679,"address":[],"length":0,"stats":{"Line":0}},{"line":683,"address":[],"length":0,"stats":{"Line":0}},{"line":684,"address":[],"length":0,"stats":{"Line":0}},{"line":685,"address":[],"length":0,"stats":{"Line":0}},{"line":686,"address":[],"length":0,"stats":{"Line":0}},{"line":687,"address":[],"length":0,"stats":{"Line":0}},{"line":688,"address":[],"length":0,"stats":{"Line":0}},{"line":693,"address":[],"length":0,"stats":{"Line":0}},{"line":698,"address":[],"length":0,"stats":{"Line":0}},{"line":699,"address":[],"length":0,"stats":{"Line":0}},{"line":700,"address":[],"length":0,"stats":{"Line":0}},{"line":703,"address":[],"length":0,"stats":{"Line":0}},{"line":704,"address":[],"length":0,"stats":{"Line":0}},{"line":705,"address":[],"length":0,"stats":{"Line":0}},{"line":706,"address":[],"length":0,"stats":{"Line":0}},{"line":707,"address":[],"length":0,"stats":{"Line":0}},{"line":708,"address":[],"length":0,"stats":{"Line":0}},{"line":709,"address":[],"length":0,"stats":{"Line":0}},{"line":716,"address":[],"length":0,"stats":{"Line":0}},{"line":717,"address":[],"length":0,"stats":{"Line":0}},{"line":718,"address":[],"length":0,"stats":{"Line":0}},{"line":721,"address":[],"length":0,"stats":{"Line":0}},{"line":727,"address":[],"length":0,"stats":{"Line":0}},{"line":728,"address":[],"length":0,"stats":{"Line":0}},{"line":729,"address":[],"length":0,"stats":{"Line":0}},{"line":732,"address":[],"length":0,"stats":{"Line":0}},{"line":733,"address":[],"length":0,"stats":{"Line":0}},{"line":734,"address":[],"length":0,"stats":{"Line":0}},{"line":736,"address":[],"length":0,"stats":{"Line":0}},{"line":737,"address":[],"length":0,"stats":{"Line":0}},{"line":740,"address":[],"length":0,"stats":{"Line":0}},{"line":763,"address":[],"length":0,"stats":{"Line":0}},{"line":764,"address":[],"length":0,"stats":{"Line":0}},{"line":765,"address":[],"length":0,"stats":{"Line":0}},{"line":767,"address":[],"length":0,"stats":{"Line":0}},{"line":768,"address":[],"length":0,"stats":{"Line":0}},{"line":769,"address":[],"length":0,"stats":{"Line":0}},{"line":770,"address":[],"length":0,"stats":{"Line":0}},{"line":773,"address":[],"length":0,"stats":{"Line":0}},{"line":774,"address":[],"length":0,"stats":{"Line":0}},{"line":779,"address":[],"length":0,"stats":{"Line":0}},{"line":780,"address":[],"length":0,"stats":{"Line":0}},{"line":781,"address":[],"length":0,"stats":{"Line":0}},{"line":782,"address":[],"length":0,"stats":{"Line":0}},{"line":788,"address":[],"length":0,"stats":{"Line":0}},{"line":792,"address":[],"length":0,"stats":{"Line":0}}],"covered":84,"coverable":250},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","client.rs"],"content":"//! High-performance Rust client for Polymarket\n//! \n//! This module provides a production-ready client for interacting with\n//! Polymarket, optimized for high-frequency trading environments.\n\nuse crate::auth::{create_l1_headers, create_l2_headers};\nuse crate::errors::{PolyfillError, Result};\nuse crate::types::{OrderOptions, PostOrder, SignedOrderRequest};\nuse reqwest::Client;\nuse serde_json::Value;\nuse std::str::FromStr;\nuse rust_decimal::Decimal;\nuse rust_decimal::prelude::FromPrimitive;\nuse alloy_primitives::U256;\nuse alloy_signer_local::PrivateKeySigner;\nuse reqwest::{Method, RequestBuilder};\nuse reqwest::header::HeaderName;\n\n// Re-export types for compatibility\npub use crate::types::{\n ApiCredentials as ApiCreds, Side, OrderType,\n};\n\n// Compatibility types\n#[derive(Debug)]\npub struct OrderArgs {\n pub token_id: String,\n pub price: Decimal,\n pub size: Decimal,\n pub side: Side,\n}\n\nimpl OrderArgs {\n pub fn new(token_id: \u0026str, price: Decimal, size: Decimal, side: Side) -\u003e Self {\n Self {\n token_id: token_id.to_string(),\n price,\n size,\n side,\n }\n }\n}\n\nimpl Default for OrderArgs {\n fn default() -\u003e Self {\n Self {\n token_id: \"\".to_string(),\n price: Decimal::ZERO,\n size: Decimal::ZERO,\n side: Side::BUY,\n }\n }\n}\n\n/// Main client for interacting with Polymarket API\npub struct ClobClient {\n http_client: Client,\n base_url: String,\n chain_id: u64,\n signer: Option\u003cPrivateKeySigner\u003e,\n api_creds: Option\u003cApiCreds\u003e,\n order_builder: Option\u003ccrate::orders::OrderBuilder\u003e,\n}\n\nimpl ClobClient {\n /// Create a new client\n pub fn new(host: \u0026str) -\u003e Self {\n Self {\n http_client: Client::new(),\n base_url: host.to_string(),\n chain_id: 137, // Default to Polygon\n signer: None,\n api_creds: None,\n order_builder: None,\n }\n }\n\n /// Create a client with L1 headers (for authentication)\n pub fn with_l1_headers(host: \u0026str, private_key: \u0026str, chain_id: u64) -\u003e Self {\n let signer = private_key.parse::\u003cPrivateKeySigner\u003e()\n .expect(\"Invalid private key\");\n \n let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None);\n \n Self {\n http_client: Client::new(),\n base_url: host.to_string(),\n chain_id,\n signer: Some(signer),\n api_creds: None,\n order_builder: Some(order_builder),\n }\n }\n\n /// Create a client with L2 headers (for API key authentication)\n pub fn with_l2_headers(host: \u0026str, private_key: \u0026str, chain_id: u64, api_creds: ApiCreds) -\u003e Self {\n let signer = private_key.parse::\u003cPrivateKeySigner\u003e()\n .expect(\"Invalid private key\");\n \n let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None);\n \n Self {\n http_client: Client::new(),\n base_url: host.to_string(),\n chain_id,\n signer: Some(signer),\n api_creds: Some(api_creds),\n order_builder: Some(order_builder),\n }\n }\n\n /// Set API credentials\n pub fn set_api_creds(\u0026mut self, api_creds: ApiCreds) {\n self.api_creds = Some(api_creds);\n }\n\n /// Test basic connectivity\n pub async fn get_ok(\u0026self) -\u003e bool {\n match self.http_client.get(\u0026format!(\"{}/ok\", self.base_url)).send().await {\n Ok(response) =\u003e response.status().is_success(),\n Err(_) =\u003e false,\n }\n }\n\n /// Get server time\n pub async fn get_server_time(\u0026self) -\u003e Result\u003cu64\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/time\", self.base_url))\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get server time\"));\n }\n\n let time_text = response.text().await?;\n let timestamp = time_text.trim()\n .parse::\u003cu64\u003e()\n .map_err(|e| PolyfillError::parse(format!(\"Invalid timestamp format: {}\", e), None))?;\n\n Ok(timestamp)\n }\n\n\n /// Get order book for a token\n pub async fn get_order_book(\u0026self, token_id: \u0026str) -\u003e Result\u003cOrderBookSummary\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/book\", self.base_url))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get order book\"));\n }\n\n let order_book: OrderBookSummary = response.json().await?;\n Ok(order_book)\n }\n\n /// Get midpoint for a token\n pub async fn get_midpoint(\u0026self, token_id: \u0026str) -\u003e Result\u003cMidpointResponse\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/midpoint\", self.base_url))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get midpoint\"));\n }\n\n let midpoint: MidpointResponse = response.json().await?;\n Ok(midpoint)\n }\n\n /// Get spread for a token\n pub async fn get_spread(\u0026self, token_id: \u0026str) -\u003e Result\u003cSpreadResponse\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/spread\", self.base_url))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get spread\"));\n }\n\n let spread: SpreadResponse = response.json().await?;\n Ok(spread)\n }\n\n /// Get spreads for multiple tokens (batch)\n pub async fn get_spreads(\u0026self, token_ids: \u0026[String]) -\u003e Result\u003cstd::collections::HashMap\u003cString, Decimal\u003e\u003e {\n let request_data: Vec\u003cstd::collections::HashMap\u003c\u0026str, String\u003e\u003e = token_ids\n .iter()\n .map(|id| {\n let mut map = std::collections::HashMap::new();\n map.insert(\"token_id\", id.clone());\n map\n })\n .collect();\n\n let response = self.http_client\n .post(\u0026format!(\"{}/spreads\", self.base_url))\n .json(\u0026request_data)\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get batch spreads\"));\n }\n\n response.json::\u003cstd::collections::HashMap\u003cString, Decimal\u003e\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get price for a token and side\n pub async fn get_price(\u0026self, token_id: \u0026str, side: Side) -\u003e Result\u003cPriceResponse\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/price\", self.base_url))\n .query(\u0026[\n (\"token_id\", token_id),\n (\"side\", side.as_str()),\n ])\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get price\"));\n }\n\n let price: PriceResponse = response.json().await?;\n Ok(price)\n }\n\n /// Get tick size for a token\n pub async fn get_tick_size(\u0026self, token_id: \u0026str) -\u003e Result\u003cDecimal\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/tick-size\", self.base_url))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get tick size\"));\n }\n\n let tick_size_response: Value = response.json().await?;\n let tick_size = tick_size_response[\"minimum_tick_size\"]\n .as_str()\n .and_then(|s| Decimal::from_str(s).ok())\n .or_else(|| tick_size_response[\"minimum_tick_size\"].as_f64().map(|f| Decimal::from_f64(f).unwrap_or(Decimal::ZERO)))\n .ok_or_else(|| PolyfillError::parse(\"Invalid tick size format\", None))?;\n\n Ok(tick_size)\n }\n\n /// Create a new API key\n pub async fn create_api_key(\u0026self, nonce: Option\u003cU256\u003e) -\u003e Result\u003cApiCreds\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n \n let headers = create_l1_headers(signer, nonce)?;\n let req = self.create_request_with_headers(Method::POST, \"/auth/api-key\", headers.into_iter());\n \n let response = req.send().await?;\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to create API key\"));\n }\n \n Ok(response.json::\u003cApiCreds\u003e().await?)\n }\n\n /// Derive an existing API key\n pub async fn derive_api_key(\u0026self, nonce: Option\u003cU256\u003e) -\u003e Result\u003cApiCreds\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n \n let headers = create_l1_headers(signer, nonce)?;\n let req = self.create_request_with_headers(Method::GET, \"/auth/derive-api-key\", headers.into_iter());\n \n let response = req.send().await?;\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to derive API key\"));\n }\n \n Ok(response.json::\u003cApiCreds\u003e().await?)\n }\n\n /// Create or derive API key (try create first, fallback to derive)\n pub async fn create_or_derive_api_key(\u0026self, nonce: Option\u003cU256\u003e) -\u003e Result\u003cApiCreds\u003e {\n match self.create_api_key(nonce).await {\n Ok(creds) =\u003e Ok(creds),\n Err(_) =\u003e self.derive_api_key(nonce).await,\n }\n }\n\n /// Get all API keys for the authenticated user\n pub async fn get_api_keys(\u0026self) -\u003e Result\u003cVec\u003cString\u003e\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::config(\"Signer not configured\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::config(\"API credentials not configured\"))?;\n\n let method = Method::GET;\n let endpoint = \"/auth/api-keys\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n let api_keys_response: crate::types::ApiKeysResponse = response.json().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))?;\n\n Ok(api_keys_response.api_keys)\n }\n\n /// Delete the current API key\n pub async fn delete_api_key(\u0026self) -\u003e Result\u003cString\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::config(\"Signer not configured\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::config(\"API credentials not configured\"))?;\n\n let method = Method::DELETE;\n let endpoint = \"/auth/api-key\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.text().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Helper to create request with headers\n fn create_request_with_headers(\n \u0026self,\n method: Method,\n endpoint: \u0026str,\n headers: impl Iterator\u003cItem = (\u0026'static str, String)\u003e,\n ) -\u003e RequestBuilder {\n let req = self.http_client.request(method, format!(\"{}{}\", \u0026self.base_url, endpoint));\n headers.fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v))\n }\n\n /// Get neg risk for a token\n pub async fn get_neg_risk(\u0026self, token_id: \u0026str) -\u003e Result\u003cbool\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/neg-risk\", self.base_url))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get neg risk\"));\n }\n\n let neg_risk_response: Value = response.json().await?;\n let neg_risk = neg_risk_response[\"neg_risk\"]\n .as_bool()\n .ok_or_else(|| PolyfillError::parse(\"Invalid neg risk format\", None))?;\n\n Ok(neg_risk)\n }\n\n /// Resolve tick size for an order\n async fn resolve_tick_size(\n \u0026self,\n token_id: \u0026str,\n tick_size: Option\u003cDecimal\u003e,\n ) -\u003e Result\u003cDecimal\u003e {\n let min_tick_size = self.get_tick_size(token_id).await?;\n\n match tick_size {\n None =\u003e Ok(min_tick_size),\n Some(t) =\u003e {\n if t \u003c min_tick_size {\n Err(PolyfillError::validation(format!(\n \"Tick size {} is smaller than min_tick_size {} for token_id: {}\",\n t, min_tick_size, token_id\n )))\n } else {\n Ok(t)\n }\n }\n }\n }\n\n /// Get filled order options\n async fn get_filled_order_options(\n \u0026self,\n token_id: \u0026str,\n options: Option\u003c\u0026OrderOptions\u003e,\n ) -\u003e Result\u003cOrderOptions\u003e {\n let (tick_size, neg_risk, fee_rate_bps) = match options {\n Some(o) =\u003e (o.tick_size, o.neg_risk, o.fee_rate_bps),\n None =\u003e (None, None, None),\n };\n\n let tick_size = self.resolve_tick_size(token_id, tick_size).await?;\n let neg_risk = match neg_risk {\n Some(nr) =\u003e nr,\n None =\u003e self.get_neg_risk(token_id).await?,\n };\n\n Ok(OrderOptions {\n tick_size: Some(tick_size),\n neg_risk: Some(neg_risk),\n fee_rate_bps,\n })\n }\n\n /// Check if price is in valid range\n fn is_price_in_range(\u0026self, price: Decimal, tick_size: Decimal) -\u003e bool {\n let min_price = tick_size;\n let max_price = Decimal::ONE - tick_size;\n price \u003e= min_price \u0026\u0026 price \u003c= max_price\n }\n\n /// Create an order\n pub async fn create_order(\n \u0026self,\n order_args: \u0026OrderArgs,\n expiration: Option\u003cu64\u003e,\n extras: Option\u003ccrate::types::ExtraOrderArgs\u003e,\n options: Option\u003c\u0026OrderOptions\u003e,\n ) -\u003e Result\u003cSignedOrderRequest\u003e {\n let order_builder = self.order_builder.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Order builder not initialized\"))?;\n\n let create_order_options = self\n .get_filled_order_options(\u0026order_args.token_id, options)\n .await?;\n \n let expiration = expiration.unwrap_or(0);\n let extras = extras.unwrap_or_default();\n\n if !self.is_price_in_range(\n order_args.price,\n create_order_options.tick_size.expect(\"Should be filled\"),\n ) {\n return Err(PolyfillError::validation(\"Price is not in range of tick_size\"));\n }\n\n order_builder.create_order(\n self.chain_id,\n order_args,\n expiration,\n \u0026extras,\n \u0026create_order_options,\n )\n }\n\n /// Calculate market price from order book\n async fn calculate_market_price(\n \u0026self,\n token_id: \u0026str,\n side: Side,\n amount: Decimal,\n ) -\u003e Result\u003cDecimal\u003e {\n let book = self.get_order_book(token_id).await?;\n let order_builder = self.order_builder.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Order builder not initialized\"))?;\n\n // Convert OrderSummary to BookLevel\n let levels: Vec\u003ccrate::types::BookLevel\u003e = match side {\n Side::BUY =\u003e book.asks.into_iter().map(|s| crate::types::BookLevel {\n price: s.price,\n size: s.size,\n }).collect(),\n Side::SELL =\u003e book.bids.into_iter().map(|s| crate::types::BookLevel {\n price: s.price,\n size: s.size,\n }).collect(),\n };\n\n order_builder.calculate_market_price(\u0026levels, amount)\n }\n\n /// Create a market order\n pub async fn create_market_order(\n \u0026self,\n order_args: \u0026crate::types::MarketOrderArgs,\n extras: Option\u003ccrate::types::ExtraOrderArgs\u003e,\n options: Option\u003c\u0026OrderOptions\u003e,\n ) -\u003e Result\u003cSignedOrderRequest\u003e {\n let order_builder = self.order_builder.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Order builder not initialized\"))?;\n\n let create_order_options = self\n .get_filled_order_options(\u0026order_args.token_id, options)\n .await?;\n\n let extras = extras.unwrap_or_default();\n let price = self\n .calculate_market_price(\u0026order_args.token_id, Side::BUY, order_args.amount)\n .await?;\n\n if !self.is_price_in_range(\n price,\n create_order_options.tick_size.expect(\"Should be filled\"),\n ) {\n return Err(PolyfillError::validation(\"Price is not in range of tick_size\"));\n }\n\n order_builder.create_market_order(\n self.chain_id,\n order_args,\n price,\n \u0026extras,\n \u0026create_order_options,\n )\n }\n\n /// Post an order to the exchange\n pub async fn post_order(\n \u0026self,\n order: SignedOrderRequest,\n order_type: OrderType,\n ) -\u003e Result\u003cValue\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"API credentials not set\"))?;\n\n let body = PostOrder::new(order, api_creds.api_key.clone(), order_type);\n\n let headers = create_l2_headers(signer, api_creds, \"POST\", \"/order\", Some(\u0026body))?;\n let req = self.create_request_with_headers(Method::POST, \"/order\", headers.into_iter());\n\n let response = req.json(\u0026body).send().await?;\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to post order\"));\n }\n\n Ok(response.json::\u003cValue\u003e().await?)\n }\n\n /// Create and post an order in one call\n pub async fn create_and_post_order(\u0026self, order_args: \u0026OrderArgs) -\u003e Result\u003cValue\u003e {\n let order = self.create_order(order_args, None, None, None).await?;\n self.post_order(order, OrderType::GTC).await\n }\n\n /// Cancel an order\n pub async fn cancel(\u0026self, order_id: \u0026str) -\u003e Result\u003cValue\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"API credentials not set\"))?;\n\n let body = std::collections::HashMap::from([(\"orderID\", order_id)]);\n\n let headers = create_l2_headers(signer, api_creds, \"DELETE\", \"/order\", Some(\u0026body))?;\n let req = self.create_request_with_headers(Method::DELETE, \"/order\", headers.into_iter());\n\n let response = req.json(\u0026body).send().await?;\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to cancel order\"));\n }\n\n Ok(response.json::\u003cValue\u003e().await?)\n }\n\n /// Cancel multiple orders\n pub async fn cancel_orders(\u0026self, order_ids: \u0026[String]) -\u003e Result\u003cValue\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"API credentials not set\"))?;\n\n let headers = create_l2_headers(signer, api_creds, \"DELETE\", \"/orders\", Some(order_ids))?;\n let req = self.create_request_with_headers(Method::DELETE, \"/orders\", headers.into_iter());\n\n let response = req.json(order_ids).send().await?;\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to cancel orders\"));\n }\n\n Ok(response.json::\u003cValue\u003e().await?)\n }\n\n /// Cancel all orders\n pub async fn cancel_all(\u0026self) -\u003e Result\u003cValue\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"API credentials not set\"))?;\n\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, \"DELETE\", \"/cancel-all\", None)?;\n let req = self.create_request_with_headers(Method::DELETE, \"/cancel-all\", headers.into_iter());\n\n let response = req.send().await?;\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to cancel all orders\"));\n }\n\n Ok(response.json::\u003cValue\u003e().await?)\n }\n\n /// Get open orders with optional filtering\n /// \n /// This retrieves all open orders for the authenticated user. You can filter by:\n /// - Order ID (exact match)\n /// - Asset/Token ID (all orders for a specific token)\n /// - Market ID (all orders for a specific market)\n /// \n /// The response includes order status, fill information, and timestamps.\n pub async fn get_orders(\u0026self, params: Option\u003c\u0026crate::types::OpenOrderParams\u003e, next_cursor: Option\u003c\u0026str\u003e) -\u003e Result\u003cVec\u003ccrate::types::OpenOrder\u003e\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"API credentials not set\"))?;\n\n let method = Method::GET;\n let endpoint = \"/data/orders\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let query_params = match params {\n None =\u003e Vec::new(),\n Some(p) =\u003e p.to_query_params(),\n };\n\n let mut next_cursor = next_cursor.unwrap_or(\"MA==\").to_string(); // INITIAL_CURSOR\n let mut output = Vec::new();\n \n while next_cursor != \"LTE=\" { // END_CURSOR\n let req = self.http_client\n .request(method.clone(), format!(\"{}{}\", self.base_url, endpoint))\n .query(\u0026query_params)\n .query(\u0026[(\"next_cursor\", \u0026next_cursor)]);\n\n let r = headers\n .clone()\n .into_iter()\n .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));\n\n let resp = r.send().await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?\n .json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))?;\n\n let new_cursor = resp[\"next_cursor\"]\n .as_str()\n .ok_or_else(|| PolyfillError::parse(\"Failed to parse next cursor\".to_string(), None))?\n .to_owned();\n\n next_cursor = new_cursor;\n\n let results = resp[\"data\"].clone();\n let orders = serde_json::from_value::\u003cVec\u003ccrate::types::OpenOrder\u003e\u003e(results)\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse data from order response: {}\", e), None))?;\n output.extend(orders);\n }\n \n Ok(output)\n }\n\n /// Get trade history with optional filtering\n /// \n /// This retrieves historical trades for the authenticated user. You can filter by:\n /// - Trade ID (exact match)\n /// - Maker address (trades where you were the maker)\n /// - Market ID (trades in a specific market)\n /// - Asset/Token ID (trades for a specific token)\n /// - Time range (before/after timestamps)\n /// \n /// Trades are returned in reverse chronological order (newest first).\n pub async fn get_trades(\u0026self, trade_params: Option\u003c\u0026crate::types::TradeParams\u003e, next_cursor: Option\u003c\u0026str\u003e) -\u003e Result\u003cVec\u003cValue\u003e\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"API credentials not set\"))?;\n\n let method = Method::GET;\n let endpoint = \"/data/trades\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let query_params = match trade_params {\n None =\u003e Vec::new(),\n Some(p) =\u003e p.to_query_params(),\n };\n\n let mut next_cursor = next_cursor.unwrap_or(\"MA==\").to_string(); // INITIAL_CURSOR\n let mut output = Vec::new();\n \n while next_cursor != \"LTE=\" { // END_CURSOR\n let req = self.http_client\n .request(method.clone(), format!(\"{}{}\", self.base_url, endpoint))\n .query(\u0026query_params)\n .query(\u0026[(\"next_cursor\", \u0026next_cursor)]);\n\n let r = headers\n .clone()\n .into_iter()\n .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));\n\n let resp = r.send().await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?\n .json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))?;\n\n let new_cursor = resp[\"next_cursor\"]\n .as_str()\n .ok_or_else(|| PolyfillError::parse(\"Failed to parse next cursor\".to_string(), None))?\n .to_owned();\n\n next_cursor = new_cursor;\n\n let results = resp[\"data\"].clone();\n output.push(results);\n }\n \n Ok(output)\n }\n\n /// Get balance and allowance information for all assets\n /// \n /// This returns the current balance and allowance for each asset in your account.\n /// Balance is how much you own, allowance is how much the exchange can spend on your behalf.\n /// \n /// You need both balance and allowance to place orders - the exchange needs permission\n /// to move your tokens when orders are filled.\n pub async fn get_balance_allowance(\u0026self, params: Option\u003ccrate::types::BalanceAllowanceParams\u003e) -\u003e Result\u003cValue\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"API credentials not set\"))?;\n\n let mut params = params.unwrap_or_default();\n if params.signature_type.is_none() {\n params.set_signature_type(\n self.order_builder\n .as_ref()\n .expect(\"OrderBuilder not set\")\n .get_sig_type(),\n );\n }\n\n let query_params = params.to_query_params();\n\n let method = Method::GET;\n let endpoint = \"/balance-allowance\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .query(\u0026query_params)\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Set up notifications for order fills and other events\n /// \n /// This configures push notifications so you get alerted when:\n /// - Your orders get filled\n /// - Your orders get cancelled\n /// - Market conditions change significantly\n /// \n /// The signature proves you own the account and want to receive notifications.\n pub async fn get_notifications(\u0026self) -\u003e Result\u003cValue\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"Signer not set\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"API credentials not set\"))?;\n\n let method = Method::GET;\n let endpoint = \"/notifications\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .query(\u0026[(\n \"signature_type\",\n \u0026self\n .order_builder\n .as_ref()\n .expect(\"OrderBuilder not set\")\n .get_sig_type().to_string(),\n )])\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get midpoints for multiple tokens in a single request\n /// \n /// This is much more efficient than calling get_midpoint() multiple times.\n /// Instead of N round trips, you make just 1 request and get all the midpoints back.\n /// \n /// Midpoints are returned as a HashMap where the key is the token_id and the value\n /// is the midpoint price (or None if there's no valid midpoint).\n pub async fn get_midpoints(\u0026self, token_ids: \u0026[String]) -\u003e Result\u003cstd::collections::HashMap\u003cString, Decimal\u003e\u003e {\n let request_data: Vec\u003cstd::collections::HashMap\u003c\u0026str, String\u003e\u003e = token_ids\n .iter()\n .map(|id| {\n let mut map = std::collections::HashMap::new();\n map.insert(\"token_id\", id.clone());\n map\n })\n .collect();\n \n let response = self.http_client\n .post(\u0026format!(\"{}/midpoints\", self.base_url))\n .json(\u0026request_data)\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get batch midpoints\"));\n }\n\n let midpoints: std::collections::HashMap\u003cString, Decimal\u003e = response.json().await?;\n Ok(midpoints)\n }\n\n /// Get bid/ask/mid prices for multiple tokens in a single request\n /// \n /// This gives you the full price picture for multiple tokens at once.\n /// Much more efficient than individual calls, especially when you're tracking\n /// a portfolio or comparing multiple markets.\n /// \n /// Returns bid (best buy price), ask (best sell price), and mid (average) for each token.\n pub async fn get_prices(\u0026self, book_params: \u0026[crate::types::BookParams]) -\u003e Result\u003cstd::collections::HashMap\u003cString, std::collections::HashMap\u003cSide, Decimal\u003e\u003e\u003e {\n let request_data: Vec\u003cstd::collections::HashMap\u003c\u0026str, String\u003e\u003e = book_params\n .iter()\n .map(|params| {\n let mut map = std::collections::HashMap::new();\n map.insert(\"token_id\", params.token_id.clone());\n map.insert(\"side\", params.side.as_str().to_string());\n map\n })\n .collect();\n \n let response = self.http_client\n .post(\u0026format!(\"{}/prices\", self.base_url))\n .json(\u0026request_data)\n .send()\n .await?;\n\n if !response.status().is_success() {\n return Err(PolyfillError::api(response.status().as_u16(), \"Failed to get batch prices\"));\n }\n\n let prices: std::collections::HashMap\u003cString, std::collections::HashMap\u003cSide, Decimal\u003e\u003e = response.json().await?;\n Ok(prices)\n }\n\n /// Get order book for multiple tokens (batch) - reference implementation compatible\n pub async fn get_order_books(\u0026self, token_ids: \u0026[String]) -\u003e Result\u003cVec\u003cOrderBookSummary\u003e\u003e {\n let request_data: Vec\u003cstd::collections::HashMap\u003c\u0026str, String\u003e\u003e = token_ids\n .iter()\n .map(|id| {\n let mut map = std::collections::HashMap::new();\n map.insert(\"token_id\", id.clone());\n map\n })\n .collect();\n\n let response = self.http_client\n .post(\u0026format!(\"{}/books\", self.base_url))\n .json(\u0026request_data)\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cVec\u003cOrderBookSummary\u003e\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get single order by ID\n pub async fn get_order(\u0026self, order_id: \u0026str) -\u003e Result\u003ccrate::types::OpenOrder\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::config(\"Signer not configured\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::config(\"API credentials not configured\"))?;\n\n let method = Method::GET;\n let endpoint = \u0026format!(\"/data/order/{}\", order_id);\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003ccrate::types::OpenOrder\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get last trade price for a token\n pub async fn get_last_trade_price(\u0026self, token_id: \u0026str) -\u003e Result\u003cValue\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/last-trade-price\", self.base_url))\n .query(\u0026[(\"token_id\", token_id)])\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get last trade prices for multiple tokens\n pub async fn get_last_trade_prices(\u0026self, token_ids: \u0026[String]) -\u003e Result\u003cValue\u003e {\n let request_data: Vec\u003cstd::collections::HashMap\u003c\u0026str, String\u003e\u003e = token_ids\n .iter()\n .map(|id| {\n let mut map = std::collections::HashMap::new();\n map.insert(\"token_id\", id.clone());\n map\n })\n .collect();\n\n let response = self.http_client\n .post(\u0026format!(\"{}/last-trades-prices\", self.base_url))\n .json(\u0026request_data)\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Cancel market orders with optional filters\n pub async fn cancel_market_orders(\u0026self, market: Option\u003c\u0026str\u003e, asset_id: Option\u003c\u0026str\u003e) -\u003e Result\u003cValue\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::config(\"Signer not configured\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::config(\"API credentials not configured\"))?;\n\n let method = Method::DELETE;\n let endpoint = \"/cancel-market-orders\";\n let body = std::collections::HashMap::from([\n (\"market\", market.unwrap_or(\"\")),\n (\"asset_id\", asset_id.unwrap_or(\"\")),\n ]);\n\n let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(\u0026body))?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .json(\u0026body)\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Drop (delete) notifications by IDs\n pub async fn drop_notifications(\u0026self, ids: \u0026[String]) -\u003e Result\u003cValue\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::config(\"Signer not configured\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::config(\"API credentials not configured\"))?;\n\n let method = Method::DELETE;\n let endpoint = \"/notifications\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .query(\u0026[(\"ids\", ids.join(\",\"))])\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Update balance allowance\n pub async fn update_balance_allowance(\u0026self, params: Option\u003ccrate::types::BalanceAllowanceParams\u003e) -\u003e Result\u003cValue\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::config(\"Signer not configured\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::config(\"API credentials not configured\"))?;\n\n let mut params = params.unwrap_or_default();\n if params.signature_type.is_none() {\n params.set_signature_type(\n self.order_builder\n .as_ref()\n .expect(\"OrderBuilder not set\")\n .get_sig_type(),\n );\n }\n\n let query_params = params.to_query_params();\n\n let method = Method::GET;\n let endpoint = \"/balance-allowance/update\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .query(\u0026query_params)\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Check if an order is scoring\n pub async fn is_order_scoring(\u0026self, order_id: \u0026str) -\u003e Result\u003cbool\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::config(\"Signer not configured\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::config(\"API credentials not configured\"))?;\n\n let method = Method::GET;\n let endpoint = \"/order-scoring\";\n let headers = create_l2_headers::\u003cValue\u003e(signer, api_creds, method.as_str(), endpoint, None)?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .query(\u0026[(\"order_id\", order_id)])\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n let result: Value = response.json().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))?;\n\n Ok(result[\"scoring\"].as_bool().unwrap_or(false))\n }\n\n /// Check if multiple orders are scoring\n pub async fn are_orders_scoring(\u0026self, order_ids: \u0026[\u0026str]) -\u003e Result\u003cstd::collections::HashMap\u003cString, bool\u003e\u003e {\n let signer = self.signer.as_ref()\n .ok_or_else(|| PolyfillError::config(\"Signer not configured\"))?;\n let api_creds = self.api_creds.as_ref()\n .ok_or_else(|| PolyfillError::config(\"API credentials not configured\"))?;\n\n let method = Method::POST;\n let endpoint = \"/orders-scoring\";\n let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(order_ids))?;\n\n let response = self.http_client\n .request(method, format!(\"{}{}\", self.base_url, endpoint))\n .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())\n .json(order_ids)\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cstd::collections::HashMap\u003cString, bool\u003e\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get sampling markets with pagination\n pub async fn get_sampling_markets(\u0026self, next_cursor: Option\u003c\u0026str\u003e) -\u003e Result\u003ccrate::types::MarketsResponse\u003e {\n let next_cursor = next_cursor.unwrap_or(\"MA==\"); // INITIAL_CURSOR\n\n let response = self.http_client\n .get(\u0026format!(\"{}/sampling-markets\", self.base_url))\n .query(\u0026[(\"next_cursor\", next_cursor)])\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003ccrate::types::MarketsResponse\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get sampling simplified markets with pagination\n pub async fn get_sampling_simplified_markets(\u0026self, next_cursor: Option\u003c\u0026str\u003e) -\u003e Result\u003ccrate::types::SimplifiedMarketsResponse\u003e {\n let next_cursor = next_cursor.unwrap_or(\"MA==\"); // INITIAL_CURSOR\n\n let response = self.http_client\n .get(\u0026format!(\"{}/sampling-simplified-markets\", self.base_url))\n .query(\u0026[(\"next_cursor\", next_cursor)])\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003ccrate::types::SimplifiedMarketsResponse\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get markets with pagination\n pub async fn get_markets(\u0026self, next_cursor: Option\u003c\u0026str\u003e) -\u003e Result\u003ccrate::types::MarketsResponse\u003e {\n let next_cursor = next_cursor.unwrap_or(\"MA==\"); // INITIAL_CURSOR\n\n let response = self.http_client\n .get(\u0026format!(\"{}/markets\", self.base_url))\n .query(\u0026[(\"next_cursor\", next_cursor)])\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003ccrate::types::MarketsResponse\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get simplified markets with pagination\n pub async fn get_simplified_markets(\u0026self, next_cursor: Option\u003c\u0026str\u003e) -\u003e Result\u003ccrate::types::SimplifiedMarketsResponse\u003e {\n let next_cursor = next_cursor.unwrap_or(\"MA==\"); // INITIAL_CURSOR\n\n let response = self.http_client\n .get(\u0026format!(\"{}/simplified-markets\", self.base_url))\n .query(\u0026[(\"next_cursor\", next_cursor)])\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003ccrate::types::SimplifiedMarketsResponse\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get single market by condition ID\n pub async fn get_market(\u0026self, condition_id: \u0026str) -\u003e Result\u003ccrate::types::Market\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/markets/{}\", self.base_url, condition_id))\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003ccrate::types::Market\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n\n /// Get market trades events\n pub async fn get_market_trades_events(\u0026self, condition_id: \u0026str) -\u003e Result\u003cValue\u003e {\n let response = self.http_client\n .get(\u0026format!(\"{}/live-activity/events/{}\", self.base_url, condition_id))\n .send()\n .await\n .map_err(|e| PolyfillError::network(format!(\"Request failed: {}\", e), e))?;\n\n response.json::\u003cValue\u003e().await\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse response: {}\", e), None))\n }\n}\n\n// Re-export types from the canonical location in types.rs\npub use crate::types::{\n ExtraOrderArgs, MarketOrderArgs, OrderBookSummary, OrderSummary, \n MidpointResponse, SpreadResponse, PriceResponse, TickSizeResponse, \n NegRiskResponse, MarketsResponse, Market, Token, Rewards,\n};\n\n// Compatibility types that need to stay in client.rs\n#[derive(Debug, Default)]\npub struct CreateOrderOptions {\n pub tick_size: Option\u003cDecimal\u003e,\n pub neg_risk: Option\u003cbool\u003e,\n}\n\n// Re-export for compatibility\npub type PolyfillClient = ClobClient; ","traces":[{"line":34,"address":[],"length":0,"stats":{"Line":1}},{"line":36,"address":[],"length":0,"stats":{"Line":3}},{"line":45,"address":[],"length":0,"stats":{"Line":1}},{"line":47,"address":[],"length":0,"stats":{"Line":2}},{"line":67,"address":[],"length":0,"stats":{"Line":1}},{"line":69,"address":[],"length":0,"stats":{"Line":2}},{"line":70,"address":[],"length":0,"stats":{"Line":3}},{"line":79,"address":[],"length":0,"stats":{"Line":0}},{"line":80,"address":[],"length":0,"stats":{"Line":0}},{"line":83,"address":[],"length":0,"stats":{"Line":0}},{"line":86,"address":[],"length":0,"stats":{"Line":0}},{"line":87,"address":[],"length":0,"stats":{"Line":0}},{"line":89,"address":[],"length":0,"stats":{"Line":0}},{"line":91,"address":[],"length":0,"stats":{"Line":0}},{"line":96,"address":[],"length":0,"stats":{"Line":0}},{"line":97,"address":[],"length":0,"stats":{"Line":0}},{"line":100,"address":[],"length":0,"stats":{"Line":0}},{"line":103,"address":[],"length":0,"stats":{"Line":0}},{"line":104,"address":[],"length":0,"stats":{"Line":0}},{"line":106,"address":[],"length":0,"stats":{"Line":0}},{"line":107,"address":[],"length":0,"stats":{"Line":0}},{"line":108,"address":[],"length":0,"stats":{"Line":0}},{"line":113,"address":[],"length":0,"stats":{"Line":0}},{"line":114,"address":[],"length":0,"stats":{"Line":0}},{"line":118,"address":[],"length":0,"stats":{"Line":0}},{"line":119,"address":[],"length":0,"stats":{"Line":0}},{"line":120,"address":[],"length":0,"stats":{"Line":0}},{"line":121,"address":[],"length":0,"stats":{"Line":0}},{"line":126,"address":[],"length":0,"stats":{"Line":0}},{"line":127,"address":[],"length":0,"stats":{"Line":0}},{"line":128,"address":[],"length":0,"stats":{"Line":0}},{"line":130,"address":[],"length":0,"stats":{"Line":0}},{"line":132,"address":[],"length":0,"stats":{"Line":0}},{"line":133,"address":[],"length":0,"stats":{"Line":0}},{"line":136,"address":[],"length":0,"stats":{"Line":0}},{"line":137,"address":[],"length":0,"stats":{"Line":0}},{"line":139,"address":[],"length":0,"stats":{"Line":0}},{"line":141,"address":[],"length":0,"stats":{"Line":0}},{"line":146,"address":[],"length":0,"stats":{"Line":0}},{"line":147,"address":[],"length":0,"stats":{"Line":0}},{"line":148,"address":[],"length":0,"stats":{"Line":0}},{"line":149,"address":[],"length":0,"stats":{"Line":0}},{"line":151,"address":[],"length":0,"stats":{"Line":0}},{"line":153,"address":[],"length":0,"stats":{"Line":0}},{"line":154,"address":[],"length":0,"stats":{"Line":0}},{"line":157,"address":[],"length":0,"stats":{"Line":0}},{"line":158,"address":[],"length":0,"stats":{"Line":0}},{"line":162,"address":[],"length":0,"stats":{"Line":0}},{"line":163,"address":[],"length":0,"stats":{"Line":0}},{"line":164,"address":[],"length":0,"stats":{"Line":0}},{"line":165,"address":[],"length":0,"stats":{"Line":0}},{"line":167,"address":[],"length":0,"stats":{"Line":0}},{"line":169,"address":[],"length":0,"stats":{"Line":0}},{"line":170,"address":[],"length":0,"stats":{"Line":0}},{"line":173,"address":[],"length":0,"stats":{"Line":0}},{"line":174,"address":[],"length":0,"stats":{"Line":0}},{"line":178,"address":[],"length":0,"stats":{"Line":0}},{"line":179,"address":[],"length":0,"stats":{"Line":0}},{"line":180,"address":[],"length":0,"stats":{"Line":0}},{"line":181,"address":[],"length":0,"stats":{"Line":0}},{"line":183,"address":[],"length":0,"stats":{"Line":0}},{"line":185,"address":[],"length":0,"stats":{"Line":0}},{"line":186,"address":[],"length":0,"stats":{"Line":0}},{"line":189,"address":[],"length":0,"stats":{"Line":0}},{"line":190,"address":[],"length":0,"stats":{"Line":0}},{"line":194,"address":[],"length":0,"stats":{"Line":0}},{"line":195,"address":[],"length":0,"stats":{"Line":0}},{"line":197,"address":[],"length":0,"stats":{"Line":0}},{"line":198,"address":[],"length":0,"stats":{"Line":0}},{"line":199,"address":[],"length":0,"stats":{"Line":0}},{"line":200,"address":[],"length":0,"stats":{"Line":0}},{"line":204,"address":[],"length":0,"stats":{"Line":0}},{"line":205,"address":[],"length":0,"stats":{"Line":0}},{"line":206,"address":[],"length":0,"stats":{"Line":0}},{"line":208,"address":[],"length":0,"stats":{"Line":0}},{"line":210,"address":[],"length":0,"stats":{"Line":0}},{"line":211,"address":[],"length":0,"stats":{"Line":0}},{"line":214,"address":[],"length":0,"stats":{"Line":0}},{"line":215,"address":[],"length":0,"stats":{"Line":0}},{"line":219,"address":[],"length":0,"stats":{"Line":0}},{"line":220,"address":[],"length":0,"stats":{"Line":0}},{"line":221,"address":[],"length":0,"stats":{"Line":0}},{"line":222,"address":[],"length":0,"stats":{"Line":0}},{"line":223,"address":[],"length":0,"stats":{"Line":0}},{"line":224,"address":[],"length":0,"stats":{"Line":0}},{"line":227,"address":[],"length":0,"stats":{"Line":0}},{"line":229,"address":[],"length":0,"stats":{"Line":0}},{"line":230,"address":[],"length":0,"stats":{"Line":0}},{"line":233,"address":[],"length":0,"stats":{"Line":0}},{"line":234,"address":[],"length":0,"stats":{"Line":0}},{"line":238,"address":[],"length":0,"stats":{"Line":0}},{"line":239,"address":[],"length":0,"stats":{"Line":0}},{"line":240,"address":[],"length":0,"stats":{"Line":0}},{"line":241,"address":[],"length":0,"stats":{"Line":0}},{"line":243,"address":[],"length":0,"stats":{"Line":0}},{"line":245,"address":[],"length":0,"stats":{"Line":0}},{"line":246,"address":[],"length":0,"stats":{"Line":0}},{"line":249,"address":[],"length":0,"stats":{"Line":0}},{"line":250,"address":[],"length":0,"stats":{"Line":0}},{"line":252,"address":[],"length":0,"stats":{"Line":0}},{"line":253,"address":[],"length":0,"stats":{"Line":0}},{"line":254,"address":[],"length":0,"stats":{"Line":0}},{"line":256,"address":[],"length":0,"stats":{"Line":0}},{"line":260,"address":[],"length":0,"stats":{"Line":0}},{"line":261,"address":[],"length":0,"stats":{"Line":0}},{"line":262,"address":[],"length":0,"stats":{"Line":0}},{"line":264,"address":[],"length":0,"stats":{"Line":0}},{"line":265,"address":[],"length":0,"stats":{"Line":0}},{"line":267,"address":[],"length":0,"stats":{"Line":0}},{"line":268,"address":[],"length":0,"stats":{"Line":0}},{"line":269,"address":[],"length":0,"stats":{"Line":0}},{"line":272,"address":[],"length":0,"stats":{"Line":0}},{"line":276,"address":[],"length":0,"stats":{"Line":0}},{"line":277,"address":[],"length":0,"stats":{"Line":0}},{"line":278,"address":[],"length":0,"stats":{"Line":0}},{"line":280,"address":[],"length":0,"stats":{"Line":0}},{"line":281,"address":[],"length":0,"stats":{"Line":0}},{"line":283,"address":[],"length":0,"stats":{"Line":0}},{"line":284,"address":[],"length":0,"stats":{"Line":0}},{"line":285,"address":[],"length":0,"stats":{"Line":0}},{"line":288,"address":[],"length":0,"stats":{"Line":0}},{"line":292,"address":[],"length":0,"stats":{"Line":0}},{"line":293,"address":[],"length":0,"stats":{"Line":0}},{"line":294,"address":[],"length":0,"stats":{"Line":0}},{"line":295,"address":[],"length":0,"stats":{"Line":0}},{"line":300,"address":[],"length":0,"stats":{"Line":0}},{"line":301,"address":[],"length":0,"stats":{"Line":0}},{"line":302,"address":[],"length":0,"stats":{"Line":0}},{"line":303,"address":[],"length":0,"stats":{"Line":0}},{"line":304,"address":[],"length":0,"stats":{"Line":0}},{"line":306,"address":[],"length":0,"stats":{"Line":0}},{"line":307,"address":[],"length":0,"stats":{"Line":0}},{"line":308,"address":[],"length":0,"stats":{"Line":0}},{"line":310,"address":[],"length":0,"stats":{"Line":0}},{"line":311,"address":[],"length":0,"stats":{"Line":0}},{"line":312,"address":[],"length":0,"stats":{"Line":0}},{"line":314,"address":[],"length":0,"stats":{"Line":0}},{"line":315,"address":[],"length":0,"stats":{"Line":0}},{"line":317,"address":[],"length":0,"stats":{"Line":0}},{"line":318,"address":[],"length":0,"stats":{"Line":0}},{"line":320,"address":[],"length":0,"stats":{"Line":0}},{"line":324,"address":[],"length":0,"stats":{"Line":0}},{"line":325,"address":[],"length":0,"stats":{"Line":0}},{"line":326,"address":[],"length":0,"stats":{"Line":0}},{"line":327,"address":[],"length":0,"stats":{"Line":0}},{"line":328,"address":[],"length":0,"stats":{"Line":0}},{"line":330,"address":[],"length":0,"stats":{"Line":0}},{"line":331,"address":[],"length":0,"stats":{"Line":0}},{"line":332,"address":[],"length":0,"stats":{"Line":0}},{"line":334,"address":[],"length":0,"stats":{"Line":0}},{"line":335,"address":[],"length":0,"stats":{"Line":0}},{"line":336,"address":[],"length":0,"stats":{"Line":0}},{"line":338,"address":[],"length":0,"stats":{"Line":0}},{"line":339,"address":[],"length":0,"stats":{"Line":0}},{"line":341,"address":[],"length":0,"stats":{"Line":0}},{"line":342,"address":[],"length":0,"stats":{"Line":0}},{"line":346,"address":[],"length":0,"stats":{"Line":0}},{"line":352,"address":[],"length":0,"stats":{"Line":0}},{"line":353,"address":[],"length":0,"stats":{"Line":0}},{"line":357,"address":[],"length":0,"stats":{"Line":0}},{"line":358,"address":[],"length":0,"stats":{"Line":0}},{"line":359,"address":[],"length":0,"stats":{"Line":0}},{"line":360,"address":[],"length":0,"stats":{"Line":0}},{"line":362,"address":[],"length":0,"stats":{"Line":0}},{"line":364,"address":[],"length":0,"stats":{"Line":0}},{"line":365,"address":[],"length":0,"stats":{"Line":0}},{"line":368,"address":[],"length":0,"stats":{"Line":0}},{"line":369,"address":[],"length":0,"stats":{"Line":0}},{"line":371,"address":[],"length":0,"stats":{"Line":0}},{"line":373,"address":[],"length":0,"stats":{"Line":0}},{"line":377,"address":[],"length":0,"stats":{"Line":0}},{"line":382,"address":[],"length":0,"stats":{"Line":0}},{"line":384,"address":[],"length":0,"stats":{"Line":0}},{"line":385,"address":[],"length":0,"stats":{"Line":0}},{"line":386,"address":[],"length":0,"stats":{"Line":0}},{"line":387,"address":[],"length":0,"stats":{"Line":0}},{"line":388,"address":[],"length":0,"stats":{"Line":0}},{"line":389,"address":[],"length":0,"stats":{"Line":0}},{"line":390,"address":[],"length":0,"stats":{"Line":0}},{"line":393,"address":[],"length":0,"stats":{"Line":0}},{"line":400,"address":[],"length":0,"stats":{"Line":0}},{"line":405,"address":[],"length":0,"stats":{"Line":0}},{"line":406,"address":[],"length":0,"stats":{"Line":0}},{"line":407,"address":[],"length":0,"stats":{"Line":0}},{"line":410,"address":[],"length":0,"stats":{"Line":0}},{"line":411,"address":[],"length":0,"stats":{"Line":0}},{"line":412,"address":[],"length":0,"stats":{"Line":0}},{"line":413,"address":[],"length":0,"stats":{"Line":0}},{"line":416,"address":[],"length":0,"stats":{"Line":0}},{"line":417,"address":[],"length":0,"stats":{"Line":0}},{"line":418,"address":[],"length":0,"stats":{"Line":0}},{"line":419,"address":[],"length":0,"stats":{"Line":0}},{"line":424,"address":[],"length":0,"stats":{"Line":0}},{"line":425,"address":[],"length":0,"stats":{"Line":0}},{"line":426,"address":[],"length":0,"stats":{"Line":0}},{"line":427,"address":[],"length":0,"stats":{"Line":0}},{"line":431,"address":[],"length":0,"stats":{"Line":0}},{"line":438,"address":[],"length":0,"stats":{"Line":0}},{"line":439,"address":[],"length":0,"stats":{"Line":0}},{"line":441,"address":[],"length":0,"stats":{"Line":0}},{"line":442,"address":[],"length":0,"stats":{"Line":0}},{"line":443,"address":[],"length":0,"stats":{"Line":0}},{"line":445,"address":[],"length":0,"stats":{"Line":0}},{"line":446,"address":[],"length":0,"stats":{"Line":0}},{"line":448,"address":[],"length":0,"stats":{"Line":0}},{"line":449,"address":[],"length":0,"stats":{"Line":0}},{"line":450,"address":[],"length":0,"stats":{"Line":0}},{"line":452,"address":[],"length":0,"stats":{"Line":0}},{"line":455,"address":[],"length":0,"stats":{"Line":0}},{"line":456,"address":[],"length":0,"stats":{"Line":0}},{"line":457,"address":[],"length":0,"stats":{"Line":0}},{"line":458,"address":[],"length":0,"stats":{"Line":0}},{"line":459,"address":[],"length":0,"stats":{"Line":0}},{"line":460,"address":[],"length":0,"stats":{"Line":0}},{"line":465,"address":[],"length":0,"stats":{"Line":0}},{"line":471,"address":[],"length":0,"stats":{"Line":0}},{"line":472,"address":[],"length":0,"stats":{"Line":0}},{"line":473,"address":[],"length":0,"stats":{"Line":0}},{"line":476,"address":[],"length":0,"stats":{"Line":0}},{"line":477,"address":[],"length":0,"stats":{"Line":0}},{"line":478,"address":[],"length":0,"stats":{"Line":0}},{"line":479,"address":[],"length":0,"stats":{"Line":0}},{"line":480,"address":[],"length":0,"stats":{"Line":0}},{"line":481,"address":[],"length":0,"stats":{"Line":0}},{"line":482,"address":[],"length":0,"stats":{"Line":0}},{"line":483,"address":[],"length":0,"stats":{"Line":0}},{"line":484,"address":[],"length":0,"stats":{"Line":0}},{"line":487,"address":[],"length":0,"stats":{"Line":0}},{"line":491,"address":[],"length":0,"stats":{"Line":0}},{"line":497,"address":[],"length":0,"stats":{"Line":0}},{"line":498,"address":[],"length":0,"stats":{"Line":0}},{"line":500,"address":[],"length":0,"stats":{"Line":0}},{"line":501,"address":[],"length":0,"stats":{"Line":0}},{"line":502,"address":[],"length":0,"stats":{"Line":0}},{"line":504,"address":[],"length":0,"stats":{"Line":0}},{"line":505,"address":[],"length":0,"stats":{"Line":0}},{"line":506,"address":[],"length":0,"stats":{"Line":0}},{"line":507,"address":[],"length":0,"stats":{"Line":0}},{"line":509,"address":[],"length":0,"stats":{"Line":0}},{"line":510,"address":[],"length":0,"stats":{"Line":0}},{"line":511,"address":[],"length":0,"stats":{"Line":0}},{"line":513,"address":[],"length":0,"stats":{"Line":0}},{"line":516,"address":[],"length":0,"stats":{"Line":0}},{"line":517,"address":[],"length":0,"stats":{"Line":0}},{"line":518,"address":[],"length":0,"stats":{"Line":0}},{"line":519,"address":[],"length":0,"stats":{"Line":0}},{"line":520,"address":[],"length":0,"stats":{"Line":0}},{"line":521,"address":[],"length":0,"stats":{"Line":0}},{"line":526,"address":[],"length":0,"stats":{"Line":0}},{"line":531,"address":[],"length":0,"stats":{"Line":0}},{"line":532,"address":[],"length":0,"stats":{"Line":0}},{"line":533,"address":[],"length":0,"stats":{"Line":0}},{"line":534,"address":[],"length":0,"stats":{"Line":0}},{"line":536,"address":[],"length":0,"stats":{"Line":0}},{"line":538,"address":[],"length":0,"stats":{"Line":0}},{"line":539,"address":[],"length":0,"stats":{"Line":0}},{"line":541,"address":[],"length":0,"stats":{"Line":0}},{"line":542,"address":[],"length":0,"stats":{"Line":0}},{"line":543,"address":[],"length":0,"stats":{"Line":0}},{"line":546,"address":[],"length":0,"stats":{"Line":0}},{"line":550,"address":[],"length":0,"stats":{"Line":0}},{"line":551,"address":[],"length":0,"stats":{"Line":0}},{"line":552,"address":[],"length":0,"stats":{"Line":0}},{"line":556,"address":[],"length":0,"stats":{"Line":0}},{"line":557,"address":[],"length":0,"stats":{"Line":0}},{"line":558,"address":[],"length":0,"stats":{"Line":0}},{"line":559,"address":[],"length":0,"stats":{"Line":0}},{"line":560,"address":[],"length":0,"stats":{"Line":0}},{"line":562,"address":[],"length":0,"stats":{"Line":0}},{"line":564,"address":[],"length":0,"stats":{"Line":0}},{"line":565,"address":[],"length":0,"stats":{"Line":0}},{"line":567,"address":[],"length":0,"stats":{"Line":0}},{"line":568,"address":[],"length":0,"stats":{"Line":0}},{"line":569,"address":[],"length":0,"stats":{"Line":0}},{"line":572,"address":[],"length":0,"stats":{"Line":0}},{"line":576,"address":[],"length":0,"stats":{"Line":0}},{"line":577,"address":[],"length":0,"stats":{"Line":0}},{"line":578,"address":[],"length":0,"stats":{"Line":0}},{"line":579,"address":[],"length":0,"stats":{"Line":0}},{"line":580,"address":[],"length":0,"stats":{"Line":0}},{"line":582,"address":[],"length":0,"stats":{"Line":0}},{"line":583,"address":[],"length":0,"stats":{"Line":0}},{"line":585,"address":[],"length":0,"stats":{"Line":0}},{"line":586,"address":[],"length":0,"stats":{"Line":0}},{"line":587,"address":[],"length":0,"stats":{"Line":0}},{"line":590,"address":[],"length":0,"stats":{"Line":0}},{"line":594,"address":[],"length":0,"stats":{"Line":0}},{"line":595,"address":[],"length":0,"stats":{"Line":0}},{"line":596,"address":[],"length":0,"stats":{"Line":0}},{"line":597,"address":[],"length":0,"stats":{"Line":0}},{"line":598,"address":[],"length":0,"stats":{"Line":0}},{"line":600,"address":[],"length":0,"stats":{"Line":0}},{"line":601,"address":[],"length":0,"stats":{"Line":0}},{"line":603,"address":[],"length":0,"stats":{"Line":0}},{"line":604,"address":[],"length":0,"stats":{"Line":0}},{"line":605,"address":[],"length":0,"stats":{"Line":0}},{"line":608,"address":[],"length":0,"stats":{"Line":0}},{"line":619,"address":[],"length":0,"stats":{"Line":0}},{"line":620,"address":[],"length":0,"stats":{"Line":0}},{"line":621,"address":[],"length":0,"stats":{"Line":0}},{"line":622,"address":[],"length":0,"stats":{"Line":0}},{"line":623,"address":[],"length":0,"stats":{"Line":0}},{"line":625,"address":[],"length":0,"stats":{"Line":0}},{"line":626,"address":[],"length":0,"stats":{"Line":0}},{"line":627,"address":[],"length":0,"stats":{"Line":0}},{"line":629,"address":[],"length":0,"stats":{"Line":0}},{"line":630,"address":[],"length":0,"stats":{"Line":0}},{"line":631,"address":[],"length":0,"stats":{"Line":0}},{"line":634,"address":[],"length":0,"stats":{"Line":0}},{"line":635,"address":[],"length":0,"stats":{"Line":0}},{"line":637,"address":[],"length":0,"stats":{"Line":0}},{"line":638,"address":[],"length":0,"stats":{"Line":0}},{"line":639,"address":[],"length":0,"stats":{"Line":0}},{"line":640,"address":[],"length":0,"stats":{"Line":0}},{"line":641,"address":[],"length":0,"stats":{"Line":0}},{"line":643,"address":[],"length":0,"stats":{"Line":0}},{"line":646,"address":[],"length":0,"stats":{"Line":0}},{"line":648,"address":[],"length":0,"stats":{"Line":0}},{"line":649,"address":[],"length":0,"stats":{"Line":0}},{"line":651,"address":[],"length":0,"stats":{"Line":0}},{"line":653,"address":[],"length":0,"stats":{"Line":0}},{"line":655,"address":[],"length":0,"stats":{"Line":0}},{"line":658,"address":[],"length":0,"stats":{"Line":0}},{"line":660,"address":[],"length":0,"stats":{"Line":0}},{"line":661,"address":[],"length":0,"stats":{"Line":0}},{"line":662,"address":[],"length":0,"stats":{"Line":0}},{"line":663,"address":[],"length":0,"stats":{"Line":0}},{"line":666,"address":[],"length":0,"stats":{"Line":0}},{"line":679,"address":[],"length":0,"stats":{"Line":0}},{"line":680,"address":[],"length":0,"stats":{"Line":0}},{"line":681,"address":[],"length":0,"stats":{"Line":0}},{"line":682,"address":[],"length":0,"stats":{"Line":0}},{"line":683,"address":[],"length":0,"stats":{"Line":0}},{"line":685,"address":[],"length":0,"stats":{"Line":0}},{"line":686,"address":[],"length":0,"stats":{"Line":0}},{"line":687,"address":[],"length":0,"stats":{"Line":0}},{"line":689,"address":[],"length":0,"stats":{"Line":0}},{"line":690,"address":[],"length":0,"stats":{"Line":0}},{"line":691,"address":[],"length":0,"stats":{"Line":0}},{"line":694,"address":[],"length":0,"stats":{"Line":0}},{"line":695,"address":[],"length":0,"stats":{"Line":0}},{"line":697,"address":[],"length":0,"stats":{"Line":0}},{"line":698,"address":[],"length":0,"stats":{"Line":0}},{"line":699,"address":[],"length":0,"stats":{"Line":0}},{"line":700,"address":[],"length":0,"stats":{"Line":0}},{"line":701,"address":[],"length":0,"stats":{"Line":0}},{"line":703,"address":[],"length":0,"stats":{"Line":0}},{"line":706,"address":[],"length":0,"stats":{"Line":0}},{"line":708,"address":[],"length":0,"stats":{"Line":0}},{"line":709,"address":[],"length":0,"stats":{"Line":0}},{"line":711,"address":[],"length":0,"stats":{"Line":0}},{"line":713,"address":[],"length":0,"stats":{"Line":0}},{"line":715,"address":[],"length":0,"stats":{"Line":0}},{"line":718,"address":[],"length":0,"stats":{"Line":0}},{"line":720,"address":[],"length":0,"stats":{"Line":0}},{"line":721,"address":[],"length":0,"stats":{"Line":0}},{"line":724,"address":[],"length":0,"stats":{"Line":0}},{"line":734,"address":[],"length":0,"stats":{"Line":0}},{"line":735,"address":[],"length":0,"stats":{"Line":0}},{"line":736,"address":[],"length":0,"stats":{"Line":0}},{"line":737,"address":[],"length":0,"stats":{"Line":0}},{"line":738,"address":[],"length":0,"stats":{"Line":0}},{"line":740,"address":[],"length":0,"stats":{"Line":0}},{"line":741,"address":[],"length":0,"stats":{"Line":0}},{"line":742,"address":[],"length":0,"stats":{"Line":0}},{"line":743,"address":[],"length":0,"stats":{"Line":0}},{"line":744,"address":[],"length":0,"stats":{"Line":0}},{"line":745,"address":[],"length":0,"stats":{"Line":0}},{"line":746,"address":[],"length":0,"stats":{"Line":0}},{"line":750,"address":[],"length":0,"stats":{"Line":0}},{"line":752,"address":[],"length":0,"stats":{"Line":0}},{"line":753,"address":[],"length":0,"stats":{"Line":0}},{"line":754,"address":[],"length":0,"stats":{"Line":0}},{"line":756,"address":[],"length":0,"stats":{"Line":0}},{"line":757,"address":[],"length":0,"stats":{"Line":0}},{"line":758,"address":[],"length":0,"stats":{"Line":0}},{"line":759,"address":[],"length":0,"stats":{"Line":0}},{"line":761,"address":[],"length":0,"stats":{"Line":0}},{"line":762,"address":[],"length":0,"stats":{"Line":0}},{"line":764,"address":[],"length":0,"stats":{"Line":0}},{"line":765,"address":[],"length":0,"stats":{"Line":0}},{"line":776,"address":[],"length":0,"stats":{"Line":0}},{"line":777,"address":[],"length":0,"stats":{"Line":0}},{"line":778,"address":[],"length":0,"stats":{"Line":0}},{"line":779,"address":[],"length":0,"stats":{"Line":0}},{"line":780,"address":[],"length":0,"stats":{"Line":0}},{"line":782,"address":[],"length":0,"stats":{"Line":0}},{"line":783,"address":[],"length":0,"stats":{"Line":0}},{"line":784,"address":[],"length":0,"stats":{"Line":0}},{"line":786,"address":[],"length":0,"stats":{"Line":0}},{"line":787,"address":[],"length":0,"stats":{"Line":0}},{"line":788,"address":[],"length":0,"stats":{"Line":0}},{"line":789,"address":[],"length":0,"stats":{"Line":0}},{"line":790,"address":[],"length":0,"stats":{"Line":0}},{"line":791,"address":[],"length":0,"stats":{"Line":0}},{"line":792,"address":[],"length":0,"stats":{"Line":0}},{"line":793,"address":[],"length":0,"stats":{"Line":0}},{"line":794,"address":[],"length":0,"stats":{"Line":0}},{"line":795,"address":[],"length":0,"stats":{"Line":0}},{"line":798,"address":[],"length":0,"stats":{"Line":0}},{"line":799,"address":[],"length":0,"stats":{"Line":0}},{"line":801,"address":[],"length":0,"stats":{"Line":0}},{"line":802,"address":[],"length":0,"stats":{"Line":0}},{"line":812,"address":[],"length":0,"stats":{"Line":0}},{"line":813,"address":[],"length":0,"stats":{"Line":0}},{"line":815,"address":[],"length":0,"stats":{"Line":0}},{"line":816,"address":[],"length":0,"stats":{"Line":0}},{"line":817,"address":[],"length":0,"stats":{"Line":0}},{"line":818,"address":[],"length":0,"stats":{"Line":0}},{"line":822,"address":[],"length":0,"stats":{"Line":0}},{"line":823,"address":[],"length":0,"stats":{"Line":0}},{"line":824,"address":[],"length":0,"stats":{"Line":0}},{"line":826,"address":[],"length":0,"stats":{"Line":0}},{"line":828,"address":[],"length":0,"stats":{"Line":0}},{"line":829,"address":[],"length":0,"stats":{"Line":0}},{"line":832,"address":[],"length":0,"stats":{"Line":0}},{"line":833,"address":[],"length":0,"stats":{"Line":0}},{"line":843,"address":[],"length":0,"stats":{"Line":0}},{"line":844,"address":[],"length":0,"stats":{"Line":0}},{"line":846,"address":[],"length":0,"stats":{"Line":0}},{"line":847,"address":[],"length":0,"stats":{"Line":0}},{"line":848,"address":[],"length":0,"stats":{"Line":0}},{"line":849,"address":[],"length":0,"stats":{"Line":0}},{"line":850,"address":[],"length":0,"stats":{"Line":0}},{"line":854,"address":[],"length":0,"stats":{"Line":0}},{"line":855,"address":[],"length":0,"stats":{"Line":0}},{"line":856,"address":[],"length":0,"stats":{"Line":0}},{"line":858,"address":[],"length":0,"stats":{"Line":0}},{"line":860,"address":[],"length":0,"stats":{"Line":0}},{"line":861,"address":[],"length":0,"stats":{"Line":0}},{"line":864,"address":[],"length":0,"stats":{"Line":0}},{"line":865,"address":[],"length":0,"stats":{"Line":0}},{"line":869,"address":[],"length":0,"stats":{"Line":0}},{"line":870,"address":[],"length":0,"stats":{"Line":0}},{"line":872,"address":[],"length":0,"stats":{"Line":0}},{"line":873,"address":[],"length":0,"stats":{"Line":0}},{"line":874,"address":[],"length":0,"stats":{"Line":0}},{"line":875,"address":[],"length":0,"stats":{"Line":0}},{"line":879,"address":[],"length":0,"stats":{"Line":0}},{"line":880,"address":[],"length":0,"stats":{"Line":0}},{"line":881,"address":[],"length":0,"stats":{"Line":0}},{"line":883,"address":[],"length":0,"stats":{"Line":0}},{"line":884,"address":[],"length":0,"stats":{"Line":0}},{"line":886,"address":[],"length":0,"stats":{"Line":0}},{"line":887,"address":[],"length":0,"stats":{"Line":0}},{"line":891,"address":[],"length":0,"stats":{"Line":0}},{"line":892,"address":[],"length":0,"stats":{"Line":0}},{"line":893,"address":[],"length":0,"stats":{"Line":0}},{"line":894,"address":[],"length":0,"stats":{"Line":0}},{"line":895,"address":[],"length":0,"stats":{"Line":0}},{"line":897,"address":[],"length":0,"stats":{"Line":0}},{"line":898,"address":[],"length":0,"stats":{"Line":0}},{"line":899,"address":[],"length":0,"stats":{"Line":0}},{"line":901,"address":[],"length":0,"stats":{"Line":0}},{"line":902,"address":[],"length":0,"stats":{"Line":0}},{"line":903,"address":[],"length":0,"stats":{"Line":0}},{"line":905,"address":[],"length":0,"stats":{"Line":0}},{"line":906,"address":[],"length":0,"stats":{"Line":0}},{"line":908,"address":[],"length":0,"stats":{"Line":0}},{"line":909,"address":[],"length":0,"stats":{"Line":0}},{"line":913,"address":[],"length":0,"stats":{"Line":0}},{"line":914,"address":[],"length":0,"stats":{"Line":0}},{"line":915,"address":[],"length":0,"stats":{"Line":0}},{"line":916,"address":[],"length":0,"stats":{"Line":0}},{"line":918,"address":[],"length":0,"stats":{"Line":0}},{"line":919,"address":[],"length":0,"stats":{"Line":0}},{"line":921,"address":[],"length":0,"stats":{"Line":0}},{"line":922,"address":[],"length":0,"stats":{"Line":0}},{"line":926,"address":[],"length":0,"stats":{"Line":0}},{"line":927,"address":[],"length":0,"stats":{"Line":0}},{"line":929,"address":[],"length":0,"stats":{"Line":0}},{"line":930,"address":[],"length":0,"stats":{"Line":0}},{"line":931,"address":[],"length":0,"stats":{"Line":0}},{"line":932,"address":[],"length":0,"stats":{"Line":0}},{"line":936,"address":[],"length":0,"stats":{"Line":0}},{"line":937,"address":[],"length":0,"stats":{"Line":0}},{"line":938,"address":[],"length":0,"stats":{"Line":0}},{"line":940,"address":[],"length":0,"stats":{"Line":0}},{"line":941,"address":[],"length":0,"stats":{"Line":0}},{"line":943,"address":[],"length":0,"stats":{"Line":0}},{"line":944,"address":[],"length":0,"stats":{"Line":0}},{"line":948,"address":[],"length":0,"stats":{"Line":0}},{"line":949,"address":[],"length":0,"stats":{"Line":0}},{"line":950,"address":[],"length":0,"stats":{"Line":0}},{"line":951,"address":[],"length":0,"stats":{"Line":0}},{"line":952,"address":[],"length":0,"stats":{"Line":0}},{"line":954,"address":[],"length":0,"stats":{"Line":0}},{"line":955,"address":[],"length":0,"stats":{"Line":0}},{"line":956,"address":[],"length":0,"stats":{"Line":0}},{"line":957,"address":[],"length":0,"stats":{"Line":0}},{"line":958,"address":[],"length":0,"stats":{"Line":0}},{"line":961,"address":[],"length":0,"stats":{"Line":0}},{"line":963,"address":[],"length":0,"stats":{"Line":0}},{"line":964,"address":[],"length":0,"stats":{"Line":0}},{"line":965,"address":[],"length":0,"stats":{"Line":0}},{"line":966,"address":[],"length":0,"stats":{"Line":0}},{"line":968,"address":[],"length":0,"stats":{"Line":0}},{"line":969,"address":[],"length":0,"stats":{"Line":0}},{"line":971,"address":[],"length":0,"stats":{"Line":0}},{"line":972,"address":[],"length":0,"stats":{"Line":0}},{"line":976,"address":[],"length":0,"stats":{"Line":0}},{"line":977,"address":[],"length":0,"stats":{"Line":0}},{"line":978,"address":[],"length":0,"stats":{"Line":0}},{"line":979,"address":[],"length":0,"stats":{"Line":0}},{"line":980,"address":[],"length":0,"stats":{"Line":0}},{"line":982,"address":[],"length":0,"stats":{"Line":0}},{"line":983,"address":[],"length":0,"stats":{"Line":0}},{"line":984,"address":[],"length":0,"stats":{"Line":0}},{"line":986,"address":[],"length":0,"stats":{"Line":0}},{"line":987,"address":[],"length":0,"stats":{"Line":0}},{"line":988,"address":[],"length":0,"stats":{"Line":0}},{"line":989,"address":[],"length":0,"stats":{"Line":0}},{"line":991,"address":[],"length":0,"stats":{"Line":0}},{"line":992,"address":[],"length":0,"stats":{"Line":0}},{"line":994,"address":[],"length":0,"stats":{"Line":0}},{"line":995,"address":[],"length":0,"stats":{"Line":0}},{"line":999,"address":[],"length":0,"stats":{"Line":0}},{"line":1000,"address":[],"length":0,"stats":{"Line":0}},{"line":1001,"address":[],"length":0,"stats":{"Line":0}},{"line":1002,"address":[],"length":0,"stats":{"Line":0}},{"line":1003,"address":[],"length":0,"stats":{"Line":0}},{"line":1005,"address":[],"length":0,"stats":{"Line":0}},{"line":1006,"address":[],"length":0,"stats":{"Line":0}},{"line":1007,"address":[],"length":0,"stats":{"Line":0}},{"line":1008,"address":[],"length":0,"stats":{"Line":0}},{"line":1009,"address":[],"length":0,"stats":{"Line":0}},{"line":1010,"address":[],"length":0,"stats":{"Line":0}},{"line":1011,"address":[],"length":0,"stats":{"Line":0}},{"line":1015,"address":[],"length":0,"stats":{"Line":0}},{"line":1017,"address":[],"length":0,"stats":{"Line":0}},{"line":1018,"address":[],"length":0,"stats":{"Line":0}},{"line":1019,"address":[],"length":0,"stats":{"Line":0}},{"line":1021,"address":[],"length":0,"stats":{"Line":0}},{"line":1022,"address":[],"length":0,"stats":{"Line":0}},{"line":1023,"address":[],"length":0,"stats":{"Line":0}},{"line":1024,"address":[],"length":0,"stats":{"Line":0}},{"line":1026,"address":[],"length":0,"stats":{"Line":0}},{"line":1027,"address":[],"length":0,"stats":{"Line":0}},{"line":1029,"address":[],"length":0,"stats":{"Line":0}},{"line":1030,"address":[],"length":0,"stats":{"Line":0}},{"line":1034,"address":[],"length":0,"stats":{"Line":0}},{"line":1035,"address":[],"length":0,"stats":{"Line":0}},{"line":1036,"address":[],"length":0,"stats":{"Line":0}},{"line":1037,"address":[],"length":0,"stats":{"Line":0}},{"line":1038,"address":[],"length":0,"stats":{"Line":0}},{"line":1040,"address":[],"length":0,"stats":{"Line":0}},{"line":1041,"address":[],"length":0,"stats":{"Line":0}},{"line":1042,"address":[],"length":0,"stats":{"Line":0}},{"line":1044,"address":[],"length":0,"stats":{"Line":0}},{"line":1045,"address":[],"length":0,"stats":{"Line":0}},{"line":1046,"address":[],"length":0,"stats":{"Line":0}},{"line":1047,"address":[],"length":0,"stats":{"Line":0}},{"line":1049,"address":[],"length":0,"stats":{"Line":0}},{"line":1050,"address":[],"length":0,"stats":{"Line":0}},{"line":1052,"address":[],"length":0,"stats":{"Line":0}},{"line":1053,"address":[],"length":0,"stats":{"Line":0}},{"line":1055,"address":[],"length":0,"stats":{"Line":0}},{"line":1059,"address":[],"length":0,"stats":{"Line":0}},{"line":1060,"address":[],"length":0,"stats":{"Line":0}},{"line":1061,"address":[],"length":0,"stats":{"Line":0}},{"line":1062,"address":[],"length":0,"stats":{"Line":0}},{"line":1063,"address":[],"length":0,"stats":{"Line":0}},{"line":1065,"address":[],"length":0,"stats":{"Line":0}},{"line":1066,"address":[],"length":0,"stats":{"Line":0}},{"line":1067,"address":[],"length":0,"stats":{"Line":0}},{"line":1069,"address":[],"length":0,"stats":{"Line":0}},{"line":1070,"address":[],"length":0,"stats":{"Line":0}},{"line":1071,"address":[],"length":0,"stats":{"Line":0}},{"line":1072,"address":[],"length":0,"stats":{"Line":0}},{"line":1074,"address":[],"length":0,"stats":{"Line":0}},{"line":1075,"address":[],"length":0,"stats":{"Line":0}},{"line":1077,"address":[],"length":0,"stats":{"Line":0}},{"line":1078,"address":[],"length":0,"stats":{"Line":0}},{"line":1082,"address":[],"length":0,"stats":{"Line":0}},{"line":1083,"address":[],"length":0,"stats":{"Line":0}},{"line":1085,"address":[],"length":0,"stats":{"Line":0}},{"line":1086,"address":[],"length":0,"stats":{"Line":0}},{"line":1087,"address":[],"length":0,"stats":{"Line":0}},{"line":1089,"address":[],"length":0,"stats":{"Line":0}},{"line":1090,"address":[],"length":0,"stats":{"Line":0}},{"line":1092,"address":[],"length":0,"stats":{"Line":0}},{"line":1093,"address":[],"length":0,"stats":{"Line":0}},{"line":1097,"address":[],"length":0,"stats":{"Line":0}},{"line":1098,"address":[],"length":0,"stats":{"Line":0}},{"line":1100,"address":[],"length":0,"stats":{"Line":0}},{"line":1101,"address":[],"length":0,"stats":{"Line":0}},{"line":1102,"address":[],"length":0,"stats":{"Line":0}},{"line":1104,"address":[],"length":0,"stats":{"Line":0}},{"line":1105,"address":[],"length":0,"stats":{"Line":0}},{"line":1107,"address":[],"length":0,"stats":{"Line":0}},{"line":1108,"address":[],"length":0,"stats":{"Line":0}},{"line":1112,"address":[],"length":0,"stats":{"Line":0}},{"line":1113,"address":[],"length":0,"stats":{"Line":0}},{"line":1115,"address":[],"length":0,"stats":{"Line":0}},{"line":1116,"address":[],"length":0,"stats":{"Line":0}},{"line":1117,"address":[],"length":0,"stats":{"Line":0}},{"line":1119,"address":[],"length":0,"stats":{"Line":0}},{"line":1120,"address":[],"length":0,"stats":{"Line":0}},{"line":1122,"address":[],"length":0,"stats":{"Line":0}},{"line":1123,"address":[],"length":0,"stats":{"Line":0}},{"line":1127,"address":[],"length":0,"stats":{"Line":0}},{"line":1128,"address":[],"length":0,"stats":{"Line":0}},{"line":1130,"address":[],"length":0,"stats":{"Line":0}},{"line":1131,"address":[],"length":0,"stats":{"Line":0}},{"line":1132,"address":[],"length":0,"stats":{"Line":0}},{"line":1134,"address":[],"length":0,"stats":{"Line":0}},{"line":1135,"address":[],"length":0,"stats":{"Line":0}},{"line":1137,"address":[],"length":0,"stats":{"Line":0}},{"line":1138,"address":[],"length":0,"stats":{"Line":0}},{"line":1142,"address":[],"length":0,"stats":{"Line":0}},{"line":1143,"address":[],"length":0,"stats":{"Line":0}},{"line":1144,"address":[],"length":0,"stats":{"Line":0}},{"line":1146,"address":[],"length":0,"stats":{"Line":0}},{"line":1147,"address":[],"length":0,"stats":{"Line":0}},{"line":1149,"address":[],"length":0,"stats":{"Line":0}},{"line":1150,"address":[],"length":0,"stats":{"Line":0}},{"line":1154,"address":[],"length":0,"stats":{"Line":0}},{"line":1155,"address":[],"length":0,"stats":{"Line":0}},{"line":1156,"address":[],"length":0,"stats":{"Line":0}},{"line":1158,"address":[],"length":0,"stats":{"Line":0}},{"line":1159,"address":[],"length":0,"stats":{"Line":0}},{"line":1161,"address":[],"length":0,"stats":{"Line":0}},{"line":1162,"address":[],"length":0,"stats":{"Line":0}}],"covered":7,"coverable":623},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","config.rs"],"content":"//! Contract configuration for Polymarket\n//!\n//! This module contains contract addresses and configuration for different\n//! networks and environments.\n\nuse std::collections::HashMap;\n\n/// Contract configuration for a specific network\n#[derive(Debug, Clone)]\npub struct ContractConfig {\n pub exchange: String,\n pub collateral: String,\n pub conditional_tokens: String,\n}\n\n/// Get contract configuration for a specific chain and risk setting\npub fn get_contract_config(chain_id: u64, neg_risk: bool) -\u003e Option\u003cContractConfig\u003e {\n match neg_risk {\n true =\u003e {\n if chain_id == 137 {\n return Some(ContractConfig {\n exchange: \"0xC5d563A36AE78145C45a50134d48A1215220f80a\".to_owned(),\n collateral: \"0x2791bca1f2de4661ed88a30c99a7a9449aa84174\".to_owned(),\n conditional_tokens: \"0x4D97DCd97eC945f40cF65F87097ACe5EA0476045\".to_owned(),\n });\n } else if chain_id == 80002 {\n return Some(ContractConfig {\n exchange: \"0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296\".to_owned(),\n collateral: \"0x9c4e1703476e875070ee25b56a58b008cfb8fa78\".to_owned(),\n conditional_tokens: \"0x69308FB512518e39F9b16112fA8d994F4e2Bf8bB\".to_owned(),\n });\n }\n None\n }\n false =\u003e {\n if chain_id == 137 {\n return Some(ContractConfig {\n exchange: \"0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E\".to_owned(),\n collateral: \"0x2791Bca1f2de4661ED88A30C99a7a9449Aa84174\".to_owned(),\n conditional_tokens: \"0x4D97DCd97eC945f40cF65F87097ACe5EA0476045\".to_owned(),\n });\n } else if chain_id == 80002 {\n return Some(ContractConfig {\n exchange: \"0xdFE02Eb6733538f8Ea35D585af8DE5958AD99E40\".to_owned(),\n collateral: \"0x9c4e1703476e875070ee25b56a58b008cfb8fa78\".to_owned(),\n conditional_tokens: \"0x69308FB512518e39F9b16112fA8d994F4e2Bf8bB\".to_owned(),\n });\n }\n None\n }\n }\n}\n\n/// Network configuration\n#[derive(Debug, Clone)]\npub struct NetworkConfig {\n pub chain_id: u64,\n pub name: String,\n pub rpc_url: String,\n pub block_explorer: String,\n pub contracts: HashMap\u003cString, ContractConfig\u003e,\n}\n\nimpl NetworkConfig {\n /// Get configuration for Polygon mainnet\n pub fn polygon_mainnet() -\u003e Self {\n let mut contracts = HashMap::new();\n contracts.insert(\"standard\".to_string(), get_contract_config(137, false).unwrap());\n contracts.insert(\"neg_risk\".to_string(), get_contract_config(137, true).unwrap());\n\n Self {\n chain_id: 137,\n name: \"Polygon Mainnet\".to_string(),\n rpc_url: \"https://polygon-rpc.com\".to_string(),\n block_explorer: \"https://polygonscan.com\".to_string(),\n contracts,\n }\n }\n\n /// Get configuration for Polygon Mumbai testnet\n pub fn polygon_mumbai() -\u003e Self {\n let mut contracts = HashMap::new();\n contracts.insert(\"standard\".to_string(), get_contract_config(80002, false).unwrap());\n contracts.insert(\"neg_risk\".to_string(), get_contract_config(80002, true).unwrap());\n\n Self {\n chain_id: 80002,\n name: \"Polygon Mumbai\".to_string(),\n rpc_url: \"https://rpc-mumbai.maticvigil.com\".to_string(),\n block_explorer: \"https://mumbai.polygonscan.com\".to_string(),\n contracts,\n }\n }\n\n /// Get contract configuration for this network\n pub fn get_contract(\u0026self, risk_type: \u0026str) -\u003e Option\u003c\u0026ContractConfig\u003e {\n self.contracts.get(risk_type)\n }\n}\n\n/// Global configuration\n#[derive(Debug, Clone)]\npub struct GlobalConfig {\n pub networks: HashMap\u003cu64, NetworkConfig\u003e,\n pub default_network: u64,\n}\n\nimpl GlobalConfig {\n /// Create default configuration\n pub fn new() -\u003e Self {\n let mut networks = HashMap::new();\n networks.insert(137, NetworkConfig::polygon_mainnet());\n networks.insert(80002, NetworkConfig::polygon_mumbai());\n\n Self {\n networks,\n default_network: 137,\n }\n }\n\n /// Get network configuration\n pub fn get_network(\u0026self, chain_id: u64) -\u003e Option\u003c\u0026NetworkConfig\u003e {\n self.networks.get(\u0026chain_id)\n }\n\n /// Get default network\n pub fn default_network(\u0026self) -\u003e Option\u003c\u0026NetworkConfig\u003e {\n self.networks.get(\u0026self.default_network)\n }\n}\n\nimpl Default for GlobalConfig {\n fn default() -\u003e Self {\n Self::new()\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_contract_config() {\n let config = get_contract_config(137, false);\n assert!(config.is_some());\n \n let config = config.unwrap();\n assert!(!config.exchange.is_empty());\n assert!(!config.collateral.is_empty());\n assert!(!config.conditional_tokens.is_empty());\n }\n\n #[test]\n fn test_network_config() {\n let polygon = NetworkConfig::polygon_mainnet();\n assert_eq!(polygon.chain_id, 137);\n assert_eq!(polygon.name, \"Polygon Mainnet\");\n \n let contract = polygon.get_contract(\"standard\");\n assert!(contract.is_some());\n }\n\n #[test]\n fn test_global_config() {\n let config = GlobalConfig::new();\n assert_eq!(config.default_network, 137);\n \n let network = config.get_network(137);\n assert!(network.is_some());\n }\n} ","traces":[],"covered":0,"coverable":0},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","decode.rs"],"content":"//! Data decoding utilities for Polymarket client\n//!\n//! This module provides high-performance decoding functions for various\n//! data formats used in trading environments.\n\nuse crate::errors::{PolyfillError, Result};\nuse crate::types::*;\nuse alloy_primitives::{Address, U256};\nuse chrono::{DateTime, Utc};\nuse rust_decimal::Decimal;\nuse serde::{Deserialize, Deserializer};\nuse serde_json::Value;\nuse std::str::FromStr;\n\n/// Fast string to number deserializers\npub mod deserializers {\n use super::*;\n use std::fmt::Display;\n\n /// Deserialize number from string or number\n pub fn number_from_string\u003c'de, T, D\u003e(deserializer: D) -\u003e std::result::Result\u003cT, D::Error\u003e\n where\n D: Deserializer\u003c'de\u003e,\n T: FromStr + serde::Deserialize\u003c'de\u003e + Clone,\n \u003cT as FromStr\u003e::Err: Display,\n {\n let value = serde_json::Value::deserialize(deserializer)?;\n match value {\n serde_json::Value::Number(n) =\u003e {\n if let Some(v) = n.as_u64() {\n T::deserialize(serde_json::Value::Number(serde_json::Number::from(v)))\n .map_err(|_| serde::de::Error::custom(\"Failed to deserialize number\"))\n } else if let Some(v) = n.as_f64() {\n T::deserialize(serde_json::Value::Number(serde_json::Number::from_f64(v).unwrap()))\n .map_err(|_| serde::de::Error::custom(\"Failed to deserialize number\"))\n } else {\n Err(serde::de::Error::custom(\"Invalid number format\"))\n }\n }\n serde_json::Value::String(s) =\u003e {\n s.parse::\u003cT\u003e().map_err(serde::de::Error::custom)\n }\n _ =\u003e Err(serde::de::Error::custom(\"Expected number or string\")),\n }\n }\n\n /// Deserialize optional number from string\n pub fn optional_number_from_string\u003c'de, T, D\u003e(\n deserializer: D,\n ) -\u003e std::result::Result\u003cOption\u003cT\u003e, D::Error\u003e\n where\n D: Deserializer\u003c'de\u003e,\n T: FromStr + serde::Deserialize\u003c'de\u003e + Clone,\n \u003cT as FromStr\u003e::Err: Display,\n {\n let value = serde_json::Value::deserialize(deserializer)?;\n match value {\n serde_json::Value::Null =\u003e Ok(None),\n serde_json::Value::Number(n) =\u003e {\n if let Some(v) = n.as_u64() {\n T::deserialize(serde_json::Value::Number(serde_json::Number::from(v)))\n .map(Some)\n .map_err(|_| serde::de::Error::custom(\"Failed to deserialize number\"))\n } else if let Some(v) = n.as_f64() {\n T::deserialize(serde_json::Value::Number(serde_json::Number::from_f64(v).unwrap()))\n .map(Some)\n .map_err(|_| serde::de::Error::custom(\"Failed to deserialize number\"))\n } else {\n Err(serde::de::Error::custom(\"Invalid number format\"))\n }\n }\n serde_json::Value::String(s) =\u003e {\n if s.is_empty() {\n Ok(None)\n } else {\n s.parse::\u003cT\u003e()\n .map(Some)\n .map_err(serde::de::Error::custom)\n }\n }\n _ =\u003e Err(serde::de::Error::custom(\"Expected number, string, or null\")),\n }\n }\n\n /// Deserialize DateTime from Unix timestamp\n pub fn datetime_from_timestamp\u003c'de, D\u003e(deserializer: D) -\u003e std::result::Result\u003cDateTime\u003cUtc\u003e, D::Error\u003e\n where\n D: Deserializer\u003c'de\u003e,\n {\n let timestamp = number_from_string::\u003cu64, D\u003e(deserializer)?;\n DateTime::from_timestamp(timestamp as i64, 0)\n .ok_or_else(|| serde::de::Error::custom(\"Invalid timestamp\"))\n }\n\n /// Deserialize optional DateTime from Unix timestamp\n pub fn optional_datetime_from_timestamp\u003c'de, D\u003e(\n deserializer: D,\n ) -\u003e std::result::Result\u003cOption\u003cDateTime\u003cUtc\u003e\u003e, D::Error\u003e\n where\n D: Deserializer\u003c'de\u003e,\n {\n match optional_number_from_string::\u003cu64, D\u003e(deserializer)? {\n Some(timestamp) =\u003e DateTime::from_timestamp(timestamp as i64, 0)\n .map(Some)\n .ok_or_else(|| serde::de::Error::custom(\"Invalid timestamp\")),\n None =\u003e Ok(None),\n }\n }\n}\n\n/// Raw API response types for efficient parsing\n#[derive(Debug, Deserialize)]\npub struct RawOrderBookResponse {\n pub market: String,\n pub asset_id: String,\n pub hash: String,\n #[serde(deserialize_with = \"deserializers::number_from_string\")]\n pub timestamp: u64,\n pub bids: Vec\u003cRawBookLevel\u003e,\n pub asks: Vec\u003cRawBookLevel\u003e,\n}\n\n#[derive(Debug, Deserialize)]\npub struct RawBookLevel {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub size: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct RawOrderResponse {\n pub id: String,\n pub status: String,\n pub market: String,\n pub asset_id: String,\n pub maker_address: String,\n pub owner: String,\n pub outcome: String,\n #[serde(rename = \"type\")]\n pub order_type: OrderType,\n pub side: Side,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub original_size: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub size_matched: Decimal,\n #[serde(deserialize_with = \"deserializers::number_from_string\")]\n pub expiration: u64,\n #[serde(deserialize_with = \"deserializers::number_from_string\")]\n pub created_at: u64,\n}\n\n#[derive(Debug, Deserialize)]\npub struct RawTradeResponse {\n pub id: String,\n pub market: String,\n pub asset_id: String,\n pub side: Side,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub size: Decimal,\n pub maker_address: String,\n pub taker_address: String,\n #[serde(deserialize_with = \"deserializers::number_from_string\")]\n pub timestamp: u64,\n}\n\n#[derive(Debug, Deserialize)]\npub struct RawMarketResponse {\n pub condition_id: String,\n pub tokens: [RawToken; 2],\n pub active: bool,\n pub closed: bool,\n pub question: String,\n pub description: String,\n pub category: Option\u003cString\u003e,\n pub end_date_iso: Option\u003cString\u003e,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub minimum_order_size: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub minimum_tick_size: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct RawToken {\n pub token_id: String,\n pub outcome: String,\n}\n\n/// Decoder implementations for converting raw responses to client types\npub trait Decoder\u003cT\u003e {\n fn decode(\u0026self) -\u003e Result\u003cT\u003e;\n}\n\nimpl Decoder\u003cOrderBook\u003e for RawOrderBookResponse {\n fn decode(\u0026self) -\u003e Result\u003cOrderBook\u003e {\n let timestamp = chrono::DateTime::from_timestamp(self.timestamp as i64, 0)\n .ok_or_else(|| PolyfillError::parse(\"Invalid timestamp\".to_string(), None))?;\n\n let bids = self\n .bids\n .iter()\n .map(|level| BookLevel {\n price: level.price,\n size: level.size,\n })\n .collect();\n\n let asks = self\n .asks\n .iter()\n .map(|level| BookLevel {\n price: level.price,\n size: level.size,\n })\n .collect();\n\n Ok(OrderBook {\n token_id: self.asset_id.clone(),\n timestamp,\n bids,\n asks,\n sequence: 0, // TODO: Get from response if available\n })\n }\n}\n\nimpl Decoder\u003cOrder\u003e for RawOrderResponse {\n fn decode(\u0026self) -\u003e Result\u003cOrder\u003e {\n let status = match self.status.as_str() {\n \"LIVE\" =\u003e OrderStatus::Live,\n \"CANCELLED\" =\u003e OrderStatus::Cancelled,\n \"FILLED\" =\u003e OrderStatus::Filled,\n \"PARTIAL\" =\u003e OrderStatus::Partial,\n \"EXPIRED\" =\u003e OrderStatus::Expired,\n _ =\u003e return Err(PolyfillError::parse(\n format!(\"Unknown order status: {}\", self.status),\n None,\n )),\n };\n\n let created_at = chrono::DateTime::from_timestamp(self.created_at as i64, 0)\n .ok_or_else(|| PolyfillError::parse(\"Invalid created_at timestamp\".to_string(), None))?;\n\n let expiration = if self.expiration \u003e 0 {\n Some(chrono::DateTime::from_timestamp(self.expiration as i64, 0)\n .ok_or_else(|| PolyfillError::parse(\"Invalid expiration timestamp\".to_string(), None))?)\n } else {\n None\n };\n\n Ok(Order {\n id: self.id.clone(),\n token_id: self.asset_id.clone(),\n side: self.side,\n price: self.price,\n original_size: self.original_size,\n filled_size: self.size_matched,\n remaining_size: self.original_size - self.size_matched,\n status,\n order_type: self.order_type,\n created_at,\n updated_at: created_at, // Use same as created for now\n expiration,\n client_id: None,\n })\n }\n}\n\nimpl Decoder\u003cFillEvent\u003e for RawTradeResponse {\n fn decode(\u0026self) -\u003e Result\u003cFillEvent\u003e {\n let timestamp = chrono::DateTime::from_timestamp(self.timestamp as i64, 0)\n .ok_or_else(|| PolyfillError::parse(\"Invalid trade timestamp\".to_string(), None))?;\n\n let maker_address = Address::from_str(\u0026self.maker_address)\n .map_err(|e| PolyfillError::parse(format!(\"Invalid maker address: {}\", e), None))?;\n\n let taker_address = Address::from_str(\u0026self.taker_address)\n .map_err(|e| PolyfillError::parse(format!(\"Invalid taker address: {}\", e), None))?;\n\n Ok(FillEvent {\n id: self.id.clone(),\n order_id: \"\".to_string(), // TODO: Get from response if available\n token_id: self.asset_id.clone(),\n side: self.side,\n price: self.price,\n size: self.size,\n timestamp,\n maker_address,\n taker_address,\n fee: Decimal::ZERO, // TODO: Calculate or get from response\n })\n }\n}\n\nimpl Decoder\u003cMarket\u003e for RawMarketResponse {\n fn decode(\u0026self) -\u003e Result\u003cMarket\u003e {\n let tokens = [\n Token {\n token_id: self.tokens[0].token_id.clone(),\n outcome: self.tokens[0].outcome.clone(),\n },\n Token {\n token_id: self.tokens[1].token_id.clone(),\n outcome: self.tokens[1].outcome.clone(),\n },\n ];\n\n Ok(Market {\n condition_id: self.condition_id.clone(),\n tokens,\n rewards: crate::types::Rewards {\n rates: None,\n min_size: Decimal::ZERO,\n max_spread: Decimal::ONE,\n event_start_date: None,\n event_end_date: None,\n in_game_multiplier: None,\n reward_epoch: None,\n },\n min_incentive_size: None,\n max_incentive_spread: None,\n active: self.active,\n closed: self.closed,\n question_id: self.condition_id.clone(), // Use condition_id as fallback\n minimum_order_size: self.minimum_order_size,\n minimum_tick_size: self.minimum_tick_size,\n description: self.description.clone(),\n category: self.category.clone(),\n end_date_iso: self.end_date_iso.clone(),\n game_start_time: None,\n question: self.question.clone(),\n market_slug: format!(\"market-{}\", self.condition_id), // Generate a slug\n seconds_delay: Decimal::ZERO,\n icon: String::new(),\n fpmm: String::new(),\n })\n }\n}\n\n/// WebSocket message parsing\npub fn parse_stream_message(raw: \u0026str) -\u003e Result\u003cStreamMessage\u003e {\n let value: Value = serde_json::from_str(raw)?;\n \n let msg_type = value[\"type\"]\n .as_str()\n .ok_or_else(|| PolyfillError::parse(\"Missing message type\".to_string(), None))?;\n\n match msg_type {\n \"book_update\" =\u003e {\n let data = value[\"data\"].clone();\n let delta: OrderDelta = serde_json::from_value(data)?;\n Ok(StreamMessage::BookUpdate { data: delta })\n }\n \"trade\" =\u003e {\n let data = value[\"data\"].clone();\n let raw_trade: RawTradeResponse = serde_json::from_value(data)?;\n let fill = raw_trade.decode()?;\n Ok(StreamMessage::Trade { data: fill })\n }\n \"order_update\" =\u003e {\n let data = value[\"data\"].clone();\n let raw_order: RawOrderResponse = serde_json::from_value(data)?;\n let order = raw_order.decode()?;\n Ok(StreamMessage::OrderUpdate { data: order })\n }\n \"heartbeat\" =\u003e {\n let timestamp = value[\"timestamp\"]\n .as_str()\n .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())\n .map(|dt| dt.with_timezone(\u0026Utc))\n .unwrap_or_else(|| Utc::now());\n Ok(StreamMessage::Heartbeat { timestamp })\n }\n _ =\u003e Err(PolyfillError::parse(\n format!(\"Unknown message type: {}\", msg_type),\n None,\n )),\n }\n}\n\n/// Batch parsing utilities for high-throughput scenarios\npub struct BatchDecoder {\n buffer: Vec\u003cu8\u003e,\n}\n\nimpl BatchDecoder {\n pub fn new() -\u003e Self {\n Self {\n buffer: Vec::with_capacity(8192),\n }\n }\n\n /// Parse multiple JSON objects from a byte stream\n pub fn parse_json_stream\u003cT\u003e(\u0026mut self, data: \u0026[u8]) -\u003e Result\u003cVec\u003cT\u003e\u003e\n where\n T: for\u003c'de\u003e serde::Deserialize\u003c'de\u003e,\n {\n self.buffer.extend_from_slice(data);\n let mut results = Vec::new();\n let mut start = 0;\n\n while let Some(end) = self.find_json_boundary(start) {\n let json_slice = \u0026self.buffer[start..end];\n if let Ok(obj) = serde_json::from_slice::\u003cT\u003e(json_slice) {\n results.push(obj);\n }\n start = end;\n }\n\n // Keep remaining incomplete data\n if start \u003e 0 {\n self.buffer.drain(0..start);\n }\n\n Ok(results)\n }\n\n /// Find the end of a JSON object in the buffer\n fn find_json_boundary(\u0026self, start: usize) -\u003e Option\u003cusize\u003e {\n let mut depth = 0;\n let mut in_string = false;\n let mut escaped = false;\n\n for (i, \u0026byte) in self.buffer[start..].iter().enumerate() {\n if escaped {\n escaped = false;\n continue;\n }\n\n match byte {\n b'\\\\' if in_string =\u003e escaped = true,\n b'\"' =\u003e in_string = !in_string,\n b'{' if !in_string =\u003e depth += 1,\n b'}' if !in_string =\u003e {\n depth -= 1;\n if depth == 0 {\n return Some(start + i + 1);\n }\n }\n _ =\u003e {}\n }\n }\n\n None\n }\n}\n\nimpl Default for BatchDecoder {\n fn default() -\u003e Self {\n Self::new()\n }\n}\n\n/// Optimized parsers for common data types\npub mod fast_parse {\n use super::*;\n\n /// Fast decimal parsing for prices\n #[inline]\n pub fn parse_decimal(s: \u0026str) -\u003e Result\u003cDecimal\u003e {\n Decimal::from_str(s)\n .map_err(|e| PolyfillError::parse(format!(\"Invalid decimal: {}\", e), None))\n }\n\n /// Fast address parsing\n #[inline]\n pub fn parse_address(s: \u0026str) -\u003e Result\u003cAddress\u003e {\n Address::from_str(s)\n .map_err(|e| PolyfillError::parse(format!(\"Invalid address: {}\", e), None))\n }\n\n /// Fast U256 parsing\n #[inline]\n pub fn parse_u256(s: \u0026str) -\u003e Result\u003cU256\u003e {\n U256::from_str_radix(s, 10)\n .map_err(|e| PolyfillError::parse(format!(\"Invalid U256: {}\", e), None))\n }\n\n /// Parse Side enum\n #[inline]\n pub fn parse_side(s: \u0026str) -\u003e Result\u003cSide\u003e {\n match s.to_uppercase().as_str() {\n \"BUY\" =\u003e Ok(Side::BUY),\n \"SELL\" =\u003e Ok(Side::SELL),\n _ =\u003e Err(PolyfillError::parse(format!(\"Invalid side: {}\", s), None)),\n }\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_parse_decimal() {\n let result = fast_parse::parse_decimal(\"123.456\").unwrap();\n assert_eq!(result, Decimal::from_str(\"123.456\").unwrap());\n }\n\n #[test]\n fn test_parse_side() {\n assert_eq!(fast_parse::parse_side(\"BUY\").unwrap(), Side::BUY);\n assert_eq!(fast_parse::parse_side(\"sell\").unwrap(), Side::SELL);\n assert!(fast_parse::parse_side(\"invalid\").is_err());\n }\n\n #[test]\n fn test_batch_decoder() {\n let mut decoder = BatchDecoder::new();\n let data = r#\"{\"test\":1}{\"test\":2}\"#.as_bytes();\n \n let results: Vec\u003cserde_json::Value\u003e = decoder.parse_json_stream(data).unwrap();\n assert_eq!(results.len(), 2);\n }\n} ","traces":[{"line":21,"address":[],"length":0,"stats":{"Line":0}},{"line":27,"address":[],"length":0,"stats":{"Line":0}},{"line":28,"address":[],"length":0,"stats":{"Line":0}},{"line":29,"address":[],"length":0,"stats":{"Line":0}},{"line":30,"address":[],"length":0,"stats":{"Line":0}},{"line":31,"address":[],"length":0,"stats":{"Line":0}},{"line":32,"address":[],"length":0,"stats":{"Line":0}},{"line":33,"address":[],"length":0,"stats":{"Line":0}},{"line":34,"address":[],"length":0,"stats":{"Line":0}},{"line":35,"address":[],"length":0,"stats":{"Line":0}},{"line":37,"address":[],"length":0,"stats":{"Line":0}},{"line":40,"address":[],"length":0,"stats":{"Line":0}},{"line":41,"address":[],"length":0,"stats":{"Line":0}},{"line":43,"address":[],"length":0,"stats":{"Line":0}},{"line":48,"address":[],"length":0,"stats":{"Line":0}},{"line":56,"address":[],"length":0,"stats":{"Line":0}},{"line":57,"address":[],"length":0,"stats":{"Line":0}},{"line":58,"address":[],"length":0,"stats":{"Line":0}},{"line":59,"address":[],"length":0,"stats":{"Line":0}},{"line":60,"address":[],"length":0,"stats":{"Line":0}},{"line":61,"address":[],"length":0,"stats":{"Line":0}},{"line":62,"address":[],"length":0,"stats":{"Line":0}},{"line":63,"address":[],"length":0,"stats":{"Line":0}},{"line":64,"address":[],"length":0,"stats":{"Line":0}},{"line":65,"address":[],"length":0,"stats":{"Line":0}},{"line":66,"address":[],"length":0,"stats":{"Line":0}},{"line":67,"address":[],"length":0,"stats":{"Line":0}},{"line":69,"address":[],"length":0,"stats":{"Line":0}},{"line":72,"address":[],"length":0,"stats":{"Line":0}},{"line":73,"address":[],"length":0,"stats":{"Line":0}},{"line":74,"address":[],"length":0,"stats":{"Line":0}},{"line":76,"address":[],"length":0,"stats":{"Line":0}},{"line":77,"address":[],"length":0,"stats":{"Line":0}},{"line":78,"address":[],"length":0,"stats":{"Line":0}},{"line":81,"address":[],"length":0,"stats":{"Line":0}},{"line":86,"address":[],"length":0,"stats":{"Line":0}},{"line":90,"address":[],"length":0,"stats":{"Line":0}},{"line":91,"address":[],"length":0,"stats":{"Line":0}},{"line":92,"address":[],"length":0,"stats":{"Line":0}},{"line":96,"address":[],"length":0,"stats":{"Line":0}},{"line":102,"address":[],"length":0,"stats":{"Line":0}},{"line":103,"address":[],"length":0,"stats":{"Line":0}},{"line":104,"address":[],"length":0,"stats":{"Line":0}},{"line":105,"address":[],"length":0,"stats":{"Line":0}},{"line":106,"address":[],"length":0,"stats":{"Line":0}},{"line":199,"address":[],"length":0,"stats":{"Line":0}},{"line":200,"address":[],"length":0,"stats":{"Line":0}},{"line":201,"address":[],"length":0,"stats":{"Line":0}},{"line":203,"address":[],"length":0,"stats":{"Line":0}},{"line":204,"address":[],"length":0,"stats":{"Line":0}},{"line":206,"address":[],"length":0,"stats":{"Line":0}},{"line":207,"address":[],"length":0,"stats":{"Line":0}},{"line":208,"address":[],"length":0,"stats":{"Line":0}},{"line":212,"address":[],"length":0,"stats":{"Line":0}},{"line":213,"address":[],"length":0,"stats":{"Line":0}},{"line":215,"address":[],"length":0,"stats":{"Line":0}},{"line":216,"address":[],"length":0,"stats":{"Line":0}},{"line":217,"address":[],"length":0,"stats":{"Line":0}},{"line":221,"address":[],"length":0,"stats":{"Line":0}},{"line":222,"address":[],"length":0,"stats":{"Line":0}},{"line":223,"address":[],"length":0,"stats":{"Line":0}},{"line":224,"address":[],"length":0,"stats":{"Line":0}},{"line":225,"address":[],"length":0,"stats":{"Line":0}},{"line":226,"address":[],"length":0,"stats":{"Line":0}},{"line":232,"address":[],"length":0,"stats":{"Line":0}},{"line":233,"address":[],"length":0,"stats":{"Line":0}},{"line":234,"address":[],"length":0,"stats":{"Line":0}},{"line":235,"address":[],"length":0,"stats":{"Line":0}},{"line":236,"address":[],"length":0,"stats":{"Line":0}},{"line":237,"address":[],"length":0,"stats":{"Line":0}},{"line":238,"address":[],"length":0,"stats":{"Line":0}},{"line":239,"address":[],"length":0,"stats":{"Line":0}},{"line":240,"address":[],"length":0,"stats":{"Line":0}},{"line":241,"address":[],"length":0,"stats":{"Line":0}},{"line":245,"address":[],"length":0,"stats":{"Line":0}},{"line":246,"address":[],"length":0,"stats":{"Line":0}},{"line":248,"address":[],"length":0,"stats":{"Line":0}},{"line":249,"address":[],"length":0,"stats":{"Line":0}},{"line":250,"address":[],"length":0,"stats":{"Line":0}},{"line":252,"address":[],"length":0,"stats":{"Line":0}},{"line":255,"address":[],"length":0,"stats":{"Line":0}},{"line":256,"address":[],"length":0,"stats":{"Line":0}},{"line":257,"address":[],"length":0,"stats":{"Line":0}},{"line":258,"address":[],"length":0,"stats":{"Line":0}},{"line":259,"address":[],"length":0,"stats":{"Line":0}},{"line":260,"address":[],"length":0,"stats":{"Line":0}},{"line":261,"address":[],"length":0,"stats":{"Line":0}},{"line":262,"address":[],"length":0,"stats":{"Line":0}},{"line":263,"address":[],"length":0,"stats":{"Line":0}},{"line":264,"address":[],"length":0,"stats":{"Line":0}},{"line":265,"address":[],"length":0,"stats":{"Line":0}},{"line":266,"address":[],"length":0,"stats":{"Line":0}},{"line":267,"address":[],"length":0,"stats":{"Line":0}},{"line":268,"address":[],"length":0,"stats":{"Line":0}},{"line":274,"address":[],"length":0,"stats":{"Line":0}},{"line":275,"address":[],"length":0,"stats":{"Line":0}},{"line":276,"address":[],"length":0,"stats":{"Line":0}},{"line":278,"address":[],"length":0,"stats":{"Line":0}},{"line":279,"address":[],"length":0,"stats":{"Line":0}},{"line":281,"address":[],"length":0,"stats":{"Line":0}},{"line":282,"address":[],"length":0,"stats":{"Line":0}},{"line":284,"address":[],"length":0,"stats":{"Line":0}},{"line":285,"address":[],"length":0,"stats":{"Line":0}},{"line":286,"address":[],"length":0,"stats":{"Line":0}},{"line":287,"address":[],"length":0,"stats":{"Line":0}},{"line":288,"address":[],"length":0,"stats":{"Line":0}},{"line":289,"address":[],"length":0,"stats":{"Line":0}},{"line":290,"address":[],"length":0,"stats":{"Line":0}},{"line":291,"address":[],"length":0,"stats":{"Line":0}},{"line":292,"address":[],"length":0,"stats":{"Line":0}},{"line":293,"address":[],"length":0,"stats":{"Line":0}},{"line":294,"address":[],"length":0,"stats":{"Line":0}},{"line":300,"address":[],"length":0,"stats":{"Line":0}},{"line":301,"address":[],"length":0,"stats":{"Line":0}},{"line":302,"address":[],"length":0,"stats":{"Line":0}},{"line":303,"address":[],"length":0,"stats":{"Line":0}},{"line":304,"address":[],"length":0,"stats":{"Line":0}},{"line":306,"address":[],"length":0,"stats":{"Line":0}},{"line":307,"address":[],"length":0,"stats":{"Line":0}},{"line":308,"address":[],"length":0,"stats":{"Line":0}},{"line":312,"address":[],"length":0,"stats":{"Line":0}},{"line":313,"address":[],"length":0,"stats":{"Line":0}},{"line":314,"address":[],"length":0,"stats":{"Line":0}},{"line":315,"address":[],"length":0,"stats":{"Line":0}},{"line":316,"address":[],"length":0,"stats":{"Line":0}},{"line":317,"address":[],"length":0,"stats":{"Line":0}},{"line":318,"address":[],"length":0,"stats":{"Line":0}},{"line":319,"address":[],"length":0,"stats":{"Line":0}},{"line":320,"address":[],"length":0,"stats":{"Line":0}},{"line":321,"address":[],"length":0,"stats":{"Line":0}},{"line":322,"address":[],"length":0,"stats":{"Line":0}},{"line":324,"address":[],"length":0,"stats":{"Line":0}},{"line":325,"address":[],"length":0,"stats":{"Line":0}},{"line":326,"address":[],"length":0,"stats":{"Line":0}},{"line":327,"address":[],"length":0,"stats":{"Line":0}},{"line":328,"address":[],"length":0,"stats":{"Line":0}},{"line":329,"address":[],"length":0,"stats":{"Line":0}},{"line":330,"address":[],"length":0,"stats":{"Line":0}},{"line":331,"address":[],"length":0,"stats":{"Line":0}},{"line":332,"address":[],"length":0,"stats":{"Line":0}},{"line":333,"address":[],"length":0,"stats":{"Line":0}},{"line":334,"address":[],"length":0,"stats":{"Line":0}},{"line":335,"address":[],"length":0,"stats":{"Line":0}},{"line":336,"address":[],"length":0,"stats":{"Line":0}},{"line":337,"address":[],"length":0,"stats":{"Line":0}},{"line":338,"address":[],"length":0,"stats":{"Line":0}},{"line":339,"address":[],"length":0,"stats":{"Line":0}},{"line":345,"address":[],"length":0,"stats":{"Line":0}},{"line":346,"address":[],"length":0,"stats":{"Line":0}},{"line":348,"address":[],"length":0,"stats":{"Line":0}},{"line":350,"address":[],"length":0,"stats":{"Line":0}},{"line":352,"address":[],"length":0,"stats":{"Line":0}},{"line":353,"address":[],"length":0,"stats":{"Line":0}},{"line":354,"address":[],"length":0,"stats":{"Line":0}},{"line":355,"address":[],"length":0,"stats":{"Line":0}},{"line":356,"address":[],"length":0,"stats":{"Line":0}},{"line":358,"address":[],"length":0,"stats":{"Line":0}},{"line":359,"address":[],"length":0,"stats":{"Line":0}},{"line":360,"address":[],"length":0,"stats":{"Line":0}},{"line":361,"address":[],"length":0,"stats":{"Line":0}},{"line":362,"address":[],"length":0,"stats":{"Line":0}},{"line":364,"address":[],"length":0,"stats":{"Line":0}},{"line":365,"address":[],"length":0,"stats":{"Line":0}},{"line":366,"address":[],"length":0,"stats":{"Line":0}},{"line":367,"address":[],"length":0,"stats":{"Line":0}},{"line":368,"address":[],"length":0,"stats":{"Line":0}},{"line":370,"address":[],"length":0,"stats":{"Line":0}},{"line":371,"address":[],"length":0,"stats":{"Line":0}},{"line":373,"address":[],"length":0,"stats":{"Line":0}},{"line":374,"address":[],"length":0,"stats":{"Line":0}},{"line":375,"address":[],"length":0,"stats":{"Line":0}},{"line":376,"address":[],"length":0,"stats":{"Line":0}},{"line":378,"address":[],"length":0,"stats":{"Line":0}},{"line":379,"address":[],"length":0,"stats":{"Line":0}},{"line":380,"address":[],"length":0,"stats":{"Line":0}},{"line":391,"address":[],"length":0,"stats":{"Line":1}},{"line":393,"address":[],"length":0,"stats":{"Line":1}},{"line":398,"address":[],"length":0,"stats":{"Line":1}},{"line":402,"address":[],"length":0,"stats":{"Line":3}},{"line":403,"address":[],"length":0,"stats":{"Line":2}},{"line":404,"address":[],"length":0,"stats":{"Line":2}},{"line":406,"address":[],"length":0,"stats":{"Line":8}},{"line":407,"address":[],"length":0,"stats":{"Line":6}},{"line":408,"address":[],"length":0,"stats":{"Line":6}},{"line":409,"address":[],"length":0,"stats":{"Line":4}},{"line":411,"address":[],"length":0,"stats":{"Line":2}},{"line":415,"address":[],"length":0,"stats":{"Line":2}},{"line":416,"address":[],"length":0,"stats":{"Line":2}},{"line":419,"address":[],"length":0,"stats":{"Line":1}},{"line":423,"address":[],"length":0,"stats":{"Line":3}},{"line":424,"address":[],"length":0,"stats":{"Line":6}},{"line":425,"address":[],"length":0,"stats":{"Line":6}},{"line":426,"address":[],"length":0,"stats":{"Line":6}},{"line":428,"address":[],"length":0,"stats":{"Line":49}},{"line":429,"address":[],"length":0,"stats":{"Line":20}},{"line":430,"address":[],"length":0,"stats":{"Line":0}},{"line":431,"address":[],"length":0,"stats":{"Line":0}},{"line":434,"address":[],"length":0,"stats":{"Line":0}},{"line":435,"address":[],"length":0,"stats":{"Line":0}},{"line":436,"address":[],"length":0,"stats":{"Line":4}},{"line":437,"address":[],"length":0,"stats":{"Line":4}},{"line":438,"address":[],"length":0,"stats":{"Line":2}},{"line":439,"address":[],"length":0,"stats":{"Line":2}},{"line":440,"address":[],"length":0,"stats":{"Line":2}},{"line":441,"address":[],"length":0,"stats":{"Line":2}},{"line":444,"address":[],"length":0,"stats":{"Line":12}},{"line":448,"address":[],"length":0,"stats":{"Line":1}},{"line":453,"address":[],"length":0,"stats":{"Line":0}},{"line":454,"address":[],"length":0,"stats":{"Line":0}},{"line":464,"address":[],"length":0,"stats":{"Line":1}},{"line":465,"address":[],"length":0,"stats":{"Line":2}},{"line":466,"address":[],"length":0,"stats":{"Line":1}},{"line":471,"address":[],"length":0,"stats":{"Line":0}},{"line":472,"address":[],"length":0,"stats":{"Line":0}},{"line":473,"address":[],"length":0,"stats":{"Line":0}},{"line":478,"address":[],"length":0,"stats":{"Line":0}},{"line":479,"address":[],"length":0,"stats":{"Line":0}},{"line":480,"address":[],"length":0,"stats":{"Line":0}},{"line":485,"address":[],"length":0,"stats":{"Line":3}},{"line":486,"address":[],"length":0,"stats":{"Line":3}},{"line":487,"address":[],"length":0,"stats":{"Line":4}},{"line":488,"address":[],"length":0,"stats":{"Line":3}},{"line":489,"address":[],"length":0,"stats":{"Line":3}}],"covered":36,"coverable":223},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","errors.rs"],"content":"//! Error types for the Polymarket client\n//!\n//! This module defines all error types used throughout the client, designed\n//! for clear error handling in trading environments where fast error recovery\n//! is critical.\n\nuse thiserror::Error;\nuse std::time::Duration;\n\n/// Main error type for the Polymarket client\n#[derive(Error, Debug)]\npub enum PolyfillError {\n /// Network-related errors (retryable)\n #[error(\"Network error: {message}\")]\n Network { \n message: String,\n #[source]\n source: Option\u003cBox\u003cdyn std::error::Error + Send + Sync\u003e\u003e,\n },\n\n /// API errors from Polymarket\n #[error(\"API error ({status}): {message}\")]\n Api { \n status: u16,\n message: String,\n error_code: Option\u003cString\u003e,\n },\n\n /// Authentication/authorization errors\n #[error(\"Auth error: {message}\")]\n Auth { \n message: String,\n kind: AuthErrorKind,\n },\n\n /// Order-related errors\n #[error(\"Order error: {message}\")]\n Order { \n message: String,\n kind: OrderErrorKind,\n },\n\n /// Market data errors\n #[error(\"Market data error: {message}\")]\n MarketData { \n message: String,\n kind: MarketDataErrorKind,\n },\n\n /// Configuration errors\n #[error(\"Config error: {message}\")]\n Config { \n message: String,\n },\n\n /// Parsing/serialization errors\n #[error(\"Parse error: {message}\")]\n Parse { \n message: String,\n #[source]\n source: Option\u003cBox\u003cdyn std::error::Error + Send + Sync\u003e\u003e,\n },\n\n /// Timeout errors\n #[error(\"Timeout error: operation timed out after {duration:?}\")]\n Timeout { \n duration: Duration,\n operation: String,\n },\n\n /// Rate limiting errors\n #[error(\"Rate limit exceeded: {message}\")]\n RateLimit { \n message: String,\n retry_after: Option\u003cDuration\u003e,\n },\n\n /// WebSocket/streaming errors\n #[error(\"Stream error: {message}\")]\n Stream { \n message: String,\n kind: StreamErrorKind,\n },\n\n /// Validation errors\n #[error(\"Validation error: {message}\")]\n Validation { \n message: String,\n field: Option\u003cString\u003e,\n },\n\n /// Internal errors (bugs)\n #[error(\"Internal error: {message}\")]\n Internal { \n message: String,\n #[source]\n source: Option\u003cBox\u003cdyn std::error::Error + Send + Sync\u003e\u003e,\n },\n}\n\n/// Authentication error subcategories\n#[derive(Debug, Clone, PartialEq)]\npub enum AuthErrorKind {\n InvalidCredentials,\n ExpiredCredentials,\n InsufficientPermissions,\n SignatureError,\n NonceError,\n}\n\n/// Order error subcategories\n#[derive(Debug, Clone, PartialEq)]\npub enum OrderErrorKind {\n InvalidPrice,\n InvalidSize,\n InsufficientBalance,\n MarketClosed,\n DuplicateOrder,\n OrderNotFound,\n CancellationFailed,\n ExecutionFailed,\n SizeConstraint,\n PriceConstraint,\n}\n\n/// Market data error subcategories\n#[derive(Debug, Clone, PartialEq)]\npub enum MarketDataErrorKind {\n TokenNotFound,\n MarketNotFound,\n StaleData,\n IncompleteData,\n BookUnavailable,\n}\n\n/// Streaming error subcategories\n#[derive(Debug, Clone, PartialEq)]\npub enum StreamErrorKind {\n ConnectionFailed,\n ConnectionLost,\n SubscriptionFailed,\n MessageCorrupted,\n Reconnecting,\n}\n\nimpl PolyfillError {\n /// Check if this error is retryable\n pub fn is_retryable(\u0026self) -\u003e bool {\n match self {\n PolyfillError::Network { .. } =\u003e true,\n PolyfillError::Api { status, .. } =\u003e {\n // 5xx errors are typically retryable\n *status \u003e= 500 \u0026\u0026 *status \u003c 600\n },\n PolyfillError::Timeout { .. } =\u003e true,\n PolyfillError::RateLimit { .. } =\u003e true,\n PolyfillError::Stream { kind, .. } =\u003e {\n matches!(kind, StreamErrorKind::ConnectionLost | StreamErrorKind::Reconnecting)\n },\n _ =\u003e false,\n }\n }\n\n /// Get suggested retry delay\n pub fn retry_delay(\u0026self) -\u003e Option\u003cDuration\u003e {\n match self {\n PolyfillError::Network { .. } =\u003e Some(Duration::from_millis(100)),\n PolyfillError::Api { status, .. } =\u003e {\n if *status \u003e= 500 {\n Some(Duration::from_millis(500))\n } else {\n None\n }\n },\n PolyfillError::Timeout { .. } =\u003e Some(Duration::from_millis(50)),\n PolyfillError::RateLimit { retry_after, .. } =\u003e {\n retry_after.or(Some(Duration::from_secs(1)))\n },\n PolyfillError::Stream { .. } =\u003e Some(Duration::from_millis(250)),\n _ =\u003e None,\n }\n }\n\n /// Check if this is a critical error that should stop trading\n pub fn is_critical(\u0026self) -\u003e bool {\n match self {\n PolyfillError::Auth { .. } =\u003e true,\n PolyfillError::Config { .. } =\u003e true,\n PolyfillError::Internal { .. } =\u003e true,\n PolyfillError::Order { kind, .. } =\u003e {\n matches!(kind, OrderErrorKind::InsufficientBalance)\n },\n _ =\u003e false,\n }\n }\n\n /// Get error category for metrics\n pub fn category(\u0026self) -\u003e \u0026'static str {\n match self {\n PolyfillError::Network { .. } =\u003e \"network\",\n PolyfillError::Api { .. } =\u003e \"api\",\n PolyfillError::Auth { .. } =\u003e \"auth\",\n PolyfillError::Order { .. } =\u003e \"order\",\n PolyfillError::MarketData { .. } =\u003e \"market_data\",\n PolyfillError::Config { .. } =\u003e \"config\",\n PolyfillError::Parse { .. } =\u003e \"parse\",\n PolyfillError::Timeout { .. } =\u003e \"timeout\",\n PolyfillError::RateLimit { .. } =\u003e \"rate_limit\",\n PolyfillError::Stream { .. } =\u003e \"stream\",\n PolyfillError::Validation { .. } =\u003e \"validation\",\n PolyfillError::Internal { .. } =\u003e \"internal\",\n }\n }\n}\n\n// Convenience constructors\nimpl PolyfillError {\n pub fn network\u003cE: std::error::Error + Send + Sync + 'static\u003e(\n message: impl Into\u003cString\u003e,\n source: E,\n ) -\u003e Self {\n Self::Network {\n message: message.into(),\n source: Some(Box::new(source)),\n }\n }\n\n pub fn api(status: u16, message: impl Into\u003cString\u003e) -\u003e Self {\n Self::Api {\n status,\n message: message.into(),\n error_code: None,\n }\n }\n\n pub fn auth(message: impl Into\u003cString\u003e) -\u003e Self {\n Self::Auth {\n message: message.into(),\n kind: AuthErrorKind::SignatureError,\n }\n }\n\n pub fn crypto(message: impl Into\u003cString\u003e) -\u003e Self {\n Self::Auth {\n message: message.into(),\n kind: AuthErrorKind::SignatureError,\n }\n }\n\n pub fn order(message: impl Into\u003cString\u003e, kind: OrderErrorKind) -\u003e Self {\n Self::Order {\n message: message.into(),\n kind,\n }\n }\n\n pub fn market_data(message: impl Into\u003cString\u003e, kind: MarketDataErrorKind) -\u003e Self {\n Self::MarketData {\n message: message.into(),\n kind,\n }\n }\n\n pub fn config(message: impl Into\u003cString\u003e) -\u003e Self {\n Self::Config {\n message: message.into(),\n }\n }\n\n pub fn parse(message: impl Into\u003cString\u003e, source: Option\u003cBox\u003cdyn std::error::Error + Send + Sync\u003e\u003e) -\u003e Self {\n Self::Parse {\n message: message.into(),\n source,\n }\n }\n\n pub fn timeout(duration: Duration, operation: impl Into\u003cString\u003e) -\u003e Self {\n Self::Timeout {\n duration,\n operation: operation.into(),\n }\n }\n\n pub fn rate_limit(message: impl Into\u003cString\u003e) -\u003e Self {\n Self::RateLimit {\n message: message.into(),\n retry_after: None,\n }\n }\n\n pub fn stream(message: impl Into\u003cString\u003e, kind: StreamErrorKind) -\u003e Self {\n Self::Stream {\n message: message.into(),\n kind,\n }\n }\n\n pub fn validation(message: impl Into\u003cString\u003e) -\u003e Self {\n Self::Validation {\n message: message.into(),\n field: None,\n }\n }\n\n pub fn internal\u003cE: std::error::Error + Send + Sync + 'static\u003e(\n message: impl Into\u003cString\u003e,\n source: E,\n ) -\u003e Self {\n Self::Internal {\n message: message.into(),\n source: Some(Box::new(source)),\n }\n }\n\n pub fn internal_simple(message: impl Into\u003cString\u003e) -\u003e Self {\n Self::Internal {\n message: message.into(),\n source: None,\n }\n }\n}\n\n// Implement From for common external error types\nimpl From\u003creqwest::Error\u003e for PolyfillError {\n fn from(err: reqwest::Error) -\u003e Self {\n if err.is_timeout() {\n PolyfillError::Timeout {\n duration: Duration::from_secs(30), // default timeout\n operation: \"HTTP request\".to_string(),\n }\n } else if err.is_connect() || err.is_request() {\n PolyfillError::network(\"HTTP request failed\", err)\n } else {\n PolyfillError::internal(\"Unexpected reqwest error\", err)\n }\n }\n}\n\nimpl From\u003cserde_json::Error\u003e for PolyfillError {\n fn from(err: serde_json::Error) -\u003e Self {\n PolyfillError::Parse {\n message: format!(\"JSON parsing failed: {}\", err),\n source: Some(Box::new(err)),\n }\n }\n}\n\nimpl From\u003curl::ParseError\u003e for PolyfillError {\n fn from(err: url::ParseError) -\u003e Self {\n PolyfillError::config(format!(\"Invalid URL: {}\", err))\n }\n}\n\n#[cfg(feature = \"stream\")]\nimpl From\u003ctokio_tungstenite::tungstenite::Error\u003e for PolyfillError {\n fn from(err: tokio_tungstenite::tungstenite::Error) -\u003e Self {\n use tokio_tungstenite::tungstenite::Error as WsError;\n \n let kind = match \u0026err {\n WsError::ConnectionClosed | WsError::AlreadyClosed =\u003e StreamErrorKind::ConnectionLost,\n WsError::Io(_) =\u003e StreamErrorKind::ConnectionFailed,\n WsError::Protocol(_) =\u003e StreamErrorKind::MessageCorrupted,\n _ =\u003e StreamErrorKind::ConnectionFailed,\n };\n\n PolyfillError::stream(format!(\"WebSocket error: {}\", err), kind)\n }\n}\n\n// Manual Clone implementation since Box\u003cdyn Error\u003e doesn't implement Clone\nimpl Clone for PolyfillError {\n fn clone(\u0026self) -\u003e Self {\n match self {\n PolyfillError::Network { message, source: _ } =\u003e {\n PolyfillError::Network { \n message: message.clone(), \n source: None \n }\n }\n PolyfillError::Api { status, message, error_code } =\u003e {\n PolyfillError::Api { \n status: *status, \n message: message.clone(), \n error_code: error_code.clone() \n }\n }\n PolyfillError::Auth { message, kind } =\u003e {\n PolyfillError::Auth { \n message: message.clone(), \n kind: kind.clone() \n }\n }\n PolyfillError::Order { message, kind } =\u003e {\n PolyfillError::Order { \n message: message.clone(), \n kind: kind.clone() \n }\n }\n PolyfillError::MarketData { message, kind } =\u003e {\n PolyfillError::MarketData { \n message: message.clone(), \n kind: kind.clone() \n }\n }\n PolyfillError::Config { message } =\u003e {\n PolyfillError::Config { \n message: message.clone() \n }\n }\n PolyfillError::Parse { message, source: _ } =\u003e {\n PolyfillError::Parse { \n message: message.clone(), \n source: None \n }\n }\n PolyfillError::Timeout { duration, operation } =\u003e {\n PolyfillError::Timeout { \n duration: *duration, \n operation: operation.clone() \n }\n }\n PolyfillError::RateLimit { message, retry_after } =\u003e {\n PolyfillError::RateLimit { \n message: message.clone(), \n retry_after: *retry_after \n }\n }\n PolyfillError::Stream { message, kind } =\u003e {\n PolyfillError::Stream { \n message: message.clone(), \n kind: kind.clone() \n }\n }\n PolyfillError::Validation { message, field } =\u003e {\n PolyfillError::Validation { \n message: message.clone(), \n field: field.clone() \n }\n }\n PolyfillError::Internal { message, source: _ } =\u003e {\n PolyfillError::Internal { \n message: message.clone(), \n source: None \n }\n }\n }\n }\n}\n\n/// Result type alias for convenience\npub type Result\u003cT\u003e = std::result::Result\u003cT, PolyfillError\u003e; ","traces":[{"line":148,"address":[],"length":0,"stats":{"Line":0}},{"line":149,"address":[],"length":0,"stats":{"Line":0}},{"line":150,"address":[],"length":0,"stats":{"Line":0}},{"line":151,"address":[],"length":0,"stats":{"Line":0}},{"line":153,"address":[],"length":0,"stats":{"Line":0}},{"line":155,"address":[],"length":0,"stats":{"Line":0}},{"line":156,"address":[],"length":0,"stats":{"Line":0}},{"line":157,"address":[],"length":0,"stats":{"Line":0}},{"line":158,"address":[],"length":0,"stats":{"Line":0}},{"line":160,"address":[],"length":0,"stats":{"Line":0}},{"line":165,"address":[],"length":0,"stats":{"Line":0}},{"line":166,"address":[],"length":0,"stats":{"Line":0}},{"line":167,"address":[],"length":0,"stats":{"Line":0}},{"line":168,"address":[],"length":0,"stats":{"Line":0}},{"line":169,"address":[],"length":0,"stats":{"Line":0}},{"line":170,"address":[],"length":0,"stats":{"Line":0}},{"line":172,"address":[],"length":0,"stats":{"Line":0}},{"line":175,"address":[],"length":0,"stats":{"Line":0}},{"line":176,"address":[],"length":0,"stats":{"Line":0}},{"line":177,"address":[],"length":0,"stats":{"Line":0}},{"line":179,"address":[],"length":0,"stats":{"Line":0}},{"line":180,"address":[],"length":0,"stats":{"Line":0}},{"line":185,"address":[],"length":0,"stats":{"Line":0}},{"line":186,"address":[],"length":0,"stats":{"Line":0}},{"line":187,"address":[],"length":0,"stats":{"Line":0}},{"line":188,"address":[],"length":0,"stats":{"Line":0}},{"line":189,"address":[],"length":0,"stats":{"Line":0}},{"line":190,"address":[],"length":0,"stats":{"Line":0}},{"line":191,"address":[],"length":0,"stats":{"Line":0}},{"line":193,"address":[],"length":0,"stats":{"Line":0}},{"line":198,"address":[],"length":0,"stats":{"Line":0}},{"line":199,"address":[],"length":0,"stats":{"Line":0}},{"line":200,"address":[],"length":0,"stats":{"Line":0}},{"line":201,"address":[],"length":0,"stats":{"Line":0}},{"line":202,"address":[],"length":0,"stats":{"Line":0}},{"line":203,"address":[],"length":0,"stats":{"Line":0}},{"line":204,"address":[],"length":0,"stats":{"Line":0}},{"line":205,"address":[],"length":0,"stats":{"Line":0}},{"line":206,"address":[],"length":0,"stats":{"Line":0}},{"line":207,"address":[],"length":0,"stats":{"Line":0}},{"line":208,"address":[],"length":0,"stats":{"Line":0}},{"line":209,"address":[],"length":0,"stats":{"Line":0}},{"line":210,"address":[],"length":0,"stats":{"Line":0}},{"line":211,"address":[],"length":0,"stats":{"Line":0}},{"line":218,"address":[],"length":0,"stats":{"Line":0}},{"line":223,"address":[],"length":0,"stats":{"Line":0}},{"line":224,"address":[],"length":0,"stats":{"Line":0}},{"line":228,"address":[],"length":0,"stats":{"Line":0}},{"line":231,"address":[],"length":0,"stats":{"Line":0}},{"line":236,"address":[],"length":0,"stats":{"Line":0}},{"line":238,"address":[],"length":0,"stats":{"Line":0}},{"line":243,"address":[],"length":0,"stats":{"Line":0}},{"line":245,"address":[],"length":0,"stats":{"Line":0}},{"line":250,"address":[],"length":0,"stats":{"Line":1}},{"line":252,"address":[],"length":0,"stats":{"Line":2}},{"line":257,"address":[],"length":0,"stats":{"Line":0}},{"line":259,"address":[],"length":0,"stats":{"Line":0}},{"line":264,"address":[],"length":0,"stats":{"Line":0}},{"line":266,"address":[],"length":0,"stats":{"Line":0}},{"line":270,"address":[],"length":0,"stats":{"Line":1}},{"line":272,"address":[],"length":0,"stats":{"Line":2}},{"line":277,"address":[],"length":0,"stats":{"Line":0}},{"line":280,"address":[],"length":0,"stats":{"Line":0}},{"line":284,"address":[],"length":0,"stats":{"Line":0}},{"line":286,"address":[],"length":0,"stats":{"Line":0}},{"line":291,"address":[],"length":0,"stats":{"Line":0}},{"line":293,"address":[],"length":0,"stats":{"Line":0}},{"line":298,"address":[],"length":0,"stats":{"Line":1}},{"line":300,"address":[],"length":0,"stats":{"Line":2}},{"line":305,"address":[],"length":0,"stats":{"Line":0}},{"line":310,"address":[],"length":0,"stats":{"Line":0}},{"line":311,"address":[],"length":0,"stats":{"Line":0}},{"line":315,"address":[],"length":0,"stats":{"Line":0}},{"line":317,"address":[],"length":0,"stats":{"Line":0}},{"line":325,"address":[],"length":0,"stats":{"Line":0}},{"line":326,"address":[],"length":0,"stats":{"Line":0}},{"line":328,"address":[],"length":0,"stats":{"Line":0}},{"line":329,"address":[],"length":0,"stats":{"Line":0}},{"line":331,"address":[],"length":0,"stats":{"Line":0}},{"line":332,"address":[],"length":0,"stats":{"Line":0}},{"line":334,"address":[],"length":0,"stats":{"Line":0}},{"line":340,"address":[],"length":0,"stats":{"Line":0}},{"line":342,"address":[],"length":0,"stats":{"Line":0}},{"line":343,"address":[],"length":0,"stats":{"Line":0}},{"line":349,"address":[],"length":0,"stats":{"Line":0}},{"line":350,"address":[],"length":0,"stats":{"Line":0}},{"line":356,"address":[],"length":0,"stats":{"Line":0}},{"line":359,"address":[],"length":0,"stats":{"Line":0}},{"line":360,"address":[],"length":0,"stats":{"Line":0}},{"line":361,"address":[],"length":0,"stats":{"Line":0}},{"line":362,"address":[],"length":0,"stats":{"Line":0}},{"line":363,"address":[],"length":0,"stats":{"Line":0}},{"line":366,"address":[],"length":0,"stats":{"Line":0}},{"line":372,"address":[],"length":0,"stats":{"Line":0}},{"line":373,"address":[],"length":0,"stats":{"Line":0}},{"line":374,"address":[],"length":0,"stats":{"Line":0}},{"line":376,"address":[],"length":0,"stats":{"Line":0}},{"line":380,"address":[],"length":0,"stats":{"Line":0}},{"line":382,"address":[],"length":0,"stats":{"Line":0}},{"line":383,"address":[],"length":0,"stats":{"Line":0}},{"line":384,"address":[],"length":0,"stats":{"Line":0}},{"line":387,"address":[],"length":0,"stats":{"Line":0}},{"line":389,"address":[],"length":0,"stats":{"Line":0}},{"line":390,"address":[],"length":0,"stats":{"Line":0}},{"line":393,"address":[],"length":0,"stats":{"Line":0}},{"line":395,"address":[],"length":0,"stats":{"Line":0}},{"line":396,"address":[],"length":0,"stats":{"Line":0}},{"line":399,"address":[],"length":0,"stats":{"Line":0}},{"line":401,"address":[],"length":0,"stats":{"Line":0}},{"line":402,"address":[],"length":0,"stats":{"Line":0}},{"line":405,"address":[],"length":0,"stats":{"Line":0}},{"line":407,"address":[],"length":0,"stats":{"Line":0}},{"line":410,"address":[],"length":0,"stats":{"Line":0}},{"line":412,"address":[],"length":0,"stats":{"Line":0}},{"line":416,"address":[],"length":0,"stats":{"Line":0}},{"line":418,"address":[],"length":0,"stats":{"Line":0}},{"line":419,"address":[],"length":0,"stats":{"Line":0}},{"line":422,"address":[],"length":0,"stats":{"Line":0}},{"line":424,"address":[],"length":0,"stats":{"Line":0}},{"line":425,"address":[],"length":0,"stats":{"Line":0}},{"line":428,"address":[],"length":0,"stats":{"Line":0}},{"line":430,"address":[],"length":0,"stats":{"Line":0}},{"line":431,"address":[],"length":0,"stats":{"Line":0}},{"line":434,"address":[],"length":0,"stats":{"Line":0}},{"line":436,"address":[],"length":0,"stats":{"Line":0}},{"line":437,"address":[],"length":0,"stats":{"Line":0}},{"line":440,"address":[],"length":0,"stats":{"Line":0}},{"line":442,"address":[],"length":0,"stats":{"Line":0}}],"covered":6,"coverable":128},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","fill.rs"],"content":"//! Trade execution and fill handling for Polymarket client\n//!\n//! This module provides high-performance trade execution logic and\n//! fill event processing for latency-sensitive trading environments.\n\nuse crate::errors::{PolyfillError, Result};\nuse crate::types::*;\nuse crate::utils::math;\nuse alloy_primitives::Address;\nuse chrono::{DateTime, Utc};\nuse rust_decimal::Decimal;\nuse std::collections::HashMap;\nuse tracing::{debug, info, warn};\n\n/// Fill execution result\n#[derive(Debug, Clone)]\npub struct FillResult {\n pub order_id: String,\n pub fills: Vec\u003cFillEvent\u003e,\n pub total_size: Decimal,\n pub average_price: Decimal,\n pub total_cost: Decimal,\n pub fees: Decimal,\n pub status: FillStatus,\n pub timestamp: DateTime\u003cUtc\u003e,\n}\n\n/// Fill execution status\n#[derive(Debug, Clone, PartialEq)]\npub enum FillStatus {\n /// Order was fully filled\n Filled,\n /// Order was partially filled\n Partial,\n /// Order was not filled (insufficient liquidity)\n Unfilled,\n /// Order was rejected\n Rejected,\n}\n\n/// Fill execution engine\n#[derive(Debug)]\npub struct FillEngine {\n /// Minimum fill size for market orders\n min_fill_size: Decimal,\n /// Maximum slippage tolerance (as percentage)\n max_slippage_pct: Decimal,\n /// Fee rate in basis points\n fee_rate_bps: u32,\n /// Track fills by order ID\n fills: HashMap\u003cString, Vec\u003cFillEvent\u003e\u003e,\n}\n\nimpl FillEngine {\n /// Create a new fill engine\n pub fn new(min_fill_size: Decimal, max_slippage_pct: Decimal, fee_rate_bps: u32) -\u003e Self {\n Self {\n min_fill_size,\n max_slippage_pct,\n fee_rate_bps,\n fills: HashMap::new(),\n }\n }\n\n /// Execute a market order against an order book\n pub fn execute_market_order(\n \u0026mut self,\n order: \u0026MarketOrderRequest,\n book: \u0026crate::book::OrderBook,\n ) -\u003e Result\u003cFillResult\u003e {\n let start_time = Utc::now();\n \n // Validate order\n self.validate_market_order(order)?;\n\n // Get available liquidity\n let levels = match order.side {\n Side::BUY =\u003e book.asks(None),\n Side::SELL =\u003e book.bids(None),\n };\n\n if levels.is_empty() {\n return Ok(FillResult {\n order_id: order.client_id.clone().unwrap_or_else(|| \"market_order\".to_string()),\n fills: Vec::new(),\n total_size: Decimal::ZERO,\n average_price: Decimal::ZERO,\n total_cost: Decimal::ZERO,\n fees: Decimal::ZERO,\n status: FillStatus::Unfilled,\n timestamp: start_time,\n });\n }\n\n // Execute fills\n let mut fills = Vec::new();\n let mut remaining_size = order.amount;\n let mut total_cost = Decimal::ZERO;\n let mut total_size = Decimal::ZERO;\n\n for level in levels {\n if remaining_size.is_zero() {\n break;\n }\n\n let fill_size = std::cmp::min(remaining_size, level.size);\n let fill_cost = fill_size * level.price;\n \n // Calculate fee\n let fee = self.calculate_fee(fill_cost);\n \n let fill = FillEvent {\n id: uuid::Uuid::new_v4().to_string(),\n order_id: order.client_id.clone().unwrap_or_else(|| \"market_order\".to_string()),\n token_id: order.token_id.clone(),\n side: order.side,\n price: level.price,\n size: fill_size,\n timestamp: Utc::now(),\n maker_address: Address::ZERO, // TODO: Get from level\n taker_address: Address::ZERO, // TODO: Get from order\n fee,\n };\n\n fills.push(fill);\n total_cost += fill_cost;\n total_size += fill_size;\n remaining_size -= fill_size;\n }\n\n // Check slippage\n if let Some(slippage) = self.calculate_slippage(order, \u0026fills) {\n if slippage \u003e self.max_slippage_pct {\n warn!(\n \"Slippage {}% exceeds maximum {}%\",\n slippage, self.max_slippage_pct\n );\n return Ok(FillResult {\n order_id: order.client_id.clone().unwrap_or_else(|| \"market_order\".to_string()),\n fills: Vec::new(),\n total_size: Decimal::ZERO,\n average_price: Decimal::ZERO,\n total_cost: Decimal::ZERO,\n fees: Decimal::ZERO,\n status: FillStatus::Rejected,\n timestamp: start_time,\n });\n }\n }\n\n // Determine status\n let status = if remaining_size.is_zero() {\n FillStatus::Filled\n } else if total_size \u003e= self.min_fill_size {\n FillStatus::Partial\n } else {\n FillStatus::Unfilled\n };\n\n let average_price = if total_size.is_zero() {\n Decimal::ZERO\n } else {\n total_cost / total_size\n };\n\n let total_fees: Decimal = fills.iter().map(|f| f.fee).sum();\n\n let result = FillResult {\n order_id: order.client_id.clone().unwrap_or_else(|| \"market_order\".to_string()),\n fills,\n total_size,\n average_price,\n total_cost,\n fees: total_fees,\n status,\n timestamp: start_time,\n };\n\n // Store fills for tracking\n if !result.fills.is_empty() {\n self.fills.insert(result.order_id.clone(), result.fills.clone());\n }\n\n info!(\n \"Market order executed: {} {} @ {} (avg: {})\",\n result.total_size,\n order.side.as_str(),\n order.amount,\n result.average_price\n );\n\n Ok(result)\n }\n\n /// Execute a limit order (simulation)\n pub fn execute_limit_order(\n \u0026mut self,\n order: \u0026OrderRequest,\n book: \u0026crate::book::OrderBook,\n ) -\u003e Result\u003cFillResult\u003e {\n let start_time = Utc::now();\n\n // Validate order\n self.validate_limit_order(order)?;\n\n // Check if order can be filled immediately\n let can_fill = match order.side {\n Side::BUY =\u003e {\n if let Some(best_ask) = book.best_ask() {\n order.price \u003e= best_ask.price\n } else {\n false\n }\n }\n Side::SELL =\u003e {\n if let Some(best_bid) = book.best_bid() {\n order.price \u003c= best_bid.price\n } else {\n false\n }\n }\n };\n\n if !can_fill {\n return Ok(FillResult {\n order_id: order.client_id.clone().unwrap_or_else(|| \"limit_order\".to_string()),\n fills: Vec::new(),\n total_size: Decimal::ZERO,\n average_price: Decimal::ZERO,\n total_cost: Decimal::ZERO,\n fees: Decimal::ZERO,\n status: FillStatus::Unfilled,\n timestamp: start_time,\n });\n }\n\n // Simulate immediate fill\n let fill = FillEvent {\n id: uuid::Uuid::new_v4().to_string(),\n order_id: order.client_id.clone().unwrap_or_else(|| \"limit_order\".to_string()),\n token_id: order.token_id.clone(),\n side: order.side,\n price: order.price,\n size: order.size,\n timestamp: Utc::now(),\n maker_address: Address::ZERO,\n taker_address: Address::ZERO,\n fee: self.calculate_fee(order.price * order.size),\n };\n\n let result = FillResult {\n order_id: order.client_id.clone().unwrap_or_else(|| \"limit_order\".to_string()),\n fills: vec![fill],\n total_size: order.size,\n average_price: order.price,\n total_cost: order.price * order.size,\n fees: self.calculate_fee(order.price * order.size),\n status: FillStatus::Filled,\n timestamp: start_time,\n };\n\n // Store fills for tracking\n self.fills.insert(result.order_id.clone(), result.fills.clone());\n\n info!(\n \"Limit order executed: {} {} @ {}\",\n result.total_size,\n order.side.as_str(),\n result.average_price\n );\n\n Ok(result)\n }\n\n /// Calculate slippage for a market order\n fn calculate_slippage(\u0026self, order: \u0026MarketOrderRequest, fills: \u0026[FillEvent]) -\u003e Option\u003cDecimal\u003e {\n if fills.is_empty() {\n return None;\n }\n\n let total_cost: Decimal = fills.iter().map(|f| f.price * f.size).sum();\n let total_size: Decimal = fills.iter().map(|f| f.size).sum();\n let average_price = total_cost / total_size;\n\n // Get reference price (best bid/ask)\n let reference_price = match order.side {\n Side::BUY =\u003e fills.first()?.price, // Best ask\n Side::SELL =\u003e fills.first()?.price, // Best bid\n };\n\n Some(math::calculate_slippage(reference_price, average_price, order.side))\n }\n\n /// Calculate fee for a trade\n fn calculate_fee(\u0026self, notional: Decimal) -\u003e Decimal {\n notional * Decimal::from(self.fee_rate_bps) / Decimal::from(10_000)\n }\n\n /// Validate market order parameters\n fn validate_market_order(\u0026self, order: \u0026MarketOrderRequest) -\u003e Result\u003c()\u003e {\n if order.amount.is_zero() {\n return Err(PolyfillError::order(\n \"Market order amount cannot be zero\",\n crate::errors::OrderErrorKind::InvalidSize,\n ));\n }\n\n if order.amount \u003c self.min_fill_size {\n return Err(PolyfillError::order(\n format!(\"Order size {} below minimum {}\", order.amount, self.min_fill_size),\n crate::errors::OrderErrorKind::SizeConstraint,\n ));\n }\n\n Ok(())\n }\n\n /// Validate limit order parameters\n fn validate_limit_order(\u0026self, order: \u0026OrderRequest) -\u003e Result\u003c()\u003e {\n if order.size.is_zero() {\n return Err(PolyfillError::order(\n \"Limit order size cannot be zero\",\n crate::errors::OrderErrorKind::InvalidSize,\n ));\n }\n\n if order.price.is_zero() {\n return Err(PolyfillError::order(\n \"Limit order price cannot be zero\",\n crate::errors::OrderErrorKind::InvalidPrice,\n ));\n }\n\n if order.size \u003c self.min_fill_size {\n return Err(PolyfillError::order(\n format!(\"Order size {} below minimum {}\", order.size, self.min_fill_size),\n crate::errors::OrderErrorKind::SizeConstraint,\n ));\n }\n\n Ok(())\n }\n\n /// Get fills for an order\n pub fn get_fills(\u0026self, order_id: \u0026str) -\u003e Option\u003c\u0026[FillEvent]\u003e {\n self.fills.get(order_id).map(|f| f.as_slice())\n }\n\n /// Get all fills\n pub fn get_all_fills(\u0026self) -\u003e Vec\u003c\u0026FillEvent\u003e {\n self.fills.values().flatten().collect()\n }\n\n /// Clear fills for an order\n pub fn clear_fills(\u0026mut self, order_id: \u0026str) {\n self.fills.remove(order_id);\n }\n\n /// Get fill statistics\n pub fn get_stats(\u0026self) -\u003e FillStats {\n let total_fills = self.fills.values().flatten().count();\n let total_volume: Decimal = self.fills.values().flatten().map(|f| f.size).sum();\n let total_fees: Decimal = self.fills.values().flatten().map(|f| f.fee).sum();\n\n FillStats {\n total_orders: self.fills.len(),\n total_fills,\n total_volume,\n total_fees,\n }\n }\n}\n\n/// Fill statistics\n#[derive(Debug, Clone)]\npub struct FillStats {\n pub total_orders: usize,\n pub total_fills: usize,\n pub total_volume: Decimal,\n pub total_fees: Decimal,\n}\n\n/// Fill event processor for real-time updates\n#[derive(Debug)]\npub struct FillProcessor {\n /// Pending fills by order ID\n pending_fills: HashMap\u003cString, Vec\u003cFillEvent\u003e\u003e,\n /// Processed fills\n processed_fills: Vec\u003cFillEvent\u003e,\n /// Maximum pending fills to keep in memory\n max_pending: usize,\n}\n\nimpl FillProcessor {\n /// Create a new fill processor\n pub fn new(max_pending: usize) -\u003e Self {\n Self {\n pending_fills: HashMap::new(),\n processed_fills: Vec::new(),\n max_pending,\n }\n }\n\n /// Process a fill event\n pub fn process_fill(\u0026mut self, fill: FillEvent) -\u003e Result\u003c()\u003e {\n // Validate fill\n self.validate_fill(\u0026fill)?;\n\n // Add to pending fills\n self.pending_fills\n .entry(fill.order_id.clone())\n .or_insert_with(Vec::new)\n .push(fill.clone());\n\n // Move to processed if complete\n if self.is_order_complete(\u0026fill.order_id) {\n if let Some(fills) = self.pending_fills.remove(\u0026fill.order_id) {\n self.processed_fills.extend(fills);\n }\n }\n\n // Cleanup if too many pending\n if self.pending_fills.len() \u003e self.max_pending {\n self.cleanup_old_pending();\n }\n\n debug!(\"Processed fill: {} {} @ {}\", fill.size, fill.side.as_str(), fill.price);\n\n Ok(())\n }\n\n /// Validate a fill event\n fn validate_fill(\u0026self, fill: \u0026FillEvent) -\u003e Result\u003c()\u003e {\n if fill.size.is_zero() {\n return Err(PolyfillError::order(\n \"Fill size cannot be zero\",\n crate::errors::OrderErrorKind::InvalidSize,\n ));\n }\n\n if fill.price.is_zero() {\n return Err(PolyfillError::order(\n \"Fill price cannot be zero\",\n crate::errors::OrderErrorKind::InvalidPrice,\n ));\n }\n\n Ok(())\n }\n\n /// Check if an order is complete\n fn is_order_complete(\u0026self, _order_id: \u0026str) -\u003e bool {\n // Simplified implementation - in practice you'd check against order book\n false\n }\n\n /// Cleanup old pending fills\n fn cleanup_old_pending(\u0026mut self) {\n // Remove oldest pending fills\n let to_remove = self.pending_fills.len() - self.max_pending;\n let mut keys: Vec\u003c_\u003e = self.pending_fills.keys().cloned().collect();\n keys.sort(); // Simple ordering - in practice you'd use timestamps\n\n for key in keys.iter().take(to_remove) {\n self.pending_fills.remove(key);\n }\n }\n\n /// Get pending fills for an order\n pub fn get_pending_fills(\u0026self, order_id: \u0026str) -\u003e Option\u003c\u0026[FillEvent]\u003e {\n self.pending_fills.get(order_id).map(|f| f.as_slice())\n }\n\n /// Get processed fills\n pub fn get_processed_fills(\u0026self) -\u003e \u0026[FillEvent] {\n \u0026self.processed_fills\n }\n\n /// Get fill statistics\n pub fn get_stats(\u0026self) -\u003e FillProcessorStats {\n let total_pending: Decimal = self.pending_fills.values().flatten().map(|f| f.size).sum();\n let total_processed: Decimal = self.processed_fills.iter().map(|f| f.size).sum();\n\n FillProcessorStats {\n pending_orders: self.pending_fills.len(),\n pending_fills: self.pending_fills.values().flatten().count(),\n pending_volume: total_pending,\n processed_fills: self.processed_fills.len(),\n processed_volume: total_processed,\n }\n }\n}\n\n/// Fill processor statistics\n#[derive(Debug, Clone)]\npub struct FillProcessorStats {\n pub pending_orders: usize,\n pub pending_fills: usize,\n pub pending_volume: Decimal,\n pub processed_fills: usize,\n pub processed_volume: Decimal,\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use rust_decimal_macros::dec;\n\n #[test]\n fn test_fill_engine_creation() {\n let engine = FillEngine::new(dec!(1), dec!(5), 10);\n assert_eq!(engine.min_fill_size, dec!(1));\n assert_eq!(engine.max_slippage_pct, dec!(5));\n assert_eq!(engine.fee_rate_bps, 10);\n }\n\n #[test]\n fn test_market_order_validation() {\n let engine = FillEngine::new(dec!(1), dec!(5), 10);\n \n let valid_order = MarketOrderRequest {\n token_id: \"test\".to_string(),\n side: Side::BUY,\n amount: dec!(100),\n slippage_tolerance: None,\n client_id: None,\n };\n assert!(engine.validate_market_order(\u0026valid_order).is_ok());\n\n let invalid_order = MarketOrderRequest {\n token_id: \"test\".to_string(),\n side: Side::BUY,\n amount: dec!(0),\n slippage_tolerance: None,\n client_id: None,\n };\n assert!(engine.validate_market_order(\u0026invalid_order).is_err());\n }\n\n #[test]\n fn test_fee_calculation() {\n let engine = FillEngine::new(dec!(1), dec!(5), 10);\n let fee = engine.calculate_fee(dec!(1000));\n assert_eq!(fee, dec!(1)); // 10 bps = 0.1% = 1 on 1000\n }\n\n #[test]\n fn test_fill_processor() {\n let mut processor = FillProcessor::new(100);\n \n let fill = FillEvent {\n id: \"fill1\".to_string(),\n order_id: \"order1\".to_string(),\n token_id: \"test\".to_string(),\n side: Side::BUY,\n price: dec!(0.5),\n size: dec!(100),\n timestamp: Utc::now(),\n maker_address: Address::ZERO,\n taker_address: Address::ZERO,\n fee: dec!(0.1),\n };\n\n assert!(processor.process_fill(fill).is_ok());\n assert_eq!(processor.pending_fills.len(), 1);\n }\n} ","traces":[{"line":56,"address":[],"length":0,"stats":{"Line":3}},{"line":61,"address":[],"length":0,"stats":{"Line":3}},{"line":66,"address":[],"length":0,"stats":{"Line":0}},{"line":71,"address":[],"length":0,"stats":{"Line":0}},{"line":74,"address":[],"length":0,"stats":{"Line":0}},{"line":77,"address":[],"length":0,"stats":{"Line":0}},{"line":78,"address":[],"length":0,"stats":{"Line":0}},{"line":79,"address":[],"length":0,"stats":{"Line":0}},{"line":82,"address":[],"length":0,"stats":{"Line":0}},{"line":84,"address":[],"length":0,"stats":{"Line":0}},{"line":85,"address":[],"length":0,"stats":{"Line":0}},{"line":90,"address":[],"length":0,"stats":{"Line":0}},{"line":91,"address":[],"length":0,"stats":{"Line":0}},{"line":96,"address":[],"length":0,"stats":{"Line":0}},{"line":97,"address":[],"length":0,"stats":{"Line":0}},{"line":98,"address":[],"length":0,"stats":{"Line":0}},{"line":99,"address":[],"length":0,"stats":{"Line":0}},{"line":101,"address":[],"length":0,"stats":{"Line":0}},{"line":102,"address":[],"length":0,"stats":{"Line":0}},{"line":103,"address":[],"length":0,"stats":{"Line":0}},{"line":106,"address":[],"length":0,"stats":{"Line":0}},{"line":107,"address":[],"length":0,"stats":{"Line":0}},{"line":110,"address":[],"length":0,"stats":{"Line":0}},{"line":113,"address":[],"length":0,"stats":{"Line":0}},{"line":114,"address":[],"length":0,"stats":{"Line":0}},{"line":115,"address":[],"length":0,"stats":{"Line":0}},{"line":116,"address":[],"length":0,"stats":{"Line":0}},{"line":117,"address":[],"length":0,"stats":{"Line":0}},{"line":119,"address":[],"length":0,"stats":{"Line":0}},{"line":125,"address":[],"length":0,"stats":{"Line":0}},{"line":126,"address":[],"length":0,"stats":{"Line":0}},{"line":127,"address":[],"length":0,"stats":{"Line":0}},{"line":128,"address":[],"length":0,"stats":{"Line":0}},{"line":132,"address":[],"length":0,"stats":{"Line":0}},{"line":133,"address":[],"length":0,"stats":{"Line":0}},{"line":134,"address":[],"length":0,"stats":{"Line":0}},{"line":135,"address":[],"length":0,"stats":{"Line":0}},{"line":139,"address":[],"length":0,"stats":{"Line":0}},{"line":140,"address":[],"length":0,"stats":{"Line":0}},{"line":145,"address":[],"length":0,"stats":{"Line":0}},{"line":146,"address":[],"length":0,"stats":{"Line":0}},{"line":152,"address":[],"length":0,"stats":{"Line":0}},{"line":153,"address":[],"length":0,"stats":{"Line":0}},{"line":154,"address":[],"length":0,"stats":{"Line":0}},{"line":155,"address":[],"length":0,"stats":{"Line":0}},{"line":157,"address":[],"length":0,"stats":{"Line":0}},{"line":160,"address":[],"length":0,"stats":{"Line":0}},{"line":161,"address":[],"length":0,"stats":{"Line":0}},{"line":163,"address":[],"length":0,"stats":{"Line":0}},{"line":166,"address":[],"length":0,"stats":{"Line":0}},{"line":169,"address":[],"length":0,"stats":{"Line":0}},{"line":180,"address":[],"length":0,"stats":{"Line":0}},{"line":181,"address":[],"length":0,"stats":{"Line":0}},{"line":184,"address":[],"length":0,"stats":{"Line":0}},{"line":185,"address":[],"length":0,"stats":{"Line":0}},{"line":187,"address":[],"length":0,"stats":{"Line":0}},{"line":192,"address":[],"length":0,"stats":{"Line":0}},{"line":196,"address":[],"length":0,"stats":{"Line":0}},{"line":201,"address":[],"length":0,"stats":{"Line":0}},{"line":204,"address":[],"length":0,"stats":{"Line":0}},{"line":207,"address":[],"length":0,"stats":{"Line":0}},{"line":209,"address":[],"length":0,"stats":{"Line":0}},{"line":210,"address":[],"length":0,"stats":{"Line":0}},{"line":212,"address":[],"length":0,"stats":{"Line":0}},{"line":216,"address":[],"length":0,"stats":{"Line":0}},{"line":217,"address":[],"length":0,"stats":{"Line":0}},{"line":219,"address":[],"length":0,"stats":{"Line":0}},{"line":224,"address":[],"length":0,"stats":{"Line":0}},{"line":226,"address":[],"length":0,"stats":{"Line":0}},{"line":227,"address":[],"length":0,"stats":{"Line":0}},{"line":232,"address":[],"length":0,"stats":{"Line":0}},{"line":233,"address":[],"length":0,"stats":{"Line":0}},{"line":239,"address":[],"length":0,"stats":{"Line":0}},{"line":240,"address":[],"length":0,"stats":{"Line":0}},{"line":241,"address":[],"length":0,"stats":{"Line":0}},{"line":242,"address":[],"length":0,"stats":{"Line":0}},{"line":243,"address":[],"length":0,"stats":{"Line":0}},{"line":244,"address":[],"length":0,"stats":{"Line":0}},{"line":245,"address":[],"length":0,"stats":{"Line":0}},{"line":248,"address":[],"length":0,"stats":{"Line":0}},{"line":252,"address":[],"length":0,"stats":{"Line":0}},{"line":253,"address":[],"length":0,"stats":{"Line":0}},{"line":254,"address":[],"length":0,"stats":{"Line":0}},{"line":255,"address":[],"length":0,"stats":{"Line":0}},{"line":256,"address":[],"length":0,"stats":{"Line":0}},{"line":257,"address":[],"length":0,"stats":{"Line":0}},{"line":263,"address":[],"length":0,"stats":{"Line":0}},{"line":265,"address":[],"length":0,"stats":{"Line":0}},{"line":266,"address":[],"length":0,"stats":{"Line":0}},{"line":268,"address":[],"length":0,"stats":{"Line":0}},{"line":272,"address":[],"length":0,"stats":{"Line":0}},{"line":276,"address":[],"length":0,"stats":{"Line":0}},{"line":277,"address":[],"length":0,"stats":{"Line":0}},{"line":278,"address":[],"length":0,"stats":{"Line":0}},{"line":281,"address":[],"length":0,"stats":{"Line":0}},{"line":282,"address":[],"length":0,"stats":{"Line":0}},{"line":283,"address":[],"length":0,"stats":{"Line":0}},{"line":286,"address":[],"length":0,"stats":{"Line":0}},{"line":287,"address":[],"length":0,"stats":{"Line":0}},{"line":288,"address":[],"length":0,"stats":{"Line":0}},{"line":291,"address":[],"length":0,"stats":{"Line":0}},{"line":295,"address":[],"length":0,"stats":{"Line":1}},{"line":296,"address":[],"length":0,"stats":{"Line":2}},{"line":300,"address":[],"length":0,"stats":{"Line":2}},{"line":301,"address":[],"length":0,"stats":{"Line":4}},{"line":302,"address":[],"length":0,"stats":{"Line":1}},{"line":303,"address":[],"length":0,"stats":{"Line":1}},{"line":304,"address":[],"length":0,"stats":{"Line":1}},{"line":308,"address":[],"length":0,"stats":{"Line":1}},{"line":309,"address":[],"length":0,"stats":{"Line":0}},{"line":310,"address":[],"length":0,"stats":{"Line":0}},{"line":311,"address":[],"length":0,"stats":{"Line":0}},{"line":315,"address":[],"length":0,"stats":{"Line":1}},{"line":319,"address":[],"length":0,"stats":{"Line":0}},{"line":320,"address":[],"length":0,"stats":{"Line":0}},{"line":321,"address":[],"length":0,"stats":{"Line":0}},{"line":322,"address":[],"length":0,"stats":{"Line":0}},{"line":323,"address":[],"length":0,"stats":{"Line":0}},{"line":327,"address":[],"length":0,"stats":{"Line":0}},{"line":328,"address":[],"length":0,"stats":{"Line":0}},{"line":329,"address":[],"length":0,"stats":{"Line":0}},{"line":330,"address":[],"length":0,"stats":{"Line":0}},{"line":334,"address":[],"length":0,"stats":{"Line":0}},{"line":335,"address":[],"length":0,"stats":{"Line":0}},{"line":336,"address":[],"length":0,"stats":{"Line":0}},{"line":337,"address":[],"length":0,"stats":{"Line":0}},{"line":341,"address":[],"length":0,"stats":{"Line":0}},{"line":345,"address":[],"length":0,"stats":{"Line":0}},{"line":346,"address":[],"length":0,"stats":{"Line":0}},{"line":350,"address":[],"length":0,"stats":{"Line":0}},{"line":351,"address":[],"length":0,"stats":{"Line":0}},{"line":355,"address":[],"length":0,"stats":{"Line":0}},{"line":356,"address":[],"length":0,"stats":{"Line":0}},{"line":360,"address":[],"length":0,"stats":{"Line":0}},{"line":361,"address":[],"length":0,"stats":{"Line":0}},{"line":362,"address":[],"length":0,"stats":{"Line":0}},{"line":363,"address":[],"length":0,"stats":{"Line":0}},{"line":366,"address":[],"length":0,"stats":{"Line":0}},{"line":396,"address":[],"length":0,"stats":{"Line":1}},{"line":398,"address":[],"length":0,"stats":{"Line":2}},{"line":399,"address":[],"length":0,"stats":{"Line":1}},{"line":405,"address":[],"length":0,"stats":{"Line":1}},{"line":407,"address":[],"length":0,"stats":{"Line":3}},{"line":410,"address":[],"length":0,"stats":{"Line":2}},{"line":411,"address":[],"length":0,"stats":{"Line":3}},{"line":412,"address":[],"length":0,"stats":{"Line":1}},{"line":413,"address":[],"length":0,"stats":{"Line":3}},{"line":416,"address":[],"length":0,"stats":{"Line":3}},{"line":417,"address":[],"length":0,"stats":{"Line":0}},{"line":418,"address":[],"length":0,"stats":{"Line":0}},{"line":423,"address":[],"length":0,"stats":{"Line":2}},{"line":424,"address":[],"length":0,"stats":{"Line":0}},{"line":427,"address":[],"length":0,"stats":{"Line":1}},{"line":429,"address":[],"length":0,"stats":{"Line":1}},{"line":433,"address":[],"length":0,"stats":{"Line":1}},{"line":434,"address":[],"length":0,"stats":{"Line":2}},{"line":435,"address":[],"length":0,"stats":{"Line":0}},{"line":436,"address":[],"length":0,"stats":{"Line":0}},{"line":437,"address":[],"length":0,"stats":{"Line":0}},{"line":441,"address":[],"length":0,"stats":{"Line":2}},{"line":442,"address":[],"length":0,"stats":{"Line":0}},{"line":443,"address":[],"length":0,"stats":{"Line":0}},{"line":444,"address":[],"length":0,"stats":{"Line":0}},{"line":448,"address":[],"length":0,"stats":{"Line":1}},{"line":452,"address":[],"length":0,"stats":{"Line":1}},{"line":454,"address":[],"length":0,"stats":{"Line":1}},{"line":458,"address":[],"length":0,"stats":{"Line":0}},{"line":460,"address":[],"length":0,"stats":{"Line":0}},{"line":461,"address":[],"length":0,"stats":{"Line":0}},{"line":462,"address":[],"length":0,"stats":{"Line":0}},{"line":464,"address":[],"length":0,"stats":{"Line":0}},{"line":465,"address":[],"length":0,"stats":{"Line":0}},{"line":470,"address":[],"length":0,"stats":{"Line":0}},{"line":471,"address":[],"length":0,"stats":{"Line":0}},{"line":475,"address":[],"length":0,"stats":{"Line":0}},{"line":476,"address":[],"length":0,"stats":{"Line":0}},{"line":480,"address":[],"length":0,"stats":{"Line":0}},{"line":481,"address":[],"length":0,"stats":{"Line":0}},{"line":482,"address":[],"length":0,"stats":{"Line":0}},{"line":485,"address":[],"length":0,"stats":{"Line":0}},{"line":486,"address":[],"length":0,"stats":{"Line":0}},{"line":488,"address":[],"length":0,"stats":{"Line":0}}],"covered":30,"coverable":182},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","lib.rs"],"content":"//! Polyfill-rs: High-performance Rust client for Polymarket\n//! \n//! # Features\n//! \n//! - **High-performance order book management** with optimized data structures\n//! - **Real-time market data streaming** with WebSocket support\n//! - **Trade execution simulation** with slippage protection\n//! - **Comprehensive error handling** with specific error types\n//! - **Rate limiting and retry logic** for robust API interactions\n//! - **Ethereum integration** with EIP-712 signing support\n//! - **Benchmarking tools** for performance analysis\n//! \n//! # Quick Start\n//! \n//! ```rust,no_run\n//! use polyfill_rs::{ClobClient, OrderArgs, Side};\n//! use rust_decimal::Decimal;\n//! use std::str::FromStr;\n//! \n//! #[tokio::main]\n//! async fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n//! // Create client (compatible with polymarket-rs-client)\n//! let mut client = ClobClient::with_l1_headers(\n//! \"https://clob.polymarket.com\",\n//! \"your_private_key\",\n//! 137,\n//! );\n//! \n//! // Get API credentials\n//! let api_creds = client.create_or_derive_api_key(None).await.unwrap();\n//! client.set_api_creds(api_creds);\n//! \n//! // Create and post order\n//! let order_args = OrderArgs::new(\n//! \"token_id\",\n//! Decimal::from_str(\"0.75\").unwrap(),\n//! Decimal::from_str(\"100.0\").unwrap(),\n//! Side::BUY,\n//! );\n//! \n//! let result = client.create_and_post_order(\u0026order_args).await.unwrap();\n//! println!(\"Order posted: {:?}\", result);\n//! \n//! Ok(())\n//! }\n//! ```\n//! \n//! # Advanced Usage\n//! \n//! ```rust,no_run\n//! use polyfill_rs::{ClobClient, OrderBookImpl};\n//! use rust_decimal::Decimal;\n//! \n//! #[tokio::main]\n//! async fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n//! // Create a basic client\n//! let client = ClobClient::new(\"https://clob.polymarket.com\");\n//! \n//! // Get market data\n//! let markets = client.get_sampling_markets(None).await.unwrap();\n//! println!(\"Found {} markets\", markets.data.len());\n//! \n//! // Create an order book for high-performance operations\n//! let mut book = OrderBookImpl::new(\"token_id\".to_string(), 100); // 100 levels depth\n//! println!(\"Order book created for token: {}\", book.token_id);\n//! \n//! Ok(())\n//! }\n//! ```\n\nuse tracing::info;\n\n\n// Global constants\npub const DEFAULT_CHAIN_ID: u64 = 137; // Polygon\npub const DEFAULT_BASE_URL: \u0026str = \"https://clob.polymarket.com\";\npub const DEFAULT_TIMEOUT_SECS: u64 = 30;\npub const DEFAULT_MAX_RETRIES: u32 = 3;\npub const DEFAULT_RATE_LIMIT_RPS: u32 = 100;\n\n// Initialize logging\npub fn init() {\n tracing_subscriber::fmt::init();\n info!(\"Polyfill-rs initialized\");\n}\n\n// Re-export main types\npub use crate::types::{\n ApiCredentials, Balance, BalanceAllowance, BatchMidpointRequest, BatchMidpointResponse,\n BatchPriceRequest, BatchPriceResponse, ClientConfig, FillEvent, MarketSnapshot, \n NotificationParams, OpenOrder, OpenOrderParams, Order, OrderBook, OrderDelta, \n OrderRequest, OrderStatus, OrderType, Side, StreamMessage, TokenPrice, TradeParams,\n WssAuth, WssSubscription, WssChannelType,\n // Additional compatibility types\n ApiKeysResponse, MidpointResponse, PriceResponse, SpreadResponse, TickSizeResponse,\n NegRiskResponse, BookParams, MarketsResponse, SimplifiedMarketsResponse, Market,\n SimplifiedMarket, Token, Rewards, ClientResult, OrderBookSummary, OrderSummary,\n BalanceAllowanceParams, AssetType,\n};\n\n// Re-export client\npub use crate::client::{ClobClient, PolyfillClient};\n\n// Re-export compatibility types (for easy migration from polymarket-rs-client)\npub use crate::client::{\n OrderArgs,\n};\n\n// Re-export error types\npub use crate::errors::{PolyfillError, Result};\n\n// Re-export advanced components\npub use crate::book::{OrderBook as OrderBookImpl, OrderBookManager};\npub use crate::fill::{FillEngine, FillResult};\npub use crate::stream::{MarketStream, StreamManager, WebSocketStream};\npub use crate::decode::Decoder;\n\n// Re-export utilities\npub use crate::utils::{\n crypto, math, retry, time, url, rate_limit,\n};\n\n// Module declarations\npub mod auth;\npub mod book;\npub mod client;\npub mod decode;\npub mod errors;\npub mod fill;\npub mod orders;\npub mod stream;\npub mod types;\npub mod utils;\n\n// Benchmarks\n#[cfg(test)]\nmod benches {\n use criterion::{criterion_group, criterion_main};\n use crate::{OrderBookManager, OrderDelta, Side};\n use rust_decimal::Decimal;\n use chrono::Utc;\n use std::str::FromStr;\n\n fn order_book_benchmark(c: \u0026mut criterion::Criterion) {\n let mut book_manager = OrderBookManager::new(100);\n \n c.bench_function(\"apply_order_delta\", |b| {\n b.iter(|| {\n let delta = OrderDelta {\n token_id: \"test_token\".to_string(),\n timestamp: Utc::now(),\n side: Side::BUY,\n price: Decimal::from_str(\"0.75\").unwrap(),\n size: Decimal::from_str(\"100.0\").unwrap(),\n sequence: 1,\n };\n \n let _ = book_manager.apply_delta(delta);\n });\n });\n }\n\n criterion_group!(benches, order_book_benchmark);\n criterion_main!(benches);\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use rust_decimal::Decimal;\n use std::str::FromStr;\n use alloy_primitives::U256;\n\n #[test]\n fn test_client_creation() {\n let client = ClobClient::new(\"https://test.example.com\");\n // Test that the client was created successfully\n // We can't test private fields, but we can verify the client exists\n assert!(true); // Client creation successful\n }\n\n #[test]\n fn test_order_args_creation() {\n let args = OrderArgs::new(\n \"test_token\",\n Decimal::from_str(\"0.75\").unwrap(),\n Decimal::from_str(\"100.0\").unwrap(),\n Side::BUY,\n );\n \n assert_eq!(args.token_id, \"test_token\");\n assert_eq!(args.side, Side::BUY);\n }\n\n #[test]\n fn test_order_args_default() {\n let args = OrderArgs::default();\n assert_eq!(args.token_id, \"\");\n assert_eq!(args.price, Decimal::ZERO);\n assert_eq!(args.size, Decimal::ZERO);\n assert_eq!(args.side, Side::BUY);\n }\n} ","traces":[{"line":82,"address":[],"length":0,"stats":{"Line":0}},{"line":83,"address":[],"length":0,"stats":{"Line":0}},{"line":84,"address":[],"length":0,"stats":{"Line":0}}],"covered":0,"coverable":3},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","orders.rs"],"content":"//! Order creation and signing functionality\n//!\n//! This module handles the complex process of creating and signing orders\n//! for the Polymarket CLOB, including EIP-712 signature generation.\n\nuse crate::auth::sign_order_message;\nuse crate::errors::{PolyfillError, Result};\nuse crate::client::OrderArgs;\nuse crate::types::{ExtraOrderArgs, MarketOrderArgs, OrderOptions, SignedOrderRequest, Side};\nuse alloy_primitives::{Address, U256};\nuse alloy_signer_local::PrivateKeySigner;\nuse rand::Rng;\nuse rust_decimal::Decimal;\nuse rust_decimal::RoundingStrategy::{AwayFromZero, MidpointTowardZero, ToZero};\nuse std::collections::HashMap;\nuse std::str::FromStr;\nuse std::sync::LazyLock;\nuse std::time::{SystemTime, UNIX_EPOCH};\n\n/// Signature types for orders\n#[derive(Copy, Clone)]\npub enum SigType {\n /// ECDSA EIP712 signatures signed by EOAs\n Eoa = 0,\n /// EIP712 signatures signed by EOAs that own Polymarket Proxy wallets\n PolyProxy = 1,\n /// EIP712 signatures signed by EOAs that own Polymarket Gnosis safes\n PolyGnosisSafe = 2,\n}\n\n/// Rounding configuration for different tick sizes\npub struct RoundConfig {\n price: u32,\n size: u32,\n amount: u32,\n}\n\n/// Contract configuration\npub struct ContractConfig {\n pub exchange: String,\n pub collateral: String,\n pub conditional_tokens: String,\n}\n\n\n/// Order builder for creating and signing orders\npub struct OrderBuilder {\n signer: PrivateKeySigner,\n sig_type: SigType,\n funder: Address,\n}\n\n/// Rounding configurations for different tick sizes\nstatic ROUNDING_CONFIG: LazyLock\u003cHashMap\u003cDecimal, RoundConfig\u003e\u003e = LazyLock::new(|| {\n HashMap::from([\n (\n Decimal::from_str(\"0.1\").unwrap(),\n RoundConfig {\n price: 1,\n size: 2,\n amount: 3,\n },\n ),\n (\n Decimal::from_str(\"0.01\").unwrap(),\n RoundConfig {\n price: 2,\n size: 2,\n amount: 4,\n },\n ),\n (\n Decimal::from_str(\"0.001\").unwrap(),\n RoundConfig {\n price: 3,\n size: 2,\n amount: 5,\n },\n ),\n (\n Decimal::from_str(\"0.0001\").unwrap(),\n RoundConfig {\n price: 4,\n size: 2,\n amount: 6,\n },\n ),\n ])\n});\n\n/// Get contract configuration for chain\npub fn get_contract_config(chain_id: u64, neg_risk: bool) -\u003e Option\u003cContractConfig\u003e {\n match (chain_id, neg_risk) {\n (137, false) =\u003e Some(ContractConfig {\n exchange: \"0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E\".to_string(),\n collateral: \"0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174\".to_string(),\n conditional_tokens: \"0x4D97DCd97eC945f40cF65F87097ACe5EA0476045\".to_string(),\n }),\n (137, true) =\u003e Some(ContractConfig {\n exchange: \"0xC5d563A36AE78145C45a50134d48A1215220f80a\".to_string(),\n collateral: \"0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174\".to_string(),\n conditional_tokens: \"0x4D97DCd97eC945f40cF65F87097ACe5EA0476045\".to_string(),\n }),\n _ =\u003e None,\n }\n}\n\n/// Generate a random seed for order salt\nfn generate_seed() -\u003e u64 {\n let mut rng = rand::thread_rng();\n let y: f64 = rng.gen();\n let timestamp = SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .expect(\"Time went backwards\")\n .as_secs();\n (timestamp as f64 * y) as u64\n}\n\n/// Convert decimal to token units (multiply by 1e6)\nfn decimal_to_token_u32(amt: Decimal) -\u003e u32 {\n let mut amt = Decimal::from_scientific(\"1e6\").expect(\"1e6 is not scientific\") * amt;\n if amt.scale() \u003e 0 {\n amt = amt.round_dp_with_strategy(0, MidpointTowardZero);\n }\n amt.try_into().expect(\"Couldn't round decimal to integer\")\n}\n\nimpl OrderBuilder {\n /// Create a new order builder\n pub fn new(\n signer: PrivateKeySigner,\n sig_type: Option\u003cSigType\u003e,\n funder: Option\u003cAddress\u003e,\n ) -\u003e Self {\n let sig_type = sig_type.unwrap_or(SigType::Eoa);\n let funder = funder.unwrap_or(signer.address());\n\n OrderBuilder {\n signer,\n sig_type,\n funder,\n }\n }\n\n /// Get signature type as u8\n pub fn get_sig_type(\u0026self) -\u003e u8 {\n self.sig_type as u8\n }\n\n /// Fix amount rounding according to configuration\n fn fix_amount_rounding(\u0026self, mut amt: Decimal, round_config: \u0026RoundConfig) -\u003e Decimal {\n if amt.scale() \u003e round_config.amount {\n amt = amt.round_dp_with_strategy(round_config.amount + 4, AwayFromZero);\n if amt.scale() \u003e round_config.amount {\n amt = amt.round_dp_with_strategy(round_config.amount, ToZero);\n }\n }\n amt\n }\n\n /// Get order amounts (maker and taker) for a regular order\n fn get_order_amounts(\n \u0026self,\n side: Side,\n size: Decimal,\n price: Decimal,\n round_config: \u0026RoundConfig,\n ) -\u003e (u32, u32) {\n let raw_price = price.round_dp_with_strategy(round_config.price, MidpointTowardZero);\n\n match side {\n Side::BUY =\u003e {\n let raw_taker_amt = size.round_dp_with_strategy(round_config.size, ToZero);\n let raw_maker_amt = raw_taker_amt * raw_price;\n let raw_maker_amt = self.fix_amount_rounding(raw_maker_amt, round_config);\n (\n decimal_to_token_u32(raw_maker_amt),\n decimal_to_token_u32(raw_taker_amt),\n )\n }\n Side::SELL =\u003e {\n let raw_maker_amt = size.round_dp_with_strategy(round_config.size, ToZero);\n let raw_taker_amt = raw_maker_amt * raw_price;\n let raw_taker_amt = self.fix_amount_rounding(raw_taker_amt, round_config);\n\n (\n decimal_to_token_u32(raw_maker_amt),\n decimal_to_token_u32(raw_taker_amt),\n )\n }\n }\n }\n\n /// Get order amounts for a market order\n fn get_market_order_amounts(\n \u0026self,\n amount: Decimal,\n price: Decimal,\n round_config: \u0026RoundConfig,\n ) -\u003e (u32, u32) {\n let raw_maker_amt = amount.round_dp_with_strategy(round_config.size, ToZero);\n let raw_price = price.round_dp_with_strategy(round_config.price, MidpointTowardZero);\n\n let raw_taker_amt = raw_maker_amt / raw_price;\n let raw_taker_amt = self.fix_amount_rounding(raw_taker_amt, round_config);\n\n (\n decimal_to_token_u32(raw_maker_amt),\n decimal_to_token_u32(raw_taker_amt),\n )\n }\n\n /// Calculate market price from order book levels\n pub fn calculate_market_price(\n \u0026self,\n positions: \u0026[crate::types::BookLevel],\n amount_to_match: Decimal,\n ) -\u003e Result\u003cDecimal\u003e {\n let mut sum = Decimal::ZERO;\n\n for level in positions {\n sum += level.size * level.price;\n if sum \u003e= amount_to_match {\n return Ok(level.price);\n }\n }\n \n Err(PolyfillError::order(\n format!(\"Not enough liquidity to create market order with amount {}\", amount_to_match),\n crate::errors::OrderErrorKind::InsufficientBalance,\n ))\n }\n\n /// Create a market order\n pub fn create_market_order(\n \u0026self,\n chain_id: u64,\n order_args: \u0026MarketOrderArgs,\n price: Decimal,\n extras: \u0026ExtraOrderArgs,\n options: \u0026OrderOptions,\n ) -\u003e Result\u003cSignedOrderRequest\u003e {\n let tick_size = options.tick_size\n .ok_or_else(|| PolyfillError::validation(\"Cannot create order without tick size\"))?;\n \n let (maker_amount, taker_amount) = self.get_market_order_amounts(\n order_args.amount,\n price,\n \u0026ROUNDING_CONFIG[\u0026tick_size],\n );\n\n let neg_risk = options.neg_risk\n .ok_or_else(|| PolyfillError::validation(\"Cannot create order without neg_risk\"))?;\n\n let contract_config = get_contract_config(chain_id, neg_risk)\n .ok_or_else(|| PolyfillError::config(\"No contract found with given chain_id and neg_risk\"))?;\n\n let exchange_address = Address::from_str(\u0026contract_config.exchange)\n .map_err(|e| PolyfillError::config(format!(\"Invalid exchange address: {}\", e)))?;\n\n self.build_signed_order(\n order_args.token_id.clone(),\n Side::BUY,\n chain_id,\n exchange_address,\n maker_amount,\n taker_amount,\n 0,\n extras,\n )\n }\n\n /// Create a regular order\n pub fn create_order(\n \u0026self,\n chain_id: u64,\n order_args: \u0026OrderArgs,\n expiration: u64,\n extras: \u0026ExtraOrderArgs,\n options: \u0026OrderOptions,\n ) -\u003e Result\u003cSignedOrderRequest\u003e {\n let tick_size = options.tick_size\n .ok_or_else(|| PolyfillError::validation(\"Cannot create order without tick size\"))?;\n \n let (maker_amount, taker_amount) = self.get_order_amounts(\n order_args.side,\n order_args.size,\n order_args.price,\n \u0026ROUNDING_CONFIG[\u0026tick_size],\n );\n\n let neg_risk = options.neg_risk\n .ok_or_else(|| PolyfillError::validation(\"Cannot create order without neg_risk\"))?;\n\n let contract_config = get_contract_config(chain_id, neg_risk)\n .ok_or_else(|| PolyfillError::config(\"No contract found with given chain_id and neg_risk\"))?;\n\n let exchange_address = Address::from_str(\u0026contract_config.exchange)\n .map_err(|e| PolyfillError::config(format!(\"Invalid exchange address: {}\", e)))?;\n\n self.build_signed_order(\n order_args.token_id.clone(),\n order_args.side,\n chain_id,\n exchange_address,\n maker_amount,\n taker_amount,\n expiration,\n extras,\n )\n }\n\n /// Build and sign an order\n #[allow(clippy::too_many_arguments)]\n fn build_signed_order(\n \u0026self,\n token_id: String,\n side: Side,\n chain_id: u64,\n exchange: Address,\n maker_amount: u32,\n taker_amount: u32,\n expiration: u64,\n extras: \u0026ExtraOrderArgs,\n ) -\u003e Result\u003cSignedOrderRequest\u003e {\n let seed = generate_seed();\n let taker_address = Address::from_str(\u0026extras.taker)\n .map_err(|e| PolyfillError::validation(format!(\"Invalid taker address: {}\", e)))?;\n\n let u256_token_id = U256::from_str_radix(\u0026token_id, 10)\n .map_err(|e| PolyfillError::validation(format!(\"Incorrect tokenId format: {}\", e)))?;\n\n let order = crate::auth::Order {\n salt: U256::from(seed),\n maker: self.funder,\n signer: self.signer.address(),\n taker: taker_address,\n tokenId: u256_token_id,\n makerAmount: U256::from(maker_amount),\n takerAmount: U256::from(taker_amount),\n expiration: U256::from(expiration),\n nonce: extras.nonce,\n feeRateBps: U256::from(extras.fee_rate_bps),\n side: side as u8,\n signatureType: self.sig_type as u8,\n };\n\n let signature = sign_order_message(\u0026self.signer, order, chain_id, exchange)?;\n\n Ok(SignedOrderRequest {\n salt: seed,\n maker: self.funder.to_checksum(None),\n signer: self.signer.address().to_checksum(None),\n taker: taker_address.to_checksum(None),\n token_id,\n maker_amount: maker_amount.to_string(),\n taker_amount: taker_amount.to_string(),\n expiration: expiration.to_string(),\n nonce: extras.nonce.to_string(),\n fee_rate_bps: extras.fee_rate_bps.to_string(),\n side: side.as_str().to_string(),\n signature_type: self.sig_type as u8,\n signature,\n })\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_decimal_to_token_u32() {\n let result = decimal_to_token_u32(Decimal::from_str(\"1.5\").unwrap());\n assert_eq!(result, 1_500_000);\n }\n\n #[test]\n fn test_generate_seed() {\n let seed1 = generate_seed();\n let seed2 = generate_seed();\n assert_ne!(seed1, seed2);\n }\n}\n","traces":[{"line":54,"address":[],"length":0,"stats":{"Line":0}},{"line":55,"address":[],"length":0,"stats":{"Line":0}},{"line":57,"address":[],"length":0,"stats":{"Line":0}},{"line":58,"address":[],"length":0,"stats":{"Line":0}},{"line":59,"address":[],"length":0,"stats":{"Line":0}},{"line":60,"address":[],"length":0,"stats":{"Line":0}},{"line":61,"address":[],"length":0,"stats":{"Line":0}},{"line":65,"address":[],"length":0,"stats":{"Line":0}},{"line":66,"address":[],"length":0,"stats":{"Line":0}},{"line":67,"address":[],"length":0,"stats":{"Line":0}},{"line":68,"address":[],"length":0,"stats":{"Line":0}},{"line":69,"address":[],"length":0,"stats":{"Line":0}},{"line":73,"address":[],"length":0,"stats":{"Line":0}},{"line":74,"address":[],"length":0,"stats":{"Line":0}},{"line":75,"address":[],"length":0,"stats":{"Line":0}},{"line":76,"address":[],"length":0,"stats":{"Line":0}},{"line":77,"address":[],"length":0,"stats":{"Line":0}},{"line":81,"address":[],"length":0,"stats":{"Line":0}},{"line":82,"address":[],"length":0,"stats":{"Line":0}},{"line":83,"address":[],"length":0,"stats":{"Line":0}},{"line":84,"address":[],"length":0,"stats":{"Line":0}},{"line":85,"address":[],"length":0,"stats":{"Line":0}},{"line":92,"address":[],"length":0,"stats":{"Line":0}},{"line":93,"address":[],"length":0,"stats":{"Line":0}},{"line":94,"address":[],"length":0,"stats":{"Line":0}},{"line":95,"address":[],"length":0,"stats":{"Line":0}},{"line":96,"address":[],"length":0,"stats":{"Line":0}},{"line":97,"address":[],"length":0,"stats":{"Line":0}},{"line":99,"address":[],"length":0,"stats":{"Line":0}},{"line":100,"address":[],"length":0,"stats":{"Line":0}},{"line":101,"address":[],"length":0,"stats":{"Line":0}},{"line":102,"address":[],"length":0,"stats":{"Line":0}},{"line":104,"address":[],"length":0,"stats":{"Line":0}},{"line":109,"address":[],"length":0,"stats":{"Line":2}},{"line":110,"address":[],"length":0,"stats":{"Line":4}},{"line":111,"address":[],"length":0,"stats":{"Line":8}},{"line":112,"address":[],"length":0,"stats":{"Line":6}},{"line":113,"address":[],"length":0,"stats":{"Line":4}},{"line":116,"address":[],"length":0,"stats":{"Line":2}},{"line":120,"address":[],"length":0,"stats":{"Line":1}},{"line":121,"address":[],"length":0,"stats":{"Line":5}},{"line":122,"address":[],"length":0,"stats":{"Line":2}},{"line":123,"address":[],"length":0,"stats":{"Line":2}},{"line":125,"address":[],"length":0,"stats":{"Line":4}},{"line":130,"address":[],"length":0,"stats":{"Line":0}},{"line":135,"address":[],"length":0,"stats":{"Line":0}},{"line":136,"address":[],"length":0,"stats":{"Line":0}},{"line":146,"address":[],"length":0,"stats":{"Line":0}},{"line":147,"address":[],"length":0,"stats":{"Line":0}},{"line":151,"address":[],"length":0,"stats":{"Line":0}},{"line":152,"address":[],"length":0,"stats":{"Line":0}},{"line":153,"address":[],"length":0,"stats":{"Line":0}},{"line":154,"address":[],"length":0,"stats":{"Line":0}},{"line":155,"address":[],"length":0,"stats":{"Line":0}},{"line":158,"address":[],"length":0,"stats":{"Line":0}},{"line":162,"address":[],"length":0,"stats":{"Line":0}},{"line":169,"address":[],"length":0,"stats":{"Line":0}},{"line":171,"address":[],"length":0,"stats":{"Line":0}},{"line":173,"address":[],"length":0,"stats":{"Line":0}},{"line":174,"address":[],"length":0,"stats":{"Line":0}},{"line":175,"address":[],"length":0,"stats":{"Line":0}},{"line":177,"address":[],"length":0,"stats":{"Line":0}},{"line":178,"address":[],"length":0,"stats":{"Line":0}},{"line":182,"address":[],"length":0,"stats":{"Line":0}},{"line":183,"address":[],"length":0,"stats":{"Line":0}},{"line":184,"address":[],"length":0,"stats":{"Line":0}},{"line":187,"address":[],"length":0,"stats":{"Line":0}},{"line":188,"address":[],"length":0,"stats":{"Line":0}},{"line":195,"address":[],"length":0,"stats":{"Line":0}},{"line":201,"address":[],"length":0,"stats":{"Line":0}},{"line":202,"address":[],"length":0,"stats":{"Line":0}},{"line":204,"address":[],"length":0,"stats":{"Line":0}},{"line":205,"address":[],"length":0,"stats":{"Line":0}},{"line":208,"address":[],"length":0,"stats":{"Line":0}},{"line":209,"address":[],"length":0,"stats":{"Line":0}},{"line":214,"address":[],"length":0,"stats":{"Line":0}},{"line":219,"address":[],"length":0,"stats":{"Line":0}},{"line":221,"address":[],"length":0,"stats":{"Line":0}},{"line":222,"address":[],"length":0,"stats":{"Line":0}},{"line":223,"address":[],"length":0,"stats":{"Line":0}},{"line":224,"address":[],"length":0,"stats":{"Line":0}},{"line":228,"address":[],"length":0,"stats":{"Line":0}},{"line":229,"address":[],"length":0,"stats":{"Line":0}},{"line":230,"address":[],"length":0,"stats":{"Line":0}},{"line":235,"address":[],"length":0,"stats":{"Line":0}},{"line":243,"address":[],"length":0,"stats":{"Line":0}},{"line":244,"address":[],"length":0,"stats":{"Line":0}},{"line":246,"address":[],"length":0,"stats":{"Line":0}},{"line":247,"address":[],"length":0,"stats":{"Line":0}},{"line":248,"address":[],"length":0,"stats":{"Line":0}},{"line":249,"address":[],"length":0,"stats":{"Line":0}},{"line":252,"address":[],"length":0,"stats":{"Line":0}},{"line":253,"address":[],"length":0,"stats":{"Line":0}},{"line":255,"address":[],"length":0,"stats":{"Line":0}},{"line":256,"address":[],"length":0,"stats":{"Line":0}},{"line":258,"address":[],"length":0,"stats":{"Line":0}},{"line":259,"address":[],"length":0,"stats":{"Line":0}},{"line":261,"address":[],"length":0,"stats":{"Line":0}},{"line":262,"address":[],"length":0,"stats":{"Line":0}},{"line":263,"address":[],"length":0,"stats":{"Line":0}},{"line":264,"address":[],"length":0,"stats":{"Line":0}},{"line":265,"address":[],"length":0,"stats":{"Line":0}},{"line":266,"address":[],"length":0,"stats":{"Line":0}},{"line":267,"address":[],"length":0,"stats":{"Line":0}},{"line":269,"address":[],"length":0,"stats":{"Line":0}},{"line":274,"address":[],"length":0,"stats":{"Line":0}},{"line":282,"address":[],"length":0,"stats":{"Line":0}},{"line":283,"address":[],"length":0,"stats":{"Line":0}},{"line":285,"address":[],"length":0,"stats":{"Line":0}},{"line":286,"address":[],"length":0,"stats":{"Line":0}},{"line":287,"address":[],"length":0,"stats":{"Line":0}},{"line":288,"address":[],"length":0,"stats":{"Line":0}},{"line":289,"address":[],"length":0,"stats":{"Line":0}},{"line":292,"address":[],"length":0,"stats":{"Line":0}},{"line":293,"address":[],"length":0,"stats":{"Line":0}},{"line":295,"address":[],"length":0,"stats":{"Line":0}},{"line":296,"address":[],"length":0,"stats":{"Line":0}},{"line":298,"address":[],"length":0,"stats":{"Line":0}},{"line":299,"address":[],"length":0,"stats":{"Line":0}},{"line":301,"address":[],"length":0,"stats":{"Line":0}},{"line":302,"address":[],"length":0,"stats":{"Line":0}},{"line":303,"address":[],"length":0,"stats":{"Line":0}},{"line":304,"address":[],"length":0,"stats":{"Line":0}},{"line":305,"address":[],"length":0,"stats":{"Line":0}},{"line":306,"address":[],"length":0,"stats":{"Line":0}},{"line":307,"address":[],"length":0,"stats":{"Line":0}},{"line":308,"address":[],"length":0,"stats":{"Line":0}},{"line":309,"address":[],"length":0,"stats":{"Line":0}},{"line":315,"address":[],"length":0,"stats":{"Line":0}},{"line":326,"address":[],"length":0,"stats":{"Line":0}},{"line":327,"address":[],"length":0,"stats":{"Line":0}},{"line":328,"address":[],"length":0,"stats":{"Line":0}},{"line":330,"address":[],"length":0,"stats":{"Line":0}},{"line":331,"address":[],"length":0,"stats":{"Line":0}},{"line":334,"address":[],"length":0,"stats":{"Line":0}},{"line":335,"address":[],"length":0,"stats":{"Line":0}},{"line":336,"address":[],"length":0,"stats":{"Line":0}},{"line":339,"address":[],"length":0,"stats":{"Line":0}},{"line":340,"address":[],"length":0,"stats":{"Line":0}},{"line":341,"address":[],"length":0,"stats":{"Line":0}},{"line":342,"address":[],"length":0,"stats":{"Line":0}},{"line":343,"address":[],"length":0,"stats":{"Line":0}},{"line":344,"address":[],"length":0,"stats":{"Line":0}},{"line":345,"address":[],"length":0,"stats":{"Line":0}},{"line":348,"address":[],"length":0,"stats":{"Line":0}},{"line":350,"address":[],"length":0,"stats":{"Line":0}},{"line":351,"address":[],"length":0,"stats":{"Line":0}},{"line":352,"address":[],"length":0,"stats":{"Line":0}},{"line":353,"address":[],"length":0,"stats":{"Line":0}},{"line":354,"address":[],"length":0,"stats":{"Line":0}},{"line":355,"address":[],"length":0,"stats":{"Line":0}},{"line":356,"address":[],"length":0,"stats":{"Line":0}},{"line":357,"address":[],"length":0,"stats":{"Line":0}},{"line":358,"address":[],"length":0,"stats":{"Line":0}},{"line":359,"address":[],"length":0,"stats":{"Line":0}},{"line":360,"address":[],"length":0,"stats":{"Line":0}},{"line":361,"address":[],"length":0,"stats":{"Line":0}},{"line":362,"address":[],"length":0,"stats":{"Line":0}},{"line":363,"address":[],"length":0,"stats":{"Line":0}}],"covered":11,"coverable":159},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","stream.rs"],"content":"//! Async streaming functionality for Polymarket client\n//!\n//! This module provides high-performance streaming capabilities for\n//! real-time market data and order updates.\n\nuse crate::errors::{PolyfillError, Result};\nuse crate::types::*;\nuse futures::{Stream, SinkExt, StreamExt};\nuse serde_json::Value;\nuse std::pin::Pin;\nuse std::task::{Context, Poll};\nuse tokio::sync::mpsc;\nuse tracing::{debug, error, info, warn};\nuse chrono::Utc;\n\n/// Trait for market data streams\npub trait MarketStream: Stream\u003cItem = Result\u003cStreamMessage\u003e\u003e + Send + Sync {\n /// Subscribe to market data for specific tokens\n fn subscribe(\u0026mut self, subscription: Subscription) -\u003e Result\u003c()\u003e;\n \n /// Unsubscribe from market data\n fn unsubscribe(\u0026mut self, token_ids: \u0026[String]) -\u003e Result\u003c()\u003e;\n \n /// Check if the stream is connected\n fn is_connected(\u0026self) -\u003e bool;\n \n /// Get connection statistics\n fn get_stats(\u0026self) -\u003e StreamStats;\n}\n\n/// WebSocket-based market stream implementation\n#[derive(Debug)]\npub struct WebSocketStream {\n /// WebSocket connection\n connection: Option\u003ctokio_tungstenite::WebSocketStream\u003ctokio_tungstenite::MaybeTlsStream\u003ctokio::net::TcpStream\u003e\u003e\u003e,\n /// URL for the WebSocket connection\n url: String,\n /// Authentication credentials\n auth: Option\u003cWssAuth\u003e,\n /// Current subscriptions\n subscriptions: Vec\u003cWssSubscription\u003e,\n /// Message sender for internal communication\n tx: mpsc::UnboundedSender\u003cStreamMessage\u003e,\n /// Message receiver\n rx: mpsc::UnboundedReceiver\u003cStreamMessage\u003e,\n /// Connection statistics\n stats: StreamStats,\n /// Reconnection configuration\n reconnect_config: ReconnectConfig,\n}\n\n/// Stream statistics\n#[derive(Debug, Clone)]\npub struct StreamStats {\n pub messages_received: u64,\n pub messages_sent: u64,\n pub errors: u64,\n pub last_message_time: Option\u003cchrono::DateTime\u003cUtc\u003e\u003e,\n pub connection_uptime: std::time::Duration,\n pub reconnect_count: u32,\n}\n\n/// Reconnection configuration\n#[derive(Debug, Clone)]\npub struct ReconnectConfig {\n pub max_retries: u32,\n pub base_delay: std::time::Duration,\n pub max_delay: std::time::Duration,\n pub backoff_multiplier: f64,\n}\n\nimpl Default for ReconnectConfig {\n fn default() -\u003e Self {\n Self {\n max_retries: 5,\n base_delay: std::time::Duration::from_secs(1),\n max_delay: std::time::Duration::from_secs(60),\n backoff_multiplier: 2.0,\n }\n }\n}\n\nimpl WebSocketStream {\n /// Create a new WebSocket stream\n pub fn new(url: \u0026str) -\u003e Self {\n let (tx, rx) = mpsc::unbounded_channel();\n \n Self {\n connection: None,\n url: url.to_string(),\n auth: None,\n subscriptions: Vec::new(),\n tx,\n rx,\n stats: StreamStats {\n messages_received: 0,\n messages_sent: 0,\n errors: 0,\n last_message_time: None,\n connection_uptime: std::time::Duration::ZERO,\n reconnect_count: 0,\n },\n reconnect_config: ReconnectConfig::default(),\n }\n }\n\n /// Set authentication credentials\n pub fn with_auth(mut self, auth: WssAuth) -\u003e Self {\n self.auth = Some(auth);\n self\n }\n\n /// Connect to the WebSocket\n async fn connect(\u0026mut self) -\u003e Result\u003c()\u003e {\n let (ws_stream, _) = tokio_tungstenite::connect_async(\u0026self.url).await\n .map_err(|e| PolyfillError::stream(format!(\"WebSocket connection failed: {}\", e), crate::errors::StreamErrorKind::ConnectionFailed))?;\n\n self.connection = Some(ws_stream);\n info!(\"Connected to WebSocket stream at {}\", self.url);\n Ok(())\n }\n\n /// Send a message to the WebSocket\n async fn send_message(\u0026mut self, message: Value) -\u003e Result\u003c()\u003e {\n if let Some(connection) = \u0026mut self.connection {\n let text = serde_json::to_string(\u0026message)\n .map_err(|e| PolyfillError::parse(format!(\"Failed to serialize message: {}\", e), None))?;\n \n let ws_message = tokio_tungstenite::tungstenite::Message::Text(text);\n connection.send(ws_message).await\n .map_err(|e| PolyfillError::stream(format!(\"Failed to send message: {}\", e), crate::errors::StreamErrorKind::MessageCorrupted))?;\n \n self.stats.messages_sent += 1;\n }\n \n Ok(())\n }\n\n /// Subscribe to market data using official Polymarket WebSocket API\n pub async fn subscribe_async(\u0026mut self, subscription: WssSubscription) -\u003e Result\u003c()\u003e {\n // Ensure connection\n if self.connection.is_none() {\n self.connect().await?;\n }\n\n // Send subscription message in the format expected by Polymarket\n let message = serde_json::json!({\n \"auth\": subscription.auth,\n \"markets\": subscription.markets,\n \"asset_ids\": subscription.asset_ids,\n \"type\": subscription.channel_type,\n });\n\n self.send_message(message).await?;\n self.subscriptions.push(subscription.clone());\n \n info!(\"Subscribed to {} channel\", subscription.channel_type);\n Ok(())\n }\n\n /// Subscribe to user channel (orders and trades)\n pub async fn subscribe_user_channel(\u0026mut self, markets: Vec\u003cString\u003e) -\u003e Result\u003c()\u003e {\n let auth = self.auth.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"No authentication provided for WebSocket\"))?\n .clone();\n\n let subscription = WssSubscription {\n auth,\n markets: Some(markets),\n asset_ids: None,\n channel_type: \"USER\".to_string(),\n };\n\n self.subscribe_async(subscription).await\n }\n\n /// Subscribe to market channel (order book and trades)\n pub async fn subscribe_market_channel(\u0026mut self, asset_ids: Vec\u003cString\u003e) -\u003e Result\u003c()\u003e {\n let auth = self.auth.as_ref()\n .ok_or_else(|| PolyfillError::auth(\"No authentication provided for WebSocket\"))?\n .clone();\n\n let subscription = WssSubscription {\n auth,\n markets: None,\n asset_ids: Some(asset_ids),\n channel_type: \"MARKET\".to_string(),\n };\n\n self.subscribe_async(subscription).await\n }\n\n /// Unsubscribe from market data\n pub async fn unsubscribe_async(\u0026mut self, token_ids: \u0026[String]) -\u003e Result\u003c()\u003e {\n // Note: Polymarket WebSocket API doesn't seem to have explicit unsubscribe\n // We'll just remove from our local subscriptions\n self.subscriptions.retain(|sub| {\n match sub.channel_type.as_str() {\n \"USER\" =\u003e {\n if let Some(markets) = \u0026sub.markets {\n !token_ids.iter().any(|id| markets.contains(id))\n } else {\n true\n }\n }\n \"MARKET\" =\u003e {\n if let Some(asset_ids) = \u0026sub.asset_ids {\n !token_ids.iter().any(|id| asset_ids.contains(id))\n } else {\n true\n }\n }\n _ =\u003e true\n }\n });\n \n info!(\"Unsubscribed from {} tokens\", token_ids.len());\n Ok(())\n }\n\n /// Handle incoming WebSocket messages\n async fn handle_message(\u0026mut self, message: tokio_tungstenite::tungstenite::Message) -\u003e Result\u003c()\u003e {\n match message {\n tokio_tungstenite::tungstenite::Message::Text(text) =\u003e {\n debug!(\"Received WebSocket message: {}\", text);\n \n // Parse the message according to Polymarket's format\n let stream_message = self.parse_polymarket_message(\u0026text)?;\n \n // Send to internal channel\n if let Err(e) = self.tx.send(stream_message) {\n error!(\"Failed to send message to internal channel: {}\", e);\n }\n \n self.stats.messages_received += 1;\n self.stats.last_message_time = Some(Utc::now());\n }\n tokio_tungstenite::tungstenite::Message::Close(_) =\u003e {\n info!(\"WebSocket connection closed by server\");\n self.connection = None;\n }\n tokio_tungstenite::tungstenite::Message::Ping(data) =\u003e {\n // Respond with pong\n if let Some(connection) = \u0026mut self.connection {\n let pong = tokio_tungstenite::tungstenite::Message::Pong(data);\n if let Err(e) = connection.send(pong).await {\n error!(\"Failed to send pong: {}\", e);\n }\n }\n }\n tokio_tungstenite::tungstenite::Message::Pong(_) =\u003e {\n // Handle pong if needed\n debug!(\"Received pong\");\n }\n tokio_tungstenite::tungstenite::Message::Binary(_) =\u003e {\n warn!(\"Received binary message (not supported)\");\n }\n tokio_tungstenite::tungstenite::Message::Frame(_) =\u003e {\n warn!(\"Received raw frame (not supported)\");\n }\n }\n \n Ok(())\n }\n\n /// Parse Polymarket WebSocket message format\n fn parse_polymarket_message(\u0026self, text: \u0026str) -\u003e Result\u003cStreamMessage\u003e {\n let value: Value = serde_json::from_str(text)\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse WebSocket message: {}\", e), Some(Box::new(e))))?;\n\n // Extract message type\n let message_type = value.get(\"type\")\n .and_then(|v| v.as_str())\n .ok_or_else(|| PolyfillError::parse(\"Missing 'type' field in WebSocket message\", None))?;\n\n match message_type {\n \"book_update\" =\u003e {\n let data = serde_json::from_value(value.get(\"data\").unwrap_or(\u0026Value::Null).clone())\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse book update: {}\", e), Some(Box::new(e))))?;\n Ok(StreamMessage::BookUpdate { data })\n }\n \"trade\" =\u003e {\n let data = serde_json::from_value(value.get(\"data\").unwrap_or(\u0026Value::Null).clone())\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse trade: {}\", e), Some(Box::new(e))))?;\n Ok(StreamMessage::Trade { data })\n }\n \"order_update\" =\u003e {\n let data = serde_json::from_value(value.get(\"data\").unwrap_or(\u0026Value::Null).clone())\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse order update: {}\", e), Some(Box::new(e))))?;\n Ok(StreamMessage::OrderUpdate { data })\n }\n \"user_order_update\" =\u003e {\n let data = serde_json::from_value(value.get(\"data\").unwrap_or(\u0026Value::Null).clone())\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse user order update: {}\", e), Some(Box::new(e))))?;\n Ok(StreamMessage::UserOrderUpdate { data })\n }\n \"user_trade\" =\u003e {\n let data = serde_json::from_value(value.get(\"data\").unwrap_or(\u0026Value::Null).clone())\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse user trade: {}\", e), Some(Box::new(e))))?;\n Ok(StreamMessage::UserTrade { data })\n }\n \"market_book_update\" =\u003e {\n let data = serde_json::from_value(value.get(\"data\").unwrap_or(\u0026Value::Null).clone())\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse market book update: {}\", e), Some(Box::new(e))))?;\n Ok(StreamMessage::MarketBookUpdate { data })\n }\n \"market_trade\" =\u003e {\n let data = serde_json::from_value(value.get(\"data\").unwrap_or(\u0026Value::Null).clone())\n .map_err(|e| PolyfillError::parse(format!(\"Failed to parse market trade: {}\", e), Some(Box::new(e))))?;\n Ok(StreamMessage::MarketTrade { data })\n }\n \"heartbeat\" =\u003e {\n let timestamp = value.get(\"timestamp\")\n .and_then(|v| v.as_u64())\n .map(|ts| chrono::DateTime::from_timestamp(ts as i64, 0).unwrap_or_default())\n .unwrap_or_else(Utc::now);\n Ok(StreamMessage::Heartbeat { timestamp })\n }\n _ =\u003e {\n warn!(\"Unknown message type: {}\", message_type);\n // Return heartbeat as fallback\n Ok(StreamMessage::Heartbeat { timestamp: Utc::now() })\n }\n }\n }\n\n /// Reconnect with exponential backoff\n async fn reconnect(\u0026mut self) -\u003e Result\u003c()\u003e {\n let mut delay = self.reconnect_config.base_delay;\n let mut retries = 0;\n\n while retries \u003c self.reconnect_config.max_retries {\n warn!(\"Attempting to reconnect (attempt {})\", retries + 1);\n \n match self.connect().await {\n Ok(()) =\u003e {\n info!(\"Successfully reconnected\");\n self.stats.reconnect_count += 1;\n \n // Resubscribe to all previous subscriptions\n let subscriptions = self.subscriptions.clone();\n for subscription in subscriptions {\n self.send_message(serde_json::to_value(subscription)?).await?;\n }\n \n return Ok(());\n }\n Err(e) =\u003e {\n error!(\"Reconnection attempt {} failed: {}\", retries + 1, e);\n retries += 1;\n \n if retries \u003c self.reconnect_config.max_retries {\n tokio::time::sleep(delay).await;\n delay = std::cmp::min(\n delay.mul_f64(self.reconnect_config.backoff_multiplier),\n self.reconnect_config.max_delay\n );\n }\n }\n }\n }\n\n Err(PolyfillError::stream(\n format!(\"Failed to reconnect after {} attempts\", self.reconnect_config.max_retries),\n crate::errors::StreamErrorKind::ConnectionFailed\n ))\n }\n}\n\nimpl Stream for WebSocketStream {\n type Item = Result\u003cStreamMessage\u003e;\n\n fn poll_next(mut self: Pin\u003c\u0026mut Self\u003e, cx: \u0026mut Context\u003c'_\u003e) -\u003e Poll\u003cOption\u003cSelf::Item\u003e\u003e {\n // First check internal channel\n if let Poll::Ready(Some(message)) = self.rx.poll_recv(cx) {\n return Poll::Ready(Some(Ok(message)));\n }\n\n // Then check WebSocket connection\n if let Some(connection) = \u0026mut self.connection {\n match connection.poll_next_unpin(cx) {\n Poll::Ready(Some(Ok(_message))) =\u003e {\n // Simplified message handling\n Poll::Ready(Some(Ok(StreamMessage::Heartbeat { timestamp: Utc::now() })))\n }\n Poll::Ready(Some(Err(e))) =\u003e {\n error!(\"WebSocket error: {}\", e);\n self.stats.errors += 1;\n Poll::Ready(Some(Err(e.into())))\n }\n Poll::Ready(None) =\u003e {\n info!(\"WebSocket stream ended\");\n Poll::Ready(None)\n }\n Poll::Pending =\u003e Poll::Pending,\n }\n } else {\n Poll::Ready(None)\n }\n }\n}\n\nimpl MarketStream for WebSocketStream {\n fn subscribe(\u0026mut self, _subscription: Subscription) -\u003e Result\u003c()\u003e {\n // This is for backward compatibility - use subscribe_async for new code\n Ok(())\n }\n\n fn unsubscribe(\u0026mut self, _token_ids: \u0026[String]) -\u003e Result\u003c()\u003e {\n // This is for backward compatibility - use unsubscribe_async for new code\n Ok(())\n }\n\n fn is_connected(\u0026self) -\u003e bool {\n self.connection.is_some()\n }\n\n fn get_stats(\u0026self) -\u003e StreamStats {\n self.stats.clone()\n }\n}\n\n/// Mock stream for testing\n#[derive(Debug)]\npub struct MockStream {\n messages: Vec\u003cResult\u003cStreamMessage\u003e\u003e,\n index: usize,\n connected: bool,\n}\n\nimpl MockStream {\n pub fn new() -\u003e Self {\n Self {\n messages: Vec::new(),\n index: 0,\n connected: true,\n }\n }\n\n pub fn add_message(\u0026mut self, message: StreamMessage) {\n self.messages.push(Ok(message));\n }\n\n pub fn add_error(\u0026mut self, error: PolyfillError) {\n self.messages.push(Err(error));\n }\n\n pub fn set_connected(\u0026mut self, connected: bool) {\n self.connected = connected;\n }\n}\n\nimpl Stream for MockStream {\n type Item = Result\u003cStreamMessage\u003e;\n\n fn poll_next(mut self: Pin\u003c\u0026mut Self\u003e, _cx: \u0026mut Context\u003c'_\u003e) -\u003e Poll\u003cOption\u003cSelf::Item\u003e\u003e {\n if self.index \u003e= self.messages.len() {\n Poll::Ready(None)\n } else {\n let message = self.messages[self.index].clone();\n self.index += 1;\n Poll::Ready(Some(message))\n }\n }\n}\n\nimpl MarketStream for MockStream {\n fn subscribe(\u0026mut self, _subscription: Subscription) -\u003e Result\u003c()\u003e {\n Ok(())\n }\n\n fn unsubscribe(\u0026mut self, _token_ids: \u0026[String]) -\u003e Result\u003c()\u003e {\n Ok(())\n }\n\n fn is_connected(\u0026self) -\u003e bool {\n self.connected\n }\n\n fn get_stats(\u0026self) -\u003e StreamStats {\n StreamStats {\n messages_received: self.messages.len() as u64,\n messages_sent: 0,\n errors: self.messages.iter().filter(|m| m.is_err()).count() as u64,\n last_message_time: None,\n connection_uptime: std::time::Duration::ZERO,\n reconnect_count: 0,\n }\n }\n}\n\n/// Stream manager for handling multiple streams\npub struct StreamManager {\n streams: Vec\u003cBox\u003cdyn MarketStream\u003e\u003e,\n message_tx: mpsc::UnboundedSender\u003cStreamMessage\u003e,\n message_rx: mpsc::UnboundedReceiver\u003cStreamMessage\u003e,\n}\n\nimpl StreamManager {\n pub fn new() -\u003e Self {\n let (message_tx, message_rx) = mpsc::unbounded_channel();\n \n Self {\n streams: Vec::new(),\n message_tx,\n message_rx,\n }\n }\n\n pub fn add_stream(\u0026mut self, stream: Box\u003cdyn MarketStream\u003e) {\n self.streams.push(stream);\n }\n\n pub fn get_message_receiver(\u0026mut self) -\u003e mpsc::UnboundedReceiver\u003cStreamMessage\u003e {\n // Note: UnboundedReceiver doesn't implement Clone\n // In a real implementation, you'd want to use a different approach\n // For now, we'll return a dummy receiver\n let (_, rx) = mpsc::unbounded_channel();\n rx\n }\n\n pub fn broadcast_message(\u0026self, message: StreamMessage) -\u003e Result\u003c()\u003e {\n self.message_tx.send(message)\n .map_err(|e| PolyfillError::internal(\"Failed to broadcast message\", e))\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_mock_stream() {\n let mut stream = MockStream::new();\n \n // Add some test messages\n stream.add_message(StreamMessage::Heartbeat { timestamp: Utc::now() });\n stream.add_message(StreamMessage::BookUpdate {\n data: OrderDelta {\n token_id: \"test\".to_string(),\n timestamp: Utc::now(),\n side: Side::BUY,\n price: rust_decimal_macros::dec!(0.5),\n size: rust_decimal_macros::dec!(100),\n sequence: 1,\n }\n });\n \n assert!(stream.is_connected());\n assert_eq!(stream.get_stats().messages_received, 2);\n }\n\n #[test]\n fn test_stream_manager() {\n let mut manager = StreamManager::new();\n let mock_stream = Box::new(MockStream::new());\n manager.add_stream(mock_stream);\n \n // Test message broadcasting\n let message = StreamMessage::Heartbeat { timestamp: Utc::now() };\n assert!(manager.broadcast_message(message).is_ok());\n }\n} ","traces":[{"line":73,"address":[],"length":0,"stats":{"Line":0}},{"line":76,"address":[],"length":0,"stats":{"Line":0}},{"line":77,"address":[],"length":0,"stats":{"Line":0}},{"line":85,"address":[],"length":0,"stats":{"Line":0}},{"line":86,"address":[],"length":0,"stats":{"Line":0}},{"line":90,"address":[],"length":0,"stats":{"Line":0}},{"line":92,"address":[],"length":0,"stats":{"Line":0}},{"line":95,"address":[],"length":0,"stats":{"Line":0}},{"line":103,"address":[],"length":0,"stats":{"Line":0}},{"line":108,"address":[],"length":0,"stats":{"Line":0}},{"line":109,"address":[],"length":0,"stats":{"Line":0}},{"line":110,"address":[],"length":0,"stats":{"Line":0}},{"line":114,"address":[],"length":0,"stats":{"Line":0}},{"line":115,"address":[],"length":0,"stats":{"Line":0}},{"line":116,"address":[],"length":0,"stats":{"Line":0}},{"line":118,"address":[],"length":0,"stats":{"Line":0}},{"line":119,"address":[],"length":0,"stats":{"Line":0}},{"line":120,"address":[],"length":0,"stats":{"Line":0}},{"line":124,"address":[],"length":0,"stats":{"Line":0}},{"line":125,"address":[],"length":0,"stats":{"Line":0}},{"line":126,"address":[],"length":0,"stats":{"Line":0}},{"line":127,"address":[],"length":0,"stats":{"Line":0}},{"line":129,"address":[],"length":0,"stats":{"Line":0}},{"line":130,"address":[],"length":0,"stats":{"Line":0}},{"line":131,"address":[],"length":0,"stats":{"Line":0}},{"line":133,"address":[],"length":0,"stats":{"Line":0}},{"line":136,"address":[],"length":0,"stats":{"Line":0}},{"line":140,"address":[],"length":0,"stats":{"Line":0}},{"line":142,"address":[],"length":0,"stats":{"Line":0}},{"line":143,"address":[],"length":0,"stats":{"Line":0}},{"line":147,"address":[],"length":0,"stats":{"Line":0}},{"line":148,"address":[],"length":0,"stats":{"Line":0}},{"line":149,"address":[],"length":0,"stats":{"Line":0}},{"line":150,"address":[],"length":0,"stats":{"Line":0}},{"line":151,"address":[],"length":0,"stats":{"Line":0}},{"line":154,"address":[],"length":0,"stats":{"Line":0}},{"line":155,"address":[],"length":0,"stats":{"Line":0}},{"line":157,"address":[],"length":0,"stats":{"Line":0}},{"line":158,"address":[],"length":0,"stats":{"Line":0}},{"line":162,"address":[],"length":0,"stats":{"Line":0}},{"line":163,"address":[],"length":0,"stats":{"Line":0}},{"line":164,"address":[],"length":0,"stats":{"Line":0}},{"line":169,"address":[],"length":0,"stats":{"Line":0}},{"line":171,"address":[],"length":0,"stats":{"Line":0}},{"line":174,"address":[],"length":0,"stats":{"Line":0}},{"line":178,"address":[],"length":0,"stats":{"Line":0}},{"line":179,"address":[],"length":0,"stats":{"Line":0}},{"line":180,"address":[],"length":0,"stats":{"Line":0}},{"line":186,"address":[],"length":0,"stats":{"Line":0}},{"line":187,"address":[],"length":0,"stats":{"Line":0}},{"line":190,"address":[],"length":0,"stats":{"Line":0}},{"line":194,"address":[],"length":0,"stats":{"Line":0}},{"line":197,"address":[],"length":0,"stats":{"Line":0}},{"line":198,"address":[],"length":0,"stats":{"Line":0}},{"line":199,"address":[],"length":0,"stats":{"Line":0}},{"line":200,"address":[],"length":0,"stats":{"Line":0}},{"line":201,"address":[],"length":0,"stats":{"Line":0}},{"line":203,"address":[],"length":0,"stats":{"Line":0}},{"line":206,"address":[],"length":0,"stats":{"Line":0}},{"line":207,"address":[],"length":0,"stats":{"Line":0}},{"line":208,"address":[],"length":0,"stats":{"Line":0}},{"line":210,"address":[],"length":0,"stats":{"Line":0}},{"line":213,"address":[],"length":0,"stats":{"Line":0}},{"line":217,"address":[],"length":0,"stats":{"Line":0}},{"line":218,"address":[],"length":0,"stats":{"Line":0}},{"line":222,"address":[],"length":0,"stats":{"Line":0}},{"line":223,"address":[],"length":0,"stats":{"Line":0}},{"line":224,"address":[],"length":0,"stats":{"Line":0}},{"line":225,"address":[],"length":0,"stats":{"Line":0}},{"line":228,"address":[],"length":0,"stats":{"Line":0}},{"line":231,"address":[],"length":0,"stats":{"Line":0}},{"line":232,"address":[],"length":0,"stats":{"Line":0}},{"line":235,"address":[],"length":0,"stats":{"Line":0}},{"line":236,"address":[],"length":0,"stats":{"Line":0}},{"line":239,"address":[],"length":0,"stats":{"Line":0}},{"line":240,"address":[],"length":0,"stats":{"Line":0}},{"line":242,"address":[],"length":0,"stats":{"Line":0}},{"line":244,"address":[],"length":0,"stats":{"Line":0}},{"line":245,"address":[],"length":0,"stats":{"Line":0}},{"line":246,"address":[],"length":0,"stats":{"Line":0}},{"line":247,"address":[],"length":0,"stats":{"Line":0}},{"line":253,"address":[],"length":0,"stats":{"Line":0}},{"line":256,"address":[],"length":0,"stats":{"Line":0}},{"line":259,"address":[],"length":0,"stats":{"Line":0}},{"line":263,"address":[],"length":0,"stats":{"Line":0}},{"line":267,"address":[],"length":0,"stats":{"Line":0}},{"line":268,"address":[],"length":0,"stats":{"Line":0}},{"line":269,"address":[],"length":0,"stats":{"Line":0}},{"line":272,"address":[],"length":0,"stats":{"Line":0}},{"line":273,"address":[],"length":0,"stats":{"Line":0}},{"line":274,"address":[],"length":0,"stats":{"Line":0}},{"line":276,"address":[],"length":0,"stats":{"Line":0}},{"line":277,"address":[],"length":0,"stats":{"Line":0}},{"line":278,"address":[],"length":0,"stats":{"Line":0}},{"line":279,"address":[],"length":0,"stats":{"Line":0}},{"line":280,"address":[],"length":0,"stats":{"Line":0}},{"line":282,"address":[],"length":0,"stats":{"Line":0}},{"line":283,"address":[],"length":0,"stats":{"Line":0}},{"line":284,"address":[],"length":0,"stats":{"Line":0}},{"line":285,"address":[],"length":0,"stats":{"Line":0}},{"line":287,"address":[],"length":0,"stats":{"Line":0}},{"line":288,"address":[],"length":0,"stats":{"Line":0}},{"line":289,"address":[],"length":0,"stats":{"Line":0}},{"line":290,"address":[],"length":0,"stats":{"Line":0}},{"line":292,"address":[],"length":0,"stats":{"Line":0}},{"line":293,"address":[],"length":0,"stats":{"Line":0}},{"line":294,"address":[],"length":0,"stats":{"Line":0}},{"line":295,"address":[],"length":0,"stats":{"Line":0}},{"line":297,"address":[],"length":0,"stats":{"Line":0}},{"line":298,"address":[],"length":0,"stats":{"Line":0}},{"line":299,"address":[],"length":0,"stats":{"Line":0}},{"line":300,"address":[],"length":0,"stats":{"Line":0}},{"line":302,"address":[],"length":0,"stats":{"Line":0}},{"line":303,"address":[],"length":0,"stats":{"Line":0}},{"line":304,"address":[],"length":0,"stats":{"Line":0}},{"line":305,"address":[],"length":0,"stats":{"Line":0}},{"line":307,"address":[],"length":0,"stats":{"Line":0}},{"line":308,"address":[],"length":0,"stats":{"Line":0}},{"line":309,"address":[],"length":0,"stats":{"Line":0}},{"line":310,"address":[],"length":0,"stats":{"Line":0}},{"line":312,"address":[],"length":0,"stats":{"Line":0}},{"line":313,"address":[],"length":0,"stats":{"Line":0}},{"line":314,"address":[],"length":0,"stats":{"Line":0}},{"line":315,"address":[],"length":0,"stats":{"Line":0}},{"line":316,"address":[],"length":0,"stats":{"Line":0}},{"line":317,"address":[],"length":0,"stats":{"Line":0}},{"line":320,"address":[],"length":0,"stats":{"Line":0}},{"line":322,"address":[],"length":0,"stats":{"Line":0}},{"line":328,"address":[],"length":0,"stats":{"Line":0}},{"line":329,"address":[],"length":0,"stats":{"Line":0}},{"line":330,"address":[],"length":0,"stats":{"Line":0}},{"line":332,"address":[],"length":0,"stats":{"Line":0}},{"line":333,"address":[],"length":0,"stats":{"Line":0}},{"line":335,"address":[],"length":0,"stats":{"Line":0}},{"line":337,"address":[],"length":0,"stats":{"Line":0}},{"line":338,"address":[],"length":0,"stats":{"Line":0}},{"line":341,"address":[],"length":0,"stats":{"Line":0}},{"line":342,"address":[],"length":0,"stats":{"Line":0}},{"line":343,"address":[],"length":0,"stats":{"Line":0}},{"line":346,"address":[],"length":0,"stats":{"Line":0}},{"line":348,"address":[],"length":0,"stats":{"Line":0}},{"line":349,"address":[],"length":0,"stats":{"Line":0}},{"line":350,"address":[],"length":0,"stats":{"Line":0}},{"line":352,"address":[],"length":0,"stats":{"Line":0}},{"line":353,"address":[],"length":0,"stats":{"Line":0}},{"line":354,"address":[],"length":0,"stats":{"Line":0}},{"line":355,"address":[],"length":0,"stats":{"Line":0}},{"line":356,"address":[],"length":0,"stats":{"Line":0}},{"line":363,"address":[],"length":0,"stats":{"Line":0}},{"line":364,"address":[],"length":0,"stats":{"Line":0}},{"line":365,"address":[],"length":0,"stats":{"Line":0}},{"line":373,"address":[],"length":0,"stats":{"Line":0}},{"line":375,"address":[],"length":0,"stats":{"Line":0}},{"line":376,"address":[],"length":0,"stats":{"Line":0}},{"line":380,"address":[],"length":0,"stats":{"Line":0}},{"line":381,"address":[],"length":0,"stats":{"Line":0}},{"line":382,"address":[],"length":0,"stats":{"Line":0}},{"line":384,"address":[],"length":0,"stats":{"Line":0}},{"line":386,"address":[],"length":0,"stats":{"Line":0}},{"line":387,"address":[],"length":0,"stats":{"Line":0}},{"line":388,"address":[],"length":0,"stats":{"Line":0}},{"line":389,"address":[],"length":0,"stats":{"Line":0}},{"line":392,"address":[],"length":0,"stats":{"Line":0}},{"line":393,"address":[],"length":0,"stats":{"Line":0}},{"line":395,"address":[],"length":0,"stats":{"Line":0}},{"line":398,"address":[],"length":0,"stats":{"Line":0}},{"line":404,"address":[],"length":0,"stats":{"Line":0}},{"line":406,"address":[],"length":0,"stats":{"Line":0}},{"line":409,"address":[],"length":0,"stats":{"Line":0}},{"line":411,"address":[],"length":0,"stats":{"Line":0}},{"line":414,"address":[],"length":0,"stats":{"Line":0}},{"line":415,"address":[],"length":0,"stats":{"Line":0}},{"line":418,"address":[],"length":0,"stats":{"Line":0}},{"line":419,"address":[],"length":0,"stats":{"Line":0}},{"line":432,"address":[],"length":0,"stats":{"Line":2}},{"line":434,"address":[],"length":0,"stats":{"Line":2}},{"line":440,"address":[],"length":0,"stats":{"Line":2}},{"line":441,"address":[],"length":0,"stats":{"Line":6}},{"line":444,"address":[],"length":0,"stats":{"Line":0}},{"line":445,"address":[],"length":0,"stats":{"Line":0}},{"line":448,"address":[],"length":0,"stats":{"Line":0}},{"line":449,"address":[],"length":0,"stats":{"Line":0}},{"line":456,"address":[],"length":0,"stats":{"Line":0}},{"line":457,"address":[],"length":0,"stats":{"Line":0}},{"line":458,"address":[],"length":0,"stats":{"Line":0}},{"line":460,"address":[],"length":0,"stats":{"Line":0}},{"line":461,"address":[],"length":0,"stats":{"Line":0}},{"line":462,"address":[],"length":0,"stats":{"Line":0}},{"line":468,"address":[],"length":0,"stats":{"Line":0}},{"line":469,"address":[],"length":0,"stats":{"Line":0}},{"line":472,"address":[],"length":0,"stats":{"Line":0}},{"line":473,"address":[],"length":0,"stats":{"Line":0}},{"line":476,"address":[],"length":0,"stats":{"Line":1}},{"line":477,"address":[],"length":0,"stats":{"Line":1}},{"line":480,"address":[],"length":0,"stats":{"Line":1}},{"line":482,"address":[],"length":0,"stats":{"Line":1}},{"line":484,"address":[],"length":0,"stats":{"Line":7}},{"line":500,"address":[],"length":0,"stats":{"Line":1}},{"line":501,"address":[],"length":0,"stats":{"Line":3}},{"line":504,"address":[],"length":0,"stats":{"Line":2}},{"line":510,"address":[],"length":0,"stats":{"Line":1}},{"line":511,"address":[],"length":0,"stats":{"Line":3}},{"line":514,"address":[],"length":0,"stats":{"Line":0}},{"line":518,"address":[],"length":0,"stats":{"Line":0}},{"line":519,"address":[],"length":0,"stats":{"Line":0}},{"line":522,"address":[],"length":0,"stats":{"Line":1}},{"line":523,"address":[],"length":0,"stats":{"Line":3}},{"line":524,"address":[],"length":0,"stats":{"Line":1}}],"covered":17,"coverable":208},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","types.rs"],"content":"//! Core types for the Polymarket client\n//!\n//! This module defines all the stable public types used throughout the client.\n//! These types are optimized for latency-sensitive trading environments.\n\nuse alloy_primitives::{Address, U256};\nuse chrono::{DateTime, Utc};\nuse rust_decimal::Decimal;\nuse rust_decimal::prelude::ToPrimitive;\nuse serde::{Deserialize, Serialize};\n\n// ============================================================================\n// FIXED-POINT OPTIMIZATION FOR HOT PATH PERFORMANCE\n// ============================================================================\n//\n// Instead of using rust_decimal::Decimal everywhere (which allocates),\n// I've used fixed-point integers for the performance-critical order book operations.\n//\n// Why this matters:\n// - Decimal operations can be 10-100x slower than integer operations\n// - Decimal allocates memory for each calculation\n// - In an order book like this we process thousands of price updates per second\n// - Most prices can be represented as integer ticks (e.g., $0.6543 = 6543 ticks)\n//\n// The strategy:\n// 1. Convert Decimal to fixed-point on ingress (when data comes in)\n// 2. Do all hot-path calculations with integers\n// 3. Convert back to Decimal only at the edges (API responses, user display)\n//\n// This is like how video games handle positions, they use integers internally\n// for speed, but show floating-point coordinates to players.\n/// Each tick represents 0.0001 (1/10,000) of the base unit\n/// Examples:\n/// - $0.6543 = 6543 ticks\n/// - $1.0000 = 10000 ticks \n/// - $0.0001 = 1 tick (minimum price increment)\n/// \n/// Why u32? \n/// - Can represent prices from $0.0001 to $429,496.7295 (way more than needed)\n/// - Fits in CPU register for fast operations\n/// - No sign bit needed since prices are always positive\npub type Price = u32;\n\n/// Quantity/size represented as fixed-point integer for performance\n/// \n/// Each unit represents 0.0001 (1/10,000) of a token\n/// Examples:\n/// - 100.0 tokens = 1,000,000 units\n/// - 0.0001 tokens = 1 unit (minimum size increment)\n/// \n/// Why i64?\n/// - Can represent quantities from -922,337,203,685.4775 to +922,337,203,685.4775\n/// - Signed because we need to handle both buys (+) and sells (-)\n/// - Large enough for any realistic trading size\npub type Qty = i64;\n\n/// Scale factor for converting between Decimal and fixed-point\n/// \n/// We use 10,000 (1e4) as our scale factor, giving us 4 decimal places of precision.\n/// This is perfect for most prediction markets where prices are between $0.01-$0.99\n/// and we need precision to the nearest $0.0001.\npub const SCALE_FACTOR: i64 = 10_000;\n\n/// Maximum valid price in ticks (prevents overflow)\n/// This represents $429,496.7295 which is way higher than any prediction market price\npub const MAX_PRICE_TICKS: Price = Price::MAX;\n\n/// Minimum valid price in ticks (1 tick = $0.0001)\npub const MIN_PRICE_TICKS: Price = 1;\n\n/// Maximum valid quantity (prevents overflow in calculations)\npub const MAX_QTY: Qty = Qty::MAX / 2; // Leave room for intermediate calculations\n\n// ============================================================================\n// CONVERSION FUNCTIONS BETWEEN DECIMAL AND FIXED-POINT\n// ============================================================================\n//\n// These functions handle the conversion between the external Decimal API\n// and our internal fixed-point representation. They're designed to be fast\n// and handle edge cases gracefully.\n\n/// Convert a Decimal price to fixed-point ticks\n/// \n/// This is called when we receive price data from the API or user input.\n/// We quantize the price to the nearest tick to ensure all prices are\n/// aligned to our internal representation.\n/// \n/// Examples:\n/// - decimal_to_price(Decimal::from_str(\"0.6543\")) = Ok(6543)\n/// - decimal_to_price(Decimal::from_str(\"1.0000\")) = Ok(10000)\n/// - decimal_to_price(Decimal::from_str(\"0.00005\")) = Ok(1) // Rounds up to min tick\npub fn decimal_to_price(decimal: Decimal) -\u003e std::result::Result\u003cPrice, \u0026'static str\u003e {\n // Convert to fixed-point by multiplying by scale factor\n let scaled = decimal * Decimal::from(SCALE_FACTOR);\n \n // Round to nearest integer (this handles tick alignment automatically)\n let rounded = scaled.round();\n \n // Convert to u64 first to handle the conversion safely\n let as_u64 = rounded.to_u64().ok_or(\"Price too large or negative\")?;\n \n // Check bounds\n if as_u64 \u003c MIN_PRICE_TICKS as u64 {\n return Ok(MIN_PRICE_TICKS); // Clamp to minimum\n }\n if as_u64 \u003e MAX_PRICE_TICKS as u64 {\n return Err(\"Price exceeds maximum\");\n }\n \n Ok(as_u64 as Price)\n}\n\n/// Convert fixed-point ticks back to Decimal price\n/// \n/// This is called when we need to return price data to the API or display to users.\n/// It's the inverse of decimal_to_price().\n/// \n/// Examples:\n/// - price_to_decimal(6543) = Decimal::from_str(\"0.6543\")\n/// - price_to_decimal(10000) = Decimal::from_str(\"1.0000\")\npub fn price_to_decimal(ticks: Price) -\u003e Decimal {\n Decimal::from(ticks) / Decimal::from(SCALE_FACTOR)\n}\n\n/// Convert a Decimal quantity to fixed-point units\n/// \n/// Similar to decimal_to_price but handles signed quantities.\n/// Quantities can be negative (for sells or position changes).\n/// \n/// Examples:\n/// - decimal_to_qty(Decimal::from_str(\"100.0\")) = Ok(1000000)\n/// - decimal_to_qty(Decimal::from_str(\"-50.5\")) = Ok(-505000)\npub fn decimal_to_qty(decimal: Decimal) -\u003e std::result::Result\u003cQty, \u0026'static str\u003e {\n let scaled = decimal * Decimal::from(SCALE_FACTOR);\n let rounded = scaled.round();\n \n let as_i64 = rounded.to_i64().ok_or(\"Quantity too large\")?;\n \n if as_i64.abs() \u003e MAX_QTY {\n return Err(\"Quantity exceeds maximum\");\n }\n \n Ok(as_i64)\n}\n\n/// Convert fixed-point units back to Decimal quantity\n/// \n/// Examples:\n/// - qty_to_decimal(1000000) = Decimal::from_str(\"100.0\")\n/// - qty_to_decimal(-505000) = Decimal::from_str(\"-50.5\")\npub fn qty_to_decimal(units: Qty) -\u003e Decimal {\n Decimal::from(units) / Decimal::from(SCALE_FACTOR)\n}\n\n/// Check if a price is properly tick-aligned\n/// \n/// This is used to validate incoming price data. In a well-behaved system,\n/// all prices should already be tick-aligned, but we check anyway to catch\n/// bugs or malicious data.\n/// \n/// A price is tick-aligned if it's an exact multiple of the minimum tick size.\n/// Since we use integer ticks internally, this just checks if the price\n/// converts cleanly to our internal representation.\npub fn is_price_tick_aligned(decimal: Decimal, tick_size_decimal: Decimal) -\u003e bool {\n // Convert tick size to our internal representation\n let tick_size_ticks = match decimal_to_price(tick_size_decimal) {\n Ok(ticks) =\u003e ticks,\n Err(_) =\u003e return false,\n };\n \n // Convert the price to ticks\n let price_ticks = match decimal_to_price(decimal) {\n Ok(ticks) =\u003e ticks,\n Err(_) =\u003e return false,\n };\n \n // Check if price is a multiple of tick size\n // If tick_size_ticks is 0, we consider everything aligned (no restrictions)\n if tick_size_ticks == 0 {\n return true;\n }\n \n price_ticks % tick_size_ticks == 0\n}\n\n/// Trading side for orders\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum Side {\n BUY = 0,\n SELL = 1,\n}\n\nimpl Side {\n pub fn as_str(\u0026self) -\u003e \u0026'static str {\n match self {\n Side::BUY =\u003e \"BUY\",\n Side::SELL =\u003e \"SELL\",\n }\n }\n\n pub fn opposite(\u0026self) -\u003e Self {\n match self {\n Side::BUY =\u003e Side::SELL,\n Side::SELL =\u003e Side::BUY,\n }\n }\n}\n\n/// Order type specifications\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum OrderType {\n GTC,\n FOK,\n GTD,\n}\n\nimpl OrderType {\n pub fn as_str(\u0026self) -\u003e \u0026'static str {\n match self {\n OrderType::GTC =\u003e \"GTC\",\n OrderType::FOK =\u003e \"FOK\",\n OrderType::GTD =\u003e \"GTD\",\n }\n }\n}\n\n/// Order status in the system\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum OrderStatus {\n #[serde(rename = \"LIVE\")]\n Live,\n #[serde(rename = \"CANCELLED\")]\n Cancelled,\n #[serde(rename = \"FILLED\")]\n Filled,\n #[serde(rename = \"PARTIAL\")]\n Partial,\n #[serde(rename = \"EXPIRED\")]\n Expired,\n}\n\n/// Market snapshot representing current state\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct MarketSnapshot {\n pub token_id: String,\n pub market_id: String,\n pub timestamp: DateTime\u003cUtc\u003e,\n pub bid: Option\u003cDecimal\u003e,\n pub ask: Option\u003cDecimal\u003e,\n pub mid: Option\u003cDecimal\u003e,\n pub spread: Option\u003cDecimal\u003e,\n pub last_price: Option\u003cDecimal\u003e,\n pub volume_24h: Option\u003cDecimal\u003e,\n}\n\n/// Order book level (price/size pair) - EXTERNAL API VERSION\n/// \n/// This is what we expose to users and serialize to JSON.\n/// It uses Decimal for precision and human readability.\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct BookLevel {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub size: Decimal,\n}\n\n/// Order book level (price/size pair) - INTERNAL HOT PATH VERSION\n/// \n/// This is what we use internally for maximum performance.\n/// All order book operations use this to avoid Decimal overhead.\n/// \n/// The performance difference is huge:\n/// - BookLevel: ~50ns per operation (Decimal math + allocation)\n/// - FastBookLevel: ~2ns per operation (integer math, no allocation)\n/// \n/// That's a 25x speedup on the critical path\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct FastBookLevel {\n pub price: Price, // Price in ticks (u32)\n pub size: Qty, // Size in fixed-point units (i64)\n}\n\nimpl FastBookLevel {\n /// Create a new fast book level\n pub fn new(price: Price, size: Qty) -\u003e Self {\n Self { price, size }\n }\n \n /// Convert to external BookLevel for API responses\n /// This is only called at the edges when we need to return data to users\n pub fn to_book_level(self) -\u003e BookLevel {\n BookLevel {\n price: price_to_decimal(self.price),\n size: qty_to_decimal(self.size),\n }\n }\n \n /// Create from external BookLevel (with validation)\n /// This is called when we receive data from the API\n pub fn from_book_level(level: \u0026BookLevel) -\u003e std::result::Result\u003cSelf, \u0026'static str\u003e {\n let price = decimal_to_price(level.price)?;\n let size = decimal_to_qty(level.size)?;\n Ok(Self::new(price, size))\n }\n \n /// Calculate notional value (price * size) in fixed-point\n /// Returns the result scaled appropriately to avoid overflow\n /// \n /// This is much faster than the Decimal equivalent:\n /// - Decimal: price.mul(size) -\u003e ~20ns + allocation\n /// - Fixed-point: (price as i64 * size) / SCALE_FACTOR -\u003e ~1ns, no allocation\n pub fn notional(self) -\u003e i64 {\n // Convert price to i64 to avoid overflow in multiplication\n let price_i64 = self.price as i64;\n // Multiply and scale back down (we scaled both price and size up by SCALE_FACTOR)\n (price_i64 * self.size) / SCALE_FACTOR\n }\n}\n\n/// Full order book state\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct OrderBook {\n /// Token ID\n pub token_id: String,\n /// Timestamp\n pub timestamp: DateTime\u003cUtc\u003e,\n /// Bid orders\n pub bids: Vec\u003cBookLevel\u003e,\n /// Ask orders\n pub asks: Vec\u003cBookLevel\u003e,\n /// Sequence number\n pub sequence: u64,\n}\n\n/// Order book delta for streaming updates - EXTERNAL API VERSION\n/// \n/// This is what we receive from WebSocket streams and REST API calls.\n/// It uses Decimal for compatibility with external systems.\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct OrderDelta {\n pub token_id: String,\n pub timestamp: DateTime\u003cUtc\u003e,\n pub side: Side,\n pub price: Decimal,\n pub size: Decimal, // 0 means remove level\n pub sequence: u64,\n}\n\n/// Order book delta for streaming updates - INTERNAL HOT PATH VERSION\n/// \n/// This is what we use internally for processing order book updates.\n/// Converting to this format on ingress gives us massive performance gains.\n/// \n/// Why the performance matters:\n/// - We might process 10,000+ deltas per second in active markets\n/// - Each delta triggers multiple calculations (spread, impact, etc.)\n/// - Using integers instead of Decimal can make the difference between\n/// keeping up with the market feed vs falling behind\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct FastOrderDelta {\n pub token_id_hash: u64, // Hash of token_id for fast lookup (avoids string comparisons)\n pub timestamp: DateTime\u003cUtc\u003e,\n pub side: Side,\n pub price: Price, // Price in ticks\n pub size: Qty, // Size in fixed-point units (0 means remove level)\n pub sequence: u64,\n}\n\nimpl FastOrderDelta {\n /// Create from external OrderDelta with validation and tick alignment\n /// \n /// This is where we enforce tick alignment - if the incoming price\n /// doesn't align to valid ticks, we either reject it or round it.\n /// This prevents bad data from corrupting our order book.\n pub fn from_order_delta(delta: \u0026OrderDelta, tick_size: Option\u003cDecimal\u003e) -\u003e std::result::Result\u003cSelf, \u0026'static str\u003e {\n // Validate tick alignment if we have a tick size\n if let Some(tick_size) = tick_size {\n if !is_price_tick_aligned(delta.price, tick_size) {\n return Err(\"Price not aligned to tick size\");\n }\n }\n \n // Convert to fixed-point with validation\n let price = decimal_to_price(delta.price)?;\n let size = decimal_to_qty(delta.size)?;\n \n // Hash the token_id for fast lookups\n // This avoids string comparisons in the hot path\n let token_id_hash = {\n use std::collections::hash_map::DefaultHasher;\n use std::hash::{Hash, Hasher};\n let mut hasher = DefaultHasher::new();\n delta.token_id.hash(\u0026mut hasher);\n hasher.finish()\n };\n \n Ok(Self {\n token_id_hash,\n timestamp: delta.timestamp,\n side: delta.side,\n price,\n size,\n sequence: delta.sequence,\n })\n }\n \n /// Convert back to external OrderDelta (for API responses)\n /// We need the original token_id since we only store the hash\n pub fn to_order_delta(self, token_id: String) -\u003e OrderDelta {\n OrderDelta {\n token_id,\n timestamp: self.timestamp,\n side: self.side,\n price: price_to_decimal(self.price),\n size: qty_to_decimal(self.size),\n sequence: self.sequence,\n }\n }\n \n /// Check if this delta removes a level (size is zero)\n pub fn is_removal(self) -\u003e bool {\n self.size == 0\n }\n}\n\n/// Trade execution event\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct FillEvent {\n pub id: String,\n pub order_id: String,\n pub token_id: String,\n pub side: Side,\n pub price: Decimal,\n pub size: Decimal,\n pub timestamp: DateTime\u003cUtc\u003e,\n pub maker_address: Address,\n pub taker_address: Address,\n pub fee: Decimal,\n}\n\n/// Order creation parameters\n#[derive(Debug, Clone)]\npub struct OrderRequest {\n pub token_id: String,\n pub side: Side,\n pub price: Decimal,\n pub size: Decimal,\n pub order_type: OrderType,\n pub expiration: Option\u003cDateTime\u003cUtc\u003e\u003e,\n pub client_id: Option\u003cString\u003e,\n}\n\n/// Market order parameters\n#[derive(Debug, Clone)]\npub struct MarketOrderRequest {\n pub token_id: String,\n pub side: Side,\n pub amount: Decimal, // USD amount for buys, token amount for sells\n pub slippage_tolerance: Option\u003cDecimal\u003e,\n pub client_id: Option\u003cString\u003e,\n}\n\n/// Order state in the system\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Order {\n pub id: String,\n pub token_id: String,\n pub side: Side,\n pub price: Decimal,\n pub original_size: Decimal,\n pub filled_size: Decimal,\n pub remaining_size: Decimal,\n pub status: OrderStatus,\n pub order_type: OrderType,\n pub created_at: DateTime\u003cUtc\u003e,\n pub updated_at: DateTime\u003cUtc\u003e,\n pub expiration: Option\u003cDateTime\u003cUtc\u003e\u003e,\n pub client_id: Option\u003cString\u003e,\n}\n\n/// API credentials for authentication\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct ApiCredentials {\n #[serde(rename = \"apiKey\")]\n pub api_key: String,\n pub secret: String,\n pub passphrase: String,\n}\n\nimpl Default for ApiCredentials {\n fn default() -\u003e Self {\n Self {\n api_key: String::new(),\n secret: String::new(),\n passphrase: String::new(),\n }\n }\n}\n\n/// Configuration for order creation\n#[derive(Debug, Clone)]\npub struct OrderOptions {\n pub tick_size: Option\u003cDecimal\u003e,\n pub neg_risk: Option\u003cbool\u003e,\n pub fee_rate_bps: Option\u003cu32\u003e,\n}\n\n/// Extra arguments for order creation\n#[derive(Debug, Clone)]\npub struct ExtraOrderArgs {\n pub fee_rate_bps: u32,\n pub nonce: U256,\n pub taker: String,\n}\n\nimpl Default for ExtraOrderArgs {\n fn default() -\u003e Self {\n Self {\n fee_rate_bps: 0,\n nonce: U256::ZERO,\n taker: \"0x0000000000000000000000000000000000000000\".to_string(),\n }\n }\n}\n\n/// Market order arguments\n#[derive(Debug, Clone)]\npub struct MarketOrderArgs {\n pub token_id: String,\n pub amount: Decimal,\n}\n\n/// Signed order request ready for submission\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct SignedOrderRequest {\n pub salt: u64,\n pub maker: String,\n pub signer: String,\n pub taker: String,\n pub token_id: String,\n pub maker_amount: String,\n pub taker_amount: String,\n pub expiration: String,\n pub nonce: String,\n pub fee_rate_bps: String,\n pub side: String,\n pub signature_type: u8,\n pub signature: String,\n}\n\n/// Post order wrapper\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PostOrder {\n pub order: SignedOrderRequest,\n pub owner: String,\n pub order_type: OrderType,\n}\n\nimpl PostOrder {\n pub fn new(order: SignedOrderRequest, owner: String, order_type: OrderType) -\u003e Self {\n Self {\n order,\n owner,\n order_type,\n }\n }\n}\n\n/// Market information\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Market {\n pub condition_id: String,\n pub tokens: [Token; 2],\n pub rewards: Rewards,\n pub min_incentive_size: Option\u003cString\u003e,\n pub max_incentive_spread: Option\u003cString\u003e,\n pub active: bool,\n pub closed: bool,\n pub question_id: String,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub minimum_order_size: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub minimum_tick_size: Decimal,\n pub description: String,\n pub category: Option\u003cString\u003e,\n pub end_date_iso: Option\u003cString\u003e,\n pub game_start_time: Option\u003cString\u003e,\n pub question: String,\n pub market_slug: String,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub seconds_delay: Decimal,\n pub icon: String,\n pub fpmm: String,\n}\n\n/// Token information within a market\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Token {\n pub token_id: String,\n pub outcome: String,\n}\n\n/// Client configuration for PolyfillClient\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct ClientConfig {\n /// Base URL for the API\n pub base_url: String,\n /// Chain ID for the network\n pub chain_id: u64,\n /// Private key for signing (optional)\n pub private_key: Option\u003cString\u003e,\n /// API credentials (optional)\n pub api_credentials: Option\u003cApiCredentials\u003e,\n /// Maximum slippage tolerance\n pub max_slippage: Option\u003cDecimal\u003e,\n /// Fee rate in basis points\n pub fee_rate: Option\u003cDecimal\u003e,\n /// Request timeout\n pub timeout: Option\u003cstd::time::Duration\u003e,\n /// Maximum number of connections\n pub max_connections: Option\u003cusize\u003e,\n}\n\nimpl Default for ClientConfig {\n fn default() -\u003e Self {\n Self {\n base_url: \"https://clob.polymarket.com\".to_string(),\n chain_id: 137, // Polygon mainnet\n private_key: None,\n api_credentials: None,\n timeout: Some(std::time::Duration::from_secs(30)),\n max_connections: Some(100),\n max_slippage: None,\n fee_rate: None,\n }\n }\n}\n\n/// WebSocket authentication for Polymarket API\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct WssAuth {\n /// User's Ethereum address\n pub address: String,\n /// EIP-712 signature\n pub signature: String,\n /// Unix timestamp\n pub timestamp: u64,\n /// Nonce for replay protection\n pub nonce: String,\n}\n\n/// WebSocket subscription request\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct WssSubscription {\n /// Authentication information\n pub auth: WssAuth,\n /// Array of markets (condition IDs) for USER channel\n pub markets: Option\u003cVec\u003cString\u003e\u003e,\n /// Array of asset IDs (token IDs) for MARKET channel\n pub asset_ids: Option\u003cVec\u003cString\u003e\u003e,\n /// Channel type: \"USER\" or \"MARKET\"\n #[serde(rename = \"type\")]\n pub channel_type: String,\n}\n\n/// WebSocket message types for streaming\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(tag = \"type\")]\npub enum StreamMessage {\n #[serde(rename = \"book_update\")]\n BookUpdate {\n data: OrderDelta,\n },\n #[serde(rename = \"trade\")]\n Trade {\n data: FillEvent,\n },\n #[serde(rename = \"order_update\")]\n OrderUpdate {\n data: Order,\n },\n #[serde(rename = \"heartbeat\")]\n Heartbeat {\n timestamp: DateTime\u003cUtc\u003e,\n },\n /// User channel events\n #[serde(rename = \"user_order_update\")]\n UserOrderUpdate {\n data: Order,\n },\n #[serde(rename = \"user_trade\")]\n UserTrade {\n data: FillEvent,\n },\n /// Market channel events\n #[serde(rename = \"market_book_update\")]\n MarketBookUpdate {\n data: OrderDelta,\n },\n #[serde(rename = \"market_trade\")]\n MarketTrade {\n data: FillEvent,\n },\n}\n\n/// Subscription parameters for streaming\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Subscription {\n pub token_ids: Vec\u003cString\u003e,\n pub channels: Vec\u003cString\u003e,\n}\n\n/// WebSocket channel types\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum WssChannelType {\n #[serde(rename = \"USER\")]\n User,\n #[serde(rename = \"MARKET\")]\n Market,\n}\n\nimpl WssChannelType {\n pub fn as_str(\u0026self) -\u003e \u0026'static str {\n match self {\n WssChannelType::User =\u003e \"USER\",\n WssChannelType::Market =\u003e \"MARKET\",\n }\n }\n}\n\n/// Price quote response\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Quote {\n pub token_id: String,\n pub side: Side,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n pub timestamp: DateTime\u003cUtc\u003e,\n}\n\n/// Balance information\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Balance {\n pub token_id: String,\n pub available: Decimal,\n pub locked: Decimal,\n pub total: Decimal,\n}\n\n/// Performance metrics for monitoring\n#[derive(Debug, Clone)]\npub struct Metrics {\n pub orders_per_second: f64,\n pub avg_latency_ms: f64,\n pub error_rate: f64,\n pub uptime_pct: f64,\n}\n\n// Type aliases for common patterns\npub type TokenId = String;\npub type OrderId = String;\npub type MarketId = String;\npub type ClientId = String;\n\n\n/// Parameters for querying open orders\n#[derive(Debug, Clone)]\npub struct OpenOrderParams {\n pub id: Option\u003cString\u003e,\n pub asset_id: Option\u003cString\u003e,\n pub market: Option\u003cString\u003e,\n}\n\nimpl OpenOrderParams {\n pub fn to_query_params(\u0026self) -\u003e Vec\u003c(\u0026str, \u0026String)\u003e {\n let mut params = Vec::with_capacity(3);\n\n if let Some(x) = \u0026self.id {\n params.push((\"id\", x));\n }\n\n if let Some(x) = \u0026self.asset_id {\n params.push((\"asset_id\", x));\n }\n\n if let Some(x) = \u0026self.market {\n params.push((\"market\", x));\n }\n params\n }\n}\n\n/// Parameters for querying trades\n#[derive(Debug, Clone)]\npub struct TradeParams {\n pub id: Option\u003cString\u003e,\n pub maker_address: Option\u003cString\u003e,\n pub market: Option\u003cString\u003e,\n pub asset_id: Option\u003cString\u003e,\n pub before: Option\u003cu64\u003e,\n pub after: Option\u003cu64\u003e,\n}\n\nimpl TradeParams {\n pub fn to_query_params(\u0026self) -\u003e Vec\u003c(\u0026str, String)\u003e {\n let mut params = Vec::with_capacity(6);\n\n if let Some(x) = \u0026self.id {\n params.push((\"id\", x.clone()));\n }\n\n if let Some(x) = \u0026self.asset_id {\n params.push((\"asset_id\", x.clone()));\n }\n\n if let Some(x) = \u0026self.market {\n params.push((\"market\", x.clone()));\n }\n \n if let Some(x) = \u0026self.maker_address {\n params.push((\"maker_address\", x.clone()));\n }\n \n if let Some(x) = \u0026self.before {\n params.push((\"before\", x.to_string()));\n }\n \n if let Some(x) = \u0026self.after {\n params.push((\"after\", x.to_string()));\n }\n \n params\n }\n}\n\n/// Open order information\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct OpenOrder {\n pub associate_trades: Vec\u003cString\u003e,\n pub id: String,\n pub status: String,\n pub market: String,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub original_size: Decimal,\n pub outcome: String,\n pub maker_address: String,\n pub owner: String,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n pub side: Side,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub size_matched: Decimal,\n pub asset_id: String,\n #[serde(deserialize_with = \"crate::decode::deserializers::number_from_string\")]\n pub expiration: u64,\n #[serde(rename = \"type\")]\n pub order_type: OrderType,\n #[serde(deserialize_with = \"crate::decode::deserializers::number_from_string\")]\n pub created_at: u64,\n}\n\n\n/// Balance allowance information\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct BalanceAllowance {\n pub asset_id: String,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub balance: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub allowance: Decimal,\n}\n\n/// Parameters for balance allowance queries (from reference implementation)\n#[derive(Default)]\npub struct BalanceAllowanceParams {\n pub asset_type: Option\u003cAssetType\u003e,\n pub token_id: Option\u003cString\u003e,\n pub signature_type: Option\u003cu8\u003e,\n}\n\nimpl BalanceAllowanceParams {\n pub fn to_query_params(\u0026self) -\u003e Vec\u003c(\u0026str, String)\u003e {\n let mut params = Vec::with_capacity(3);\n\n if let Some(x) = \u0026self.asset_type {\n params.push((\"asset_type\", x.to_string()));\n }\n\n if let Some(x) = \u0026self.token_id {\n params.push((\"token_id\", x.to_string()));\n }\n\n if let Some(x) = \u0026self.signature_type {\n params.push((\"signature_type\", x.to_string()));\n }\n params\n }\n\n pub fn set_signature_type(\u0026mut self, s: u8) {\n self.signature_type = Some(s);\n }\n}\n\n/// Asset type enum for balance allowance queries\npub enum AssetType {\n COLLATERAL,\n CONDITIONAL,\n}\n\nimpl ToString for AssetType {\n fn to_string(\u0026self) -\u003e String {\n match self {\n AssetType::COLLATERAL =\u003e \"COLLATERAL\".to_string(),\n AssetType::CONDITIONAL =\u003e \"CONDITIONAL\".to_string(),\n }\n }\n}\n\n/// Notification preferences\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct NotificationParams {\n pub signature: String,\n pub timestamp: u64,\n}\n\n/// Batch midpoint request\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct BatchMidpointRequest {\n pub token_ids: Vec\u003cString\u003e,\n}\n\n/// Batch midpoint response\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct BatchMidpointResponse {\n pub midpoints: std::collections::HashMap\u003cString, Option\u003cDecimal\u003e\u003e,\n}\n\n/// Batch price request\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct BatchPriceRequest {\n pub token_ids: Vec\u003cString\u003e,\n}\n\n/// Price information for a token\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct TokenPrice {\n pub token_id: String,\n #[serde(skip_serializing_if = \"Option::is_none\")]\n pub bid: Option\u003cDecimal\u003e,\n #[serde(skip_serializing_if = \"Option::is_none\")]\n pub ask: Option\u003cDecimal\u003e,\n #[serde(skip_serializing_if = \"Option::is_none\")]\n pub mid: Option\u003cDecimal\u003e,\n}\n\n/// Batch price response\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct BatchPriceResponse {\n pub prices: Vec\u003cTokenPrice\u003e,\n}\n\n// Additional types for API compatibility with reference implementation\n#[derive(Debug, Deserialize)]\npub struct ApiKeysResponse {\n #[serde(rename = \"apiKeys\")]\n pub api_keys: Vec\u003cString\u003e,\n}\n\n#[derive(Debug, Deserialize)]\npub struct MidpointResponse {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub mid: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct PriceResponse {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct SpreadResponse {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub spread: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct TickSizeResponse {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub minimum_tick_size: Decimal,\n}\n\n#[derive(Debug, Deserialize)]\npub struct NegRiskResponse {\n pub neg_risk: bool,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct BookParams {\n pub token_id: String,\n pub side: Side,\n}\n\n#[derive(Debug, Deserialize)]\npub struct OrderBookSummary {\n pub market: String,\n pub asset_id: String,\n pub hash: String,\n #[serde(deserialize_with = \"crate::decode::deserializers::number_from_string\")]\n pub timestamp: u64,\n pub bids: Vec\u003cOrderSummary\u003e,\n pub asks: Vec\u003cOrderSummary\u003e,\n}\n\n#[derive(Debug, Deserialize)]\npub struct OrderSummary {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub price: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub size: Decimal,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct MarketsResponse {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub limit: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub count: Decimal,\n pub next_cursor: Option\u003cString\u003e,\n pub data: Vec\u003cMarket\u003e,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct SimplifiedMarketsResponse {\n #[serde(with = \"rust_decimal::serde::str\")]\n pub limit: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub count: Decimal,\n pub next_cursor: Option\u003cString\u003e,\n pub data: Vec\u003cSimplifiedMarket\u003e,\n}\n\n/// Simplified market structure for batch operations\n#[derive(Debug, Serialize, Deserialize)]\npub struct SimplifiedMarket {\n pub condition_id: String,\n pub tokens: [Token; 2],\n pub rewards: Rewards,\n pub min_incentive_size: Option\u003cString\u003e,\n pub max_incentive_spread: Option\u003cString\u003e,\n pub active: bool,\n pub closed: bool,\n}\n\n/// Rewards structure for markets\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Rewards {\n pub rates: Option\u003cserde_json::Value\u003e,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub min_size: Decimal,\n #[serde(with = \"rust_decimal::serde::str\")]\n pub max_spread: Decimal,\n pub event_start_date: Option\u003cString\u003e,\n pub event_end_date: Option\u003cString\u003e,\n #[serde(skip_serializing_if = \"Option::is_none\")]\n pub in_game_multiplier: Option\u003cDecimal\u003e,\n #[serde(skip_serializing_if = \"Option::is_none\")]\n pub reward_epoch: Option\u003cDecimal\u003e,\n}\n\n// For compatibility with reference implementation\npub type ClientResult\u003cT\u003e = anyhow::Result\u003cT\u003e;\n\n/// Result type used throughout the client\npub type Result\u003cT\u003e = std::result::Result\u003cT, crate::errors::PolyfillError\u003e; ","traces":[{"line":92,"address":[],"length":0,"stats":{"Line":6}},{"line":94,"address":[],"length":0,"stats":{"Line":12}},{"line":97,"address":[],"length":0,"stats":{"Line":18}},{"line":100,"address":[],"length":0,"stats":{"Line":24}},{"line":103,"address":[],"length":0,"stats":{"Line":6}},{"line":104,"address":[],"length":0,"stats":{"Line":0}},{"line":106,"address":[],"length":0,"stats":{"Line":6}},{"line":107,"address":[],"length":0,"stats":{"Line":0}},{"line":110,"address":[],"length":0,"stats":{"Line":6}},{"line":121,"address":[],"length":0,"stats":{"Line":7}},{"line":122,"address":[],"length":0,"stats":{"Line":14}},{"line":133,"address":[],"length":0,"stats":{"Line":6}},{"line":134,"address":[],"length":0,"stats":{"Line":12}},{"line":135,"address":[],"length":0,"stats":{"Line":18}},{"line":137,"address":[],"length":0,"stats":{"Line":24}},{"line":139,"address":[],"length":0,"stats":{"Line":6}},{"line":140,"address":[],"length":0,"stats":{"Line":0}},{"line":143,"address":[],"length":0,"stats":{"Line":6}},{"line":151,"address":[],"length":0,"stats":{"Line":6}},{"line":152,"address":[],"length":0,"stats":{"Line":12}},{"line":164,"address":[],"length":0,"stats":{"Line":0}},{"line":166,"address":[],"length":0,"stats":{"Line":0}},{"line":167,"address":[],"length":0,"stats":{"Line":0}},{"line":168,"address":[],"length":0,"stats":{"Line":0}},{"line":172,"address":[],"length":0,"stats":{"Line":0}},{"line":173,"address":[],"length":0,"stats":{"Line":0}},{"line":174,"address":[],"length":0,"stats":{"Line":0}},{"line":179,"address":[],"length":0,"stats":{"Line":0}},{"line":180,"address":[],"length":0,"stats":{"Line":0}},{"line":183,"address":[],"length":0,"stats":{"Line":0}},{"line":194,"address":[],"length":0,"stats":{"Line":0}},{"line":195,"address":[],"length":0,"stats":{"Line":0}},{"line":196,"address":[],"length":0,"stats":{"Line":0}},{"line":197,"address":[],"length":0,"stats":{"Line":0}},{"line":201,"address":[],"length":0,"stats":{"Line":0}},{"line":202,"address":[],"length":0,"stats":{"Line":0}},{"line":203,"address":[],"length":0,"stats":{"Line":0}},{"line":204,"address":[],"length":0,"stats":{"Line":0}},{"line":218,"address":[],"length":0,"stats":{"Line":0}},{"line":219,"address":[],"length":0,"stats":{"Line":0}},{"line":220,"address":[],"length":0,"stats":{"Line":0}},{"line":221,"address":[],"length":0,"stats":{"Line":0}},{"line":222,"address":[],"length":0,"stats":{"Line":0}},{"line":286,"address":[],"length":0,"stats":{"Line":0}},{"line":292,"address":[],"length":0,"stats":{"Line":0}},{"line":294,"address":[],"length":0,"stats":{"Line":0}},{"line":295,"address":[],"length":0,"stats":{"Line":0}},{"line":301,"address":[],"length":0,"stats":{"Line":0}},{"line":302,"address":[],"length":0,"stats":{"Line":0}},{"line":303,"address":[],"length":0,"stats":{"Line":0}},{"line":304,"address":[],"length":0,"stats":{"Line":0}},{"line":313,"address":[],"length":0,"stats":{"Line":0}},{"line":315,"address":[],"length":0,"stats":{"Line":0}},{"line":317,"address":[],"length":0,"stats":{"Line":0}},{"line":376,"address":[],"length":0,"stats":{"Line":6}},{"line":378,"address":[],"length":0,"stats":{"Line":6}},{"line":379,"address":[],"length":0,"stats":{"Line":0}},{"line":380,"address":[],"length":0,"stats":{"Line":0}},{"line":385,"address":[],"length":0,"stats":{"Line":18}},{"line":386,"address":[],"length":0,"stats":{"Line":18}},{"line":390,"address":[],"length":0,"stats":{"Line":6}},{"line":393,"address":[],"length":0,"stats":{"Line":12}},{"line":394,"address":[],"length":0,"stats":{"Line":18}},{"line":395,"address":[],"length":0,"stats":{"Line":12}},{"line":398,"address":[],"length":0,"stats":{"Line":6}},{"line":399,"address":[],"length":0,"stats":{"Line":12}},{"line":400,"address":[],"length":0,"stats":{"Line":12}},{"line":401,"address":[],"length":0,"stats":{"Line":12}},{"line":402,"address":[],"length":0,"stats":{"Line":12}},{"line":403,"address":[],"length":0,"stats":{"Line":6}},{"line":404,"address":[],"length":0,"stats":{"Line":6}},{"line":410,"address":[],"length":0,"stats":{"Line":0}},{"line":413,"address":[],"length":0,"stats":{"Line":0}},{"line":414,"address":[],"length":0,"stats":{"Line":0}},{"line":415,"address":[],"length":0,"stats":{"Line":0}},{"line":416,"address":[],"length":0,"stats":{"Line":0}},{"line":417,"address":[],"length":0,"stats":{"Line":0}},{"line":422,"address":[],"length":0,"stats":{"Line":0}},{"line":423,"address":[],"length":0,"stats":{"Line":0}},{"line":492,"address":[],"length":0,"stats":{"Line":0}},{"line":494,"address":[],"length":0,"stats":{"Line":0}},{"line":495,"address":[],"length":0,"stats":{"Line":0}},{"line":496,"address":[],"length":0,"stats":{"Line":0}},{"line":518,"address":[],"length":0,"stats":{"Line":0}},{"line":522,"address":[],"length":0,"stats":{"Line":0}},{"line":563,"address":[],"length":0,"stats":{"Line":0}},{"line":628,"address":[],"length":0,"stats":{"Line":0}},{"line":630,"address":[],"length":0,"stats":{"Line":0}},{"line":634,"address":[],"length":0,"stats":{"Line":0}},{"line":635,"address":[],"length":0,"stats":{"Line":0}},{"line":726,"address":[],"length":0,"stats":{"Line":0}},{"line":727,"address":[],"length":0,"stats":{"Line":0}},{"line":728,"address":[],"length":0,"stats":{"Line":0}},{"line":729,"address":[],"length":0,"stats":{"Line":0}},{"line":778,"address":[],"length":0,"stats":{"Line":0}},{"line":779,"address":[],"length":0,"stats":{"Line":0}},{"line":781,"address":[],"length":0,"stats":{"Line":0}},{"line":782,"address":[],"length":0,"stats":{"Line":0}},{"line":785,"address":[],"length":0,"stats":{"Line":0}},{"line":786,"address":[],"length":0,"stats":{"Line":0}},{"line":789,"address":[],"length":0,"stats":{"Line":0}},{"line":790,"address":[],"length":0,"stats":{"Line":0}},{"line":792,"address":[],"length":0,"stats":{"Line":0}},{"line":808,"address":[],"length":0,"stats":{"Line":0}},{"line":809,"address":[],"length":0,"stats":{"Line":0}},{"line":811,"address":[],"length":0,"stats":{"Line":0}},{"line":812,"address":[],"length":0,"stats":{"Line":0}},{"line":815,"address":[],"length":0,"stats":{"Line":0}},{"line":816,"address":[],"length":0,"stats":{"Line":0}},{"line":819,"address":[],"length":0,"stats":{"Line":0}},{"line":820,"address":[],"length":0,"stats":{"Line":0}},{"line":823,"address":[],"length":0,"stats":{"Line":0}},{"line":824,"address":[],"length":0,"stats":{"Line":0}},{"line":827,"address":[],"length":0,"stats":{"Line":0}},{"line":828,"address":[],"length":0,"stats":{"Line":0}},{"line":831,"address":[],"length":0,"stats":{"Line":0}},{"line":832,"address":[],"length":0,"stats":{"Line":0}},{"line":835,"address":[],"length":0,"stats":{"Line":0}},{"line":885,"address":[],"length":0,"stats":{"Line":0}},{"line":886,"address":[],"length":0,"stats":{"Line":0}},{"line":888,"address":[],"length":0,"stats":{"Line":0}},{"line":889,"address":[],"length":0,"stats":{"Line":0}},{"line":892,"address":[],"length":0,"stats":{"Line":0}},{"line":893,"address":[],"length":0,"stats":{"Line":0}},{"line":896,"address":[],"length":0,"stats":{"Line":0}},{"line":897,"address":[],"length":0,"stats":{"Line":0}},{"line":899,"address":[],"length":0,"stats":{"Line":0}},{"line":902,"address":[],"length":0,"stats":{"Line":0}},{"line":903,"address":[],"length":0,"stats":{"Line":0}},{"line":914,"address":[],"length":0,"stats":{"Line":0}},{"line":915,"address":[],"length":0,"stats":{"Line":0}},{"line":916,"address":[],"length":0,"stats":{"Line":0}},{"line":917,"address":[],"length":0,"stats":{"Line":0}}],"covered":32,"coverable":133},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","src","utils.rs"],"content":"//! Utility functions for the Polymarket client\n//!\n//! This module contains optimized utility functions for performance-critical\n//! operations in trading environments.\n\nuse crate::errors::{PolyfillError, Result};\nuse alloy_primitives::{Address, U256};\nuse base64::{engine::general_purpose::URL_SAFE, Engine};\nuse chrono::{DateTime, Utc};\nuse hmac::{Hmac, Mac};\nuse rust_decimal::Decimal;\nuse serde::Serialize;\nuse sha2::Sha256;\nuse std::str::FromStr;\nuse std::time::{Duration, SystemTime, UNIX_EPOCH};\nuse ::url::Url;\n\ntype HmacSha256 = Hmac\u003cSha256\u003e;\n\n/// High-precision timestamp utilities\npub mod time {\n use super::*;\n\n /// Get current Unix timestamp in seconds\n #[inline]\n pub fn now_secs() -\u003e u64 {\n SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .expect(\"Time went backwards\")\n .as_secs()\n }\n\n /// Get current Unix timestamp in milliseconds\n #[inline]\n pub fn now_millis() -\u003e u64 {\n SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .expect(\"Time went backwards\")\n .as_millis() as u64\n }\n\n /// Get current Unix timestamp in microseconds\n #[inline]\n pub fn now_micros() -\u003e u64 {\n SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .expect(\"Time went backwards\")\n .as_micros() as u64\n }\n\n /// Get current Unix timestamp in nanoseconds\n #[inline]\n pub fn now_nanos() -\u003e u128 {\n SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .expect(\"Time went backwards\")\n .as_nanos()\n }\n\n /// Convert DateTime to Unix timestamp in seconds\n #[inline]\n pub fn datetime_to_secs(dt: DateTime\u003cUtc\u003e) -\u003e u64 {\n dt.timestamp() as u64\n }\n\n /// Convert Unix timestamp to DateTime\n #[inline]\n pub fn secs_to_datetime(timestamp: u64) -\u003e DateTime\u003cUtc\u003e {\n DateTime::from_timestamp(timestamp as i64, 0)\n .unwrap_or_else(|| Utc::now())\n }\n}\n\n/// Cryptographic utilities for signing and authentication\npub mod crypto {\n use super::*;\n\n /// Build HMAC-SHA256 signature for API authentication\n pub fn build_hmac_signature\u003cT\u003e(\n secret: \u0026str,\n timestamp: u64,\n method: \u0026str,\n path: \u0026str,\n body: Option\u003c\u0026T\u003e,\n ) -\u003e Result\u003cString\u003e\n where\n T: ?Sized + Serialize,\n {\n let decoded = URL_SAFE\n .decode(secret)\n .map_err(|e| PolyfillError::config(format!(\"Invalid secret format: {}\", e)))?;\n\n let message = match body {\n None =\u003e format!(\"{timestamp}{method}{path}\"),\n Some(data) =\u003e {\n let json = serde_json::to_string(data)?;\n format!(\"{timestamp}{method}{path}{json}\")\n }\n };\n\n let mut mac = HmacSha256::new_from_slice(\u0026decoded)\n .map_err(|e| PolyfillError::internal(\"HMAC initialization failed\", e))?;\n \n mac.update(message.as_bytes());\n let result = mac.finalize();\n\n Ok(URL_SAFE.encode(result.into_bytes()))\n }\n\n /// Generate a secure random nonce\n pub fn generate_nonce() -\u003e U256 {\n use rand::RngCore;\n let mut rng = rand::thread_rng();\n let mut bytes = [0u8; 32];\n rng.fill_bytes(\u0026mut bytes);\n U256::from_be_bytes(bytes)\n }\n\n /// Generate a secure random salt\n pub fn generate_salt() -\u003e u64 {\n use rand::RngCore;\n let mut rng = rand::thread_rng();\n rng.next_u64()\n }\n}\n\n/// Price and size calculation utilities\npub mod math {\n use super::*;\n use rust_decimal::prelude::*;\n use crate::types::{Price, Qty, SCALE_FACTOR};\n\n // ========================================================================\n // LEGACY DECIMAL FUNCTIONS (for backward compatibility)\n // ========================================================================\n // \n // These are kept for API compatibility, but internally we should use\n // the fixed-point versions below for better performance.\n\n /// Round price to tick size (LEGACY - use fixed-point version when possible)\n #[inline]\n pub fn round_to_tick(price: Decimal, tick_size: Decimal) -\u003e Decimal {\n if tick_size.is_zero() {\n return price;\n }\n (price / tick_size).round() * tick_size\n }\n\n /// Calculate notional value (price * size) (LEGACY - use fixed-point version when possible)\n #[inline]\n pub fn notional(price: Decimal, size: Decimal) -\u003e Decimal {\n price * size\n }\n\n /// Calculate spread as percentage (LEGACY - use fixed-point version when possible)\n #[inline]\n pub fn spread_pct(bid: Decimal, ask: Decimal) -\u003e Option\u003cDecimal\u003e {\n if bid.is_zero() || ask \u003c= bid {\n return None;\n }\n Some((ask - bid) / bid * Decimal::from(100))\n }\n\n /// Calculate mid price (LEGACY - use fixed-point version when possible)\n #[inline]\n pub fn mid_price(bid: Decimal, ask: Decimal) -\u003e Option\u003cDecimal\u003e {\n if bid.is_zero() || ask.is_zero() || ask \u003c= bid {\n return None;\n }\n Some((bid + ask) / Decimal::from(2))\n }\n\n // ========================================================================\n // HIGH-PERFORMANCE FIXED-POINT FUNCTIONS\n // ========================================================================\n //\n // These functions operate on our internal Price/Qty types and are\n // optimized for maximum performance. They avoid all Decimal operations\n // and memory allocations.\n //\n // Performance comparison (approximate):\n // - Decimal operations: 20-100ns + allocation overhead\n // - Fixed-point operations: 1-5ns, no allocations\n //\n // That's a 10-50x speedup on the critical path!\n\n /// Round price to tick size (FAST VERSION)\n /// \n /// This is much faster than the Decimal version because it's just\n /// integer division and multiplication.\n /// \n /// Example: round_to_tick_fast(6543, 10) = 6540 (rounds to nearest 10 ticks)\n #[inline]\n pub fn round_to_tick_fast(price_ticks: Price, tick_size_ticks: Price) -\u003e Price {\n if tick_size_ticks == 0 {\n return price_ticks;\n }\n // Integer division automatically truncates, then multiply back\n // For proper rounding, we add half the tick size before dividing\n let half_tick = tick_size_ticks / 2;\n ((price_ticks + half_tick) / tick_size_ticks) * tick_size_ticks\n }\n\n /// Calculate notional value (price * size) (FAST VERSION)\n /// \n /// Returns the result in the same scale as our quantities.\n /// This avoids the expensive Decimal multiplication.\n /// \n /// Example: notional_fast(6543, 1000000) = 6543000000 (representing $654.30)\n #[inline]\n pub fn notional_fast(price_ticks: Price, size_units: Qty) -\u003e i64 {\n // Convert price to i64 to avoid overflow\n let price_i64 = price_ticks as i64;\n // Multiply and scale appropriately\n // Both price and size are scaled by SCALE_FACTOR, so result is scaled by SCALE_FACTOR^2\n // We divide by SCALE_FACTOR to get back to normal scale\n (price_i64 * size_units) / SCALE_FACTOR\n }\n\n /// Calculate spread as percentage (FAST VERSION)\n /// \n /// Returns the spread as a percentage in basis points (1/100th of a percent).\n /// This avoids floating-point arithmetic entirely.\n /// \n /// Example: spread_pct_fast(6500, 6700) = Some(307) (representing 3.07%)\n #[inline]\n pub fn spread_pct_fast(bid_ticks: Price, ask_ticks: Price) -\u003e Option\u003cu32\u003e {\n if bid_ticks == 0 || ask_ticks \u003c= bid_ticks {\n return None;\n }\n \n let spread = ask_ticks - bid_ticks;\n // Calculate percentage in basis points (multiply by 10000 for 4 decimal places)\n // We use u64 for intermediate calculation to avoid overflow\n let spread_bps = ((spread as u64) * 10000) / (bid_ticks as u64);\n \n // Convert back to u32 (should always fit since spreads are typically small)\n Some(spread_bps as u32)\n }\n\n /// Calculate mid price (FAST VERSION)\n /// \n /// Returns the midpoint between bid and ask in ticks.\n /// Much faster than the Decimal version.\n /// \n /// Example: mid_price_fast(6500, 6700) = Some(6600)\n #[inline]\n pub fn mid_price_fast(bid_ticks: Price, ask_ticks: Price) -\u003e Option\u003cPrice\u003e {\n if bid_ticks == 0 || ask_ticks == 0 || ask_ticks \u003c= bid_ticks {\n return None;\n }\n \n // Use u64 to avoid overflow in addition\n let sum = (bid_ticks as u64) + (ask_ticks as u64);\n Some((sum / 2) as Price)\n }\n\n /// Calculate spread in ticks (FAST VERSION)\n /// \n /// Simple subtraction - much faster than Decimal operations.\n /// \n /// Example: spread_fast(6500, 6700) = Some(200) (representing $0.02 spread)\n #[inline]\n pub fn spread_fast(bid_ticks: Price, ask_ticks: Price) -\u003e Option\u003cPrice\u003e {\n if ask_ticks \u003c= bid_ticks {\n return None;\n }\n Some(ask_ticks - bid_ticks)\n }\n\n /// Check if price is within valid range (FAST VERSION)\n /// \n /// Much faster than converting to Decimal and back.\n /// \n /// Example: is_valid_price_fast(6543, 1, 10000) = true\n #[inline]\n pub fn is_valid_price_fast(price_ticks: Price, min_tick: Price, max_tick: Price) -\u003e bool {\n price_ticks \u003e= min_tick \u0026\u0026 price_ticks \u003c= max_tick\n }\n\n /// Convert decimal to token units (6 decimal places)\n #[inline]\n pub fn decimal_to_token_units(amount: Decimal) -\u003e u64 {\n let scaled = amount * Decimal::from(1_000_000);\n scaled.to_u64().unwrap_or(0)\n }\n\n /// Convert token units back to decimal\n #[inline]\n pub fn token_units_to_decimal(units: u64) -\u003e Decimal {\n Decimal::from(units) / Decimal::from(1_000_000)\n }\n\n /// Check if price is within valid range [tick_size, 1-tick_size]\n #[inline]\n pub fn is_valid_price(price: Decimal, tick_size: Decimal) -\u003e bool {\n price \u003e= tick_size \u0026\u0026 price \u003c= (Decimal::ONE - tick_size)\n }\n\n /// Calculate maximum slippage for market order\n pub fn calculate_slippage(\n target_price: Decimal,\n executed_price: Decimal,\n side: crate::types::Side,\n ) -\u003e Decimal {\n match side {\n crate::types::Side::BUY =\u003e {\n if executed_price \u003e target_price {\n (executed_price - target_price) / target_price\n } else {\n Decimal::ZERO\n }\n }\n crate::types::Side::SELL =\u003e {\n if executed_price \u003c target_price {\n (target_price - executed_price) / target_price\n } else {\n Decimal::ZERO\n }\n }\n }\n }\n}\n\n/// Network and retry utilities\npub mod retry {\n use super::*;\n use std::future::Future;\n use tokio::time::{sleep, Duration};\n\n /// Exponential backoff configuration\n #[derive(Debug, Clone)]\n pub struct RetryConfig {\n pub max_attempts: usize,\n pub initial_delay: Duration,\n pub max_delay: Duration,\n pub backoff_factor: f64,\n pub jitter: bool,\n }\n\n impl Default for RetryConfig {\n fn default() -\u003e Self {\n Self {\n max_attempts: 3,\n initial_delay: Duration::from_millis(100),\n max_delay: Duration::from_secs(10),\n backoff_factor: 2.0,\n jitter: true,\n }\n }\n }\n\n /// Retry a future with exponential backoff\n pub async fn with_retry\u003cF, Fut, T\u003e(\n config: \u0026RetryConfig,\n mut operation: F,\n ) -\u003e Result\u003cT\u003e\n where\n F: FnMut() -\u003e Fut,\n Fut: Future\u003cOutput = Result\u003cT\u003e\u003e,\n {\n let mut delay = config.initial_delay;\n let mut last_error = None;\n\n for attempt in 0..config.max_attempts {\n match operation().await {\n Ok(result) =\u003e return Ok(result),\n Err(err) =\u003e {\n last_error = Some(err.clone());\n \n if !err.is_retryable() || attempt == config.max_attempts - 1 {\n return Err(err);\n }\n\n // Add jitter if enabled\n let actual_delay = if config.jitter {\n let jitter_factor = rand::random::\u003cf64\u003e() * 0.1; // ±10%\n let jitter = 1.0 + (jitter_factor - 0.05);\n Duration::from_nanos((delay.as_nanos() as f64 * jitter) as u64)\n } else {\n delay\n };\n\n sleep(actual_delay).await;\n\n // Exponential backoff\n delay = std::cmp::min(\n Duration::from_nanos((delay.as_nanos() as f64 * config.backoff_factor) as u64),\n config.max_delay,\n );\n }\n }\n }\n\n Err(last_error.unwrap_or_else(|| PolyfillError::internal(\"Retry loop failed\", std::io::Error::new(std::io::ErrorKind::Other, \"No error captured\"))))\n }\n}\n\n/// Address and token ID utilities\npub mod address {\n use super::*;\n\n /// Validate and parse Ethereum address\n pub fn parse_address(addr: \u0026str) -\u003e Result\u003cAddress\u003e {\n Address::from_str(addr)\n .map_err(|e| PolyfillError::validation(format!(\"Invalid address format: {}\", e)))\n }\n\n /// Validate token ID format\n pub fn validate_token_id(token_id: \u0026str) -\u003e Result\u003c()\u003e {\n if token_id.is_empty() {\n return Err(PolyfillError::validation(\"Token ID cannot be empty\"));\n }\n\n // Token IDs should be numeric strings\n if !token_id.chars().all(|c| c.is_ascii_digit()) {\n return Err(PolyfillError::validation(\"Token ID must be numeric\"));\n }\n\n Ok(())\n }\n\n /// Convert token ID to U256\n pub fn token_id_to_u256(token_id: \u0026str) -\u003e Result\u003cU256\u003e {\n validate_token_id(token_id)?;\n U256::from_str_radix(token_id, 10)\n .map_err(|e| PolyfillError::validation(format!(\"Invalid token ID: {}\", e)))\n }\n}\n\n/// URL building utilities\npub mod url {\n use super::*;\n\n /// Build API endpoint URL\n pub fn build_endpoint(base_url: \u0026str, path: \u0026str) -\u003e Result\u003cString\u003e {\n let base = base_url.trim_end_matches('/');\n let path = path.trim_start_matches('/');\n Ok(format!(\"{}/{}\", base, path))\n }\n\n /// Add query parameters to URL\n pub fn add_query_params(\n mut url: url::Url,\n params: \u0026[(\u0026str, \u0026str)],\n ) -\u003e url::Url {\n {\n let mut query_pairs = url.query_pairs_mut();\n for (key, value) in params {\n query_pairs.append_pair(key, value);\n }\n }\n url\n }\n}\n\n/// Rate limiting utilities\npub mod rate_limit {\n use super::*;\n use std::sync::{Arc, Mutex};\n\n /// Simple token bucket rate limiter\n #[derive(Debug)]\n pub struct TokenBucket {\n capacity: usize,\n tokens: Arc\u003cMutex\u003cusize\u003e\u003e,\n refill_rate: Duration,\n last_refill: Arc\u003cMutex\u003cSystemTime\u003e\u003e,\n }\n\n impl TokenBucket {\n pub fn new(capacity: usize, refill_per_second: usize) -\u003e Self {\n Self {\n capacity,\n tokens: Arc::new(Mutex::new(capacity)),\n refill_rate: Duration::from_secs(1) / refill_per_second as u32,\n last_refill: Arc::new(Mutex::new(SystemTime::now())),\n }\n }\n\n /// Try to consume a token, return true if successful\n pub fn try_consume(\u0026self) -\u003e bool {\n self.refill();\n \n let mut tokens = self.tokens.lock().unwrap();\n if *tokens \u003e 0 {\n *tokens -= 1;\n true\n } else {\n false\n }\n }\n\n fn refill(\u0026self) {\n let now = SystemTime::now();\n let mut last_refill = self.last_refill.lock().unwrap();\n let elapsed = now.duration_since(*last_refill).unwrap_or_default();\n \n if elapsed \u003e= self.refill_rate {\n let tokens_to_add = elapsed.as_nanos() / self.refill_rate.as_nanos();\n let mut tokens = self.tokens.lock().unwrap();\n *tokens = std::cmp::min(self.capacity, *tokens + tokens_to_add as usize);\n *last_refill = now;\n }\n }\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_round_to_tick() {\n use math::round_to_tick;\n \n let price = Decimal::from_str(\"0.567\").unwrap();\n let tick = Decimal::from_str(\"0.01\").unwrap();\n let rounded = round_to_tick(price, tick);\n assert_eq!(rounded, Decimal::from_str(\"0.57\").unwrap());\n }\n\n #[test]\n fn test_mid_price() {\n use math::mid_price;\n \n let bid = Decimal::from_str(\"0.50\").unwrap();\n let ask = Decimal::from_str(\"0.52\").unwrap();\n let mid = mid_price(bid, ask).unwrap();\n assert_eq!(mid, Decimal::from_str(\"0.51\").unwrap());\n }\n\n #[test]\n fn test_token_units_conversion() {\n use math::{decimal_to_token_units, token_units_to_decimal};\n \n let amount = Decimal::from_str(\"1.234567\").unwrap();\n let units = decimal_to_token_units(amount);\n assert_eq!(units, 1_234_567);\n \n let back = token_units_to_decimal(units);\n assert_eq!(back, amount);\n }\n\n #[test]\n fn test_address_validation() {\n use address::parse_address;\n \n let valid = \"0x1234567890123456789012345678901234567890\";\n assert!(parse_address(valid).is_ok());\n \n let invalid = \"invalid_address\";\n assert!(parse_address(invalid).is_err());\n }\n} ","traces":[{"line":26,"address":[],"length":0,"stats":{"Line":0}},{"line":27,"address":[],"length":0,"stats":{"Line":0}},{"line":28,"address":[],"length":0,"stats":{"Line":0}},{"line":35,"address":[],"length":0,"stats":{"Line":0}},{"line":36,"address":[],"length":0,"stats":{"Line":0}},{"line":37,"address":[],"length":0,"stats":{"Line":0}},{"line":38,"address":[],"length":0,"stats":{"Line":0}},{"line":39,"address":[],"length":0,"stats":{"Line":0}},{"line":44,"address":[],"length":0,"stats":{"Line":0}},{"line":45,"address":[],"length":0,"stats":{"Line":0}},{"line":46,"address":[],"length":0,"stats":{"Line":0}},{"line":47,"address":[],"length":0,"stats":{"Line":0}},{"line":48,"address":[],"length":0,"stats":{"Line":0}},{"line":53,"address":[],"length":0,"stats":{"Line":0}},{"line":54,"address":[],"length":0,"stats":{"Line":0}},{"line":55,"address":[],"length":0,"stats":{"Line":0}},{"line":62,"address":[],"length":0,"stats":{"Line":0}},{"line":63,"address":[],"length":0,"stats":{"Line":0}},{"line":68,"address":[],"length":0,"stats":{"Line":0}},{"line":69,"address":[],"length":0,"stats":{"Line":0}},{"line":70,"address":[],"length":0,"stats":{"Line":0}},{"line":79,"address":[],"length":0,"stats":{"Line":0}},{"line":89,"address":[],"length":0,"stats":{"Line":0}},{"line":90,"address":[],"length":0,"stats":{"Line":0}},{"line":91,"address":[],"length":0,"stats":{"Line":0}},{"line":93,"address":[],"length":0,"stats":{"Line":0}},{"line":94,"address":[],"length":0,"stats":{"Line":0}},{"line":95,"address":[],"length":0,"stats":{"Line":0}},{"line":96,"address":[],"length":0,"stats":{"Line":0}},{"line":97,"address":[],"length":0,"stats":{"Line":0}},{"line":101,"address":[],"length":0,"stats":{"Line":0}},{"line":102,"address":[],"length":0,"stats":{"Line":0}},{"line":104,"address":[],"length":0,"stats":{"Line":0}},{"line":105,"address":[],"length":0,"stats":{"Line":0}},{"line":107,"address":[],"length":0,"stats":{"Line":0}},{"line":111,"address":[],"length":0,"stats":{"Line":0}},{"line":113,"address":[],"length":0,"stats":{"Line":0}},{"line":114,"address":[],"length":0,"stats":{"Line":0}},{"line":115,"address":[],"length":0,"stats":{"Line":0}},{"line":116,"address":[],"length":0,"stats":{"Line":0}},{"line":120,"address":[],"length":0,"stats":{"Line":0}},{"line":122,"address":[],"length":0,"stats":{"Line":0}},{"line":123,"address":[],"length":0,"stats":{"Line":0}},{"line":142,"address":[],"length":0,"stats":{"Line":1}},{"line":143,"address":[],"length":0,"stats":{"Line":2}},{"line":144,"address":[],"length":0,"stats":{"Line":0}},{"line":146,"address":[],"length":0,"stats":{"Line":2}},{"line":151,"address":[],"length":0,"stats":{"Line":0}},{"line":152,"address":[],"length":0,"stats":{"Line":0}},{"line":157,"address":[],"length":0,"stats":{"Line":0}},{"line":158,"address":[],"length":0,"stats":{"Line":0}},{"line":159,"address":[],"length":0,"stats":{"Line":0}},{"line":161,"address":[],"length":0,"stats":{"Line":0}},{"line":166,"address":[],"length":0,"stats":{"Line":1}},{"line":167,"address":[],"length":0,"stats":{"Line":5}},{"line":168,"address":[],"length":0,"stats":{"Line":0}},{"line":170,"address":[],"length":0,"stats":{"Line":1}},{"line":194,"address":[],"length":0,"stats":{"Line":0}},{"line":195,"address":[],"length":0,"stats":{"Line":0}},{"line":196,"address":[],"length":0,"stats":{"Line":0}},{"line":200,"address":[],"length":0,"stats":{"Line":0}},{"line":201,"address":[],"length":0,"stats":{"Line":0}},{"line":211,"address":[],"length":0,"stats":{"Line":0}},{"line":213,"address":[],"length":0,"stats":{"Line":0}},{"line":217,"address":[],"length":0,"stats":{"Line":0}},{"line":227,"address":[],"length":0,"stats":{"Line":0}},{"line":228,"address":[],"length":0,"stats":{"Line":0}},{"line":229,"address":[],"length":0,"stats":{"Line":0}},{"line":232,"address":[],"length":0,"stats":{"Line":0}},{"line":235,"address":[],"length":0,"stats":{"Line":0}},{"line":238,"address":[],"length":0,"stats":{"Line":0}},{"line":248,"address":[],"length":0,"stats":{"Line":0}},{"line":249,"address":[],"length":0,"stats":{"Line":0}},{"line":250,"address":[],"length":0,"stats":{"Line":0}},{"line":254,"address":[],"length":0,"stats":{"Line":0}},{"line":255,"address":[],"length":0,"stats":{"Line":0}},{"line":264,"address":[],"length":0,"stats":{"Line":1}},{"line":265,"address":[],"length":0,"stats":{"Line":1}},{"line":266,"address":[],"length":0,"stats":{"Line":0}},{"line":268,"address":[],"length":0,"stats":{"Line":1}},{"line":277,"address":[],"length":0,"stats":{"Line":0}},{"line":278,"address":[],"length":0,"stats":{"Line":0}},{"line":283,"address":[],"length":0,"stats":{"Line":1}},{"line":284,"address":[],"length":0,"stats":{"Line":2}},{"line":285,"address":[],"length":0,"stats":{"Line":3}},{"line":290,"address":[],"length":0,"stats":{"Line":1}},{"line":291,"address":[],"length":0,"stats":{"Line":2}},{"line":296,"address":[],"length":0,"stats":{"Line":0}},{"line":297,"address":[],"length":0,"stats":{"Line":0}},{"line":301,"address":[],"length":0,"stats":{"Line":0}},{"line":306,"address":[],"length":0,"stats":{"Line":0}},{"line":308,"address":[],"length":0,"stats":{"Line":0}},{"line":309,"address":[],"length":0,"stats":{"Line":0}},{"line":311,"address":[],"length":0,"stats":{"Line":0}},{"line":315,"address":[],"length":0,"stats":{"Line":0}},{"line":316,"address":[],"length":0,"stats":{"Line":0}},{"line":318,"address":[],"length":0,"stats":{"Line":0}},{"line":342,"address":[],"length":0,"stats":{"Line":0}},{"line":345,"address":[],"length":0,"stats":{"Line":0}},{"line":346,"address":[],"length":0,"stats":{"Line":0}},{"line":354,"address":[],"length":0,"stats":{"Line":0}},{"line":362,"address":[],"length":0,"stats":{"Line":0}},{"line":363,"address":[],"length":0,"stats":{"Line":0}},{"line":365,"address":[],"length":0,"stats":{"Line":0}},{"line":366,"address":[],"length":0,"stats":{"Line":0}},{"line":367,"address":[],"length":0,"stats":{"Line":0}},{"line":368,"address":[],"length":0,"stats":{"Line":0}},{"line":369,"address":[],"length":0,"stats":{"Line":0}},{"line":371,"address":[],"length":0,"stats":{"Line":0}},{"line":372,"address":[],"length":0,"stats":{"Line":0}},{"line":376,"address":[],"length":0,"stats":{"Line":0}},{"line":377,"address":[],"length":0,"stats":{"Line":0}},{"line":378,"address":[],"length":0,"stats":{"Line":0}},{"line":379,"address":[],"length":0,"stats":{"Line":0}},{"line":381,"address":[],"length":0,"stats":{"Line":0}},{"line":384,"address":[],"length":0,"stats":{"Line":0}},{"line":387,"address":[],"length":0,"stats":{"Line":0}},{"line":388,"address":[],"length":0,"stats":{"Line":0}},{"line":389,"address":[],"length":0,"stats":{"Line":0}},{"line":395,"address":[],"length":0,"stats":{"Line":0}},{"line":404,"address":[],"length":0,"stats":{"Line":2}},{"line":405,"address":[],"length":0,"stats":{"Line":4}},{"line":406,"address":[],"length":0,"stats":{"Line":5}},{"line":410,"address":[],"length":0,"stats":{"Line":0}},{"line":411,"address":[],"length":0,"stats":{"Line":0}},{"line":412,"address":[],"length":0,"stats":{"Line":0}},{"line":416,"address":[],"length":0,"stats":{"Line":0}},{"line":417,"address":[],"length":0,"stats":{"Line":0}},{"line":420,"address":[],"length":0,"stats":{"Line":0}},{"line":424,"address":[],"length":0,"stats":{"Line":0}},{"line":425,"address":[],"length":0,"stats":{"Line":0}},{"line":426,"address":[],"length":0,"stats":{"Line":0}},{"line":427,"address":[],"length":0,"stats":{"Line":0}},{"line":436,"address":[],"length":0,"stats":{"Line":0}},{"line":437,"address":[],"length":0,"stats":{"Line":0}},{"line":438,"address":[],"length":0,"stats":{"Line":0}},{"line":439,"address":[],"length":0,"stats":{"Line":0}},{"line":443,"address":[],"length":0,"stats":{"Line":0}},{"line":448,"address":[],"length":0,"stats":{"Line":0}},{"line":449,"address":[],"length":0,"stats":{"Line":0}},{"line":450,"address":[],"length":0,"stats":{"Line":0}},{"line":453,"address":[],"length":0,"stats":{"Line":0}},{"line":472,"address":[],"length":0,"stats":{"Line":0}},{"line":475,"address":[],"length":0,"stats":{"Line":0}},{"line":476,"address":[],"length":0,"stats":{"Line":0}},{"line":477,"address":[],"length":0,"stats":{"Line":0}},{"line":482,"address":[],"length":0,"stats":{"Line":0}},{"line":483,"address":[],"length":0,"stats":{"Line":0}},{"line":485,"address":[],"length":0,"stats":{"Line":0}},{"line":486,"address":[],"length":0,"stats":{"Line":0}},{"line":487,"address":[],"length":0,"stats":{"Line":0}},{"line":488,"address":[],"length":0,"stats":{"Line":0}},{"line":490,"address":[],"length":0,"stats":{"Line":0}},{"line":494,"address":[],"length":0,"stats":{"Line":0}},{"line":495,"address":[],"length":0,"stats":{"Line":0}},{"line":496,"address":[],"length":0,"stats":{"Line":0}},{"line":497,"address":[],"length":0,"stats":{"Line":0}},{"line":499,"address":[],"length":0,"stats":{"Line":0}},{"line":500,"address":[],"length":0,"stats":{"Line":0}},{"line":501,"address":[],"length":0,"stats":{"Line":0}},{"line":502,"address":[],"length":0,"stats":{"Line":0}},{"line":503,"address":[],"length":0,"stats":{"Line":0}}],"covered":17,"coverable":162},{"path":["/","Users","juliustranquilli","personal","polyfill-rs","tests","common","mod.rs"],"content":"//! Common utilities for integration tests\n\nuse polyfill_rs::{ClobClient, Result};\nuse std::env;\nuse std::time::Duration;\n\n/// Test configuration loaded from environment variables\n#[derive(Debug, Clone)]\npub struct TestConfig {\n pub host: String,\n pub chain_id: u64,\n pub private_key: Option\u003cString\u003e,\n pub api_key: Option\u003cString\u003e,\n pub api_secret: Option\u003cString\u003e,\n pub api_passphrase: Option\u003cString\u003e,\n pub test_timeout: Duration,\n}\n\nimpl Default for TestConfig {\n fn default() -\u003e Self {\n Self {\n host: env::var(\"POLYMARKET_HOST\").unwrap_or_else(|_| \"https://clob.polymarket.com\".to_string()),\n chain_id: env::var(\"POLYMARKET_CHAIN_ID\")\n .unwrap_or_else(|_| \"137\".to_string())\n .parse()\n .unwrap_or(137),\n private_key: env::var(\"POLYMARKET_PRIVATE_KEY\").ok(),\n api_key: env::var(\"POLYMARKET_API_KEY\").ok(),\n api_secret: env::var(\"POLYMARKET_API_SECRET\").ok(),\n api_passphrase: env::var(\"POLYMARKET_API_PASSPHRASE\").ok(),\n test_timeout: Duration::from_secs(30),\n }\n }\n}\n\nimpl TestConfig {\n /// Check if we have authentication credentials\n pub fn has_auth(\u0026self) -\u003e bool {\n self.private_key.is_some()\n }\n\n /// Check if we have API credentials\n pub fn has_api_creds(\u0026self) -\u003e bool {\n self.api_key.is_some() \u0026\u0026 self.api_secret.is_some() \u0026\u0026 self.api_passphrase.is_some()\n }\n\n /// Create a basic client for testing\n pub fn create_basic_client(\u0026self) -\u003e ClobClient {\n ClobClient::new(\u0026self.host)\n }\n\n /// Create an authenticated client for testing\n pub fn create_auth_client(\u0026self) -\u003e Result\u003cClobClient\u003e {\n let private_key = self.private_key.as_ref()\n .ok_or_else(|| polyfill_rs::PolyfillError::auth(\"No private key provided\", polyfill_rs::errors::AuthErrorKind::InvalidCredentials))?;\n \n Ok(ClobClient::with_l1_headers(\u0026self.host, private_key, self.chain_id))\n }\n\n /// Print test configuration (without sensitive data)\n pub fn print_config(\u0026self) {\n println!(\"Test Configuration:\");\n println!(\" Host: {}\", self.host);\n println!(\" Chain ID: {}\", self.chain_id);\n println!(\" Has Auth: {}\", self.has_auth());\n println!(\" Has API Creds: {}\", self.has_api_creds());\n println!(\" Timeout: {:?}\", self.test_timeout);\n }\n}\n\n/// Test utilities for common operations\npub struct TestUtils;\n\nimpl TestUtils {\n /// Get a valid token_id for testing\n pub async fn get_test_token_id(client: \u0026ClobClient) -\u003e Result\u003cString\u003e {\n let markets = client.get_sampling_markets(None).await?;\n if markets.data.is_empty() {\n return Err(polyfill_rs::PolyfillError::internal_simple(\"No markets available for testing\"));\n }\n \n let token_id = markets.data[0].tokens[0].token_id.clone();\n println!(\"Using test token_id: {}\", token_id);\n Ok(token_id)\n }\n\n /// Wait for a condition with timeout\n pub async fn wait_for\u003cF, Fut\u003e(mut condition: F, timeout: Duration) -\u003e Result\u003c()\u003e\n where\n F: FnMut() -\u003e Fut,\n Fut: std::future::Future\u003cOutput = Result\u003cbool\u003e\u003e,\n {\n let start = std::time::Instant::now();\n let check_interval = Duration::from_millis(100);\n\n while start.elapsed() \u003c timeout {\n if condition().await? {\n return Ok(());\n }\n tokio::time::sleep(check_interval).await;\n }\n\n Err(polyfill_rs::PolyfillError::timeout(\n timeout,\n \"Condition not met within timeout\".to_string(),\n ))\n }\n\n /// Measure execution time of an async operation\n pub async fn measure_time\u003cF, Fut, T\u003e(operation: F) -\u003e (T, Duration)\n where\n F: FnOnce() -\u003e Fut,\n Fut: std::future::Future\u003cOutput = T\u003e,\n {\n let start = std::time::Instant::now();\n let result = operation().await;\n let duration = start.elapsed();\n (result, duration)\n }\n\n /// Assert that an operation completes within a reasonable time\n pub async fn assert_timely\u003cF, Fut, T\u003e(operation: F, max_duration: Duration) -\u003e Result\u003cT\u003e\n where\n F: FnOnce() -\u003e Fut,\n Fut: std::future::Future\u003cOutput = Result\u003cT\u003e\u003e,\n {\n let (result, duration) = Self::measure_time(|| async {\n operation().await\n }).await;\n\n if duration \u003e max_duration {\n return Err(polyfill_rs::PolyfillError::timeout(\n duration,\n format!(\"Operation took too long: {:?} \u003e {:?}\", duration, max_duration),\n ));\n }\n\n println!(\"Operation completed in {:?}\", duration);\n result\n }\n}\n\n/// Test result reporting\npub struct TestReporter;\n\nimpl TestReporter {\n /// Report test success\n pub fn success(test_name: \u0026str) {\n println!(\"{} passed\", test_name);\n }\n\n /// Report test failure\n pub fn failure(test_name: \u0026str, error: \u0026dyn std::error::Error) {\n println!(\"{} failed: {}\", test_name, error);\n }\n\n /// Report test skip\n pub fn skip(test_name: \u0026str, reason: \u0026str) {\n println!(\"{} skipped: {}\", test_name, reason);\n }\n\n /// Report test performance\n pub fn performance(test_name: \u0026str, duration: Duration) {\n println!(\"{} completed in {:?}\", test_name, duration);\n }\n} ","traces":[],"covered":0,"coverable":0}]};
var previousData = null;
</script>
<script crossorigin>/** @license React v16.13.1
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';(function(d,r){"object"===typeof exports&&"undefined"!==typeof module?r(exports):"function"===typeof define&&define.amd?define(["exports"],r):(d=d||self,r(d.React={}))})(this,function(d){function r(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;c<arguments.length;c++)b+="&args[]="+encodeURIComponent(arguments[c]);return"Minified React error #"+a+"; visit "+b+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}
function w(a,b,c){this.props=a;this.context=b;this.refs=ba;this.updater=c||ca}function da(){}function L(a,b,c){this.props=a;this.context=b;this.refs=ba;this.updater=c||ca}function ea(a,b,c){var g,e={},fa=null,d=null;if(null!=b)for(g in void 0!==b.ref&&(d=b.ref),void 0!==b.key&&(fa=""+b.key),b)ha.call(b,g)&&!ia.hasOwnProperty(g)&&(e[g]=b[g]);var h=arguments.length-2;if(1===h)e.children=c;else if(1<h){for(var k=Array(h),f=0;f<h;f++)k[f]=arguments[f+2];e.children=k}if(a&&a.defaultProps)for(g in h=a.defaultProps,
h)void 0===e[g]&&(e[g]=h[g]);return{$$typeof:x,type:a,key:fa,ref:d,props:e,_owner:M.current}}function va(a,b){return{$$typeof:x,type:a.type,key:b,ref:a.ref,props:a.props,_owner:a._owner}}function N(a){return"object"===typeof a&&null!==a&&a.$$typeof===x}function wa(a){var b={"=":"=0",":":"=2"};return"$"+(""+a).replace(/[=:]/g,function(a){return b[a]})}function ja(a,b,c,g){if(C.length){var e=C.pop();e.result=a;e.keyPrefix=b;e.func=c;e.context=g;e.count=0;return e}return{result:a,keyPrefix:b,func:c,
context:g,count:0}}function ka(a){a.result=null;a.keyPrefix=null;a.func=null;a.context=null;a.count=0;10>C.length&&C.push(a)}function O(a,b,c,g){var e=typeof a;if("undefined"===e||"boolean"===e)a=null;var d=!1;if(null===a)d=!0;else switch(e){case "string":case "number":d=!0;break;case "object":switch(a.$$typeof){case x:case xa:d=!0}}if(d)return c(g,a,""===b?"."+P(a,0):b),1;d=0;b=""===b?".":b+":";if(Array.isArray(a))for(var f=0;f<a.length;f++){e=a[f];var h=b+P(e,f);d+=O(e,h,c,g)}else if(null===a||
"object"!==typeof a?h=null:(h=la&&a[la]||a["@@iterator"],h="function"===typeof h?h:null),"function"===typeof h)for(a=h.call(a),f=0;!(e=a.next()).done;)e=e.value,h=b+P(e,f++),d+=O(e,h,c,g);else if("object"===e)throw c=""+a,Error(r(31,"[object Object]"===c?"object with keys {"+Object.keys(a).join(", ")+"}":c,""));return d}function Q(a,b,c){return null==a?0:O(a,"",b,c)}function P(a,b){return"object"===typeof a&&null!==a&&null!=a.key?wa(a.key):b.toString(36)}function ya(a,b,c){a.func.call(a.context,b,
a.count++)}function za(a,b,c){var g=a.result,e=a.keyPrefix;a=a.func.call(a.context,b,a.count++);Array.isArray(a)?R(a,g,c,function(a){return a}):null!=a&&(N(a)&&(a=va(a,e+(!a.key||b&&b.key===a.key?"":(""+a.key).replace(ma,"$&/")+"/")+c)),g.push(a))}function R(a,b,c,g,e){var d="";null!=c&&(d=(""+c).replace(ma,"$&/")+"/");b=ja(b,d,g,e);Q(a,za,b);ka(b)}function t(){var a=na.current;if(null===a)throw Error(r(321));return a}function S(a,b){var c=a.length;a.push(b);a:for(;;){var g=c-1>>>1,e=a[g];if(void 0!==
e&&0<D(e,b))a[g]=b,a[c]=e,c=g;else break a}}function n(a){a=a[0];return void 0===a?null:a}function E(a){var b=a[0];if(void 0!==b){var c=a.pop();if(c!==b){a[0]=c;a:for(var g=0,e=a.length;g<e;){var d=2*(g+1)-1,f=a[d],h=d+1,k=a[h];if(void 0!==f&&0>D(f,c))void 0!==k&&0>D(k,f)?(a[g]=k,a[h]=c,g=h):(a[g]=f,a[d]=c,g=d);else if(void 0!==k&&0>D(k,c))a[g]=k,a[h]=c,g=h;else break a}}return b}return null}function D(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}function F(a){for(var b=n(u);null!==
b;){if(null===b.callback)E(u);else if(b.startTime<=a)E(u),b.sortIndex=b.expirationTime,S(p,b);else break;b=n(u)}}function T(a){y=!1;F(a);if(!v)if(null!==n(p))v=!0,z(U);else{var b=n(u);null!==b&&G(T,b.startTime-a)}}function U(a,b){v=!1;y&&(y=!1,V());H=!0;var c=m;try{F(b);for(l=n(p);null!==l&&(!(l.expirationTime>b)||a&&!W());){var g=l.callback;if(null!==g){l.callback=null;m=l.priorityLevel;var e=g(l.expirationTime<=b);b=q();"function"===typeof e?l.callback=e:l===n(p)&&E(p);F(b)}else E(p);l=n(p)}if(null!==
l)var d=!0;else{var f=n(u);null!==f&&G(T,f.startTime-b);d=!1}return d}finally{l=null,m=c,H=!1}}function oa(a){switch(a){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1E4;default:return 5E3}}var f="function"===typeof Symbol&&Symbol.for,x=f?Symbol.for("react.element"):60103,xa=f?Symbol.for("react.portal"):60106,Aa=f?Symbol.for("react.fragment"):60107,Ba=f?Symbol.for("react.strict_mode"):60108,Ca=f?Symbol.for("react.profiler"):60114,Da=f?Symbol.for("react.provider"):60109,
Ea=f?Symbol.for("react.context"):60110,Fa=f?Symbol.for("react.forward_ref"):60112,Ga=f?Symbol.for("react.suspense"):60113,Ha=f?Symbol.for("react.memo"):60115,Ia=f?Symbol.for("react.lazy"):60116,la="function"===typeof Symbol&&Symbol.iterator,pa=Object.getOwnPropertySymbols,Ja=Object.prototype.hasOwnProperty,Ka=Object.prototype.propertyIsEnumerable,I=function(){try{if(!Object.assign)return!1;var a=new String("abc");a[5]="de";if("5"===Object.getOwnPropertyNames(a)[0])return!1;var b={};for(a=0;10>a;a++)b["_"+
String.fromCharCode(a)]=a;if("0123456789"!==Object.getOwnPropertyNames(b).map(function(a){return b[a]}).join(""))return!1;var c={};"abcdefghijklmnopqrst".split("").forEach(function(a){c[a]=a});return"abcdefghijklmnopqrst"!==Object.keys(Object.assign({},c)).join("")?!1:!0}catch(g){return!1}}()?Object.assign:function(a,b){if(null===a||void 0===a)throw new TypeError("Object.assign cannot be called with null or undefined");var c=Object(a);for(var g,e=1;e<arguments.length;e++){var d=Object(arguments[e]);
for(var f in d)Ja.call(d,f)&&(c[f]=d[f]);if(pa){g=pa(d);for(var h=0;h<g.length;h++)Ka.call(d,g[h])&&(c[g[h]]=d[g[h]])}}return c},ca={isMounted:function(a){return!1},enqueueForceUpdate:function(a,b,c){},enqueueReplaceState:function(a,b,c,d){},enqueueSetState:function(a,b,c,d){}},ba={};w.prototype.isReactComponent={};w.prototype.setState=function(a,b){if("object"!==typeof a&&"function"!==typeof a&&null!=a)throw Error(r(85));this.updater.enqueueSetState(this,a,b,"setState")};w.prototype.forceUpdate=
function(a){this.updater.enqueueForceUpdate(this,a,"forceUpdate")};da.prototype=w.prototype;f=L.prototype=new da;f.constructor=L;I(f,w.prototype);f.isPureReactComponent=!0;var M={current:null},ha=Object.prototype.hasOwnProperty,ia={key:!0,ref:!0,__self:!0,__source:!0},ma=/\/+/g,C=[],na={current:null},X;if("undefined"===typeof window||"function"!==typeof MessageChannel){var A=null,qa=null,ra=function(){if(null!==A)try{var a=q();A(!0,a);A=null}catch(b){throw setTimeout(ra,0),b;}},La=Date.now();var q=
function(){return Date.now()-La};var z=function(a){null!==A?setTimeout(z,0,a):(A=a,setTimeout(ra,0))};var G=function(a,b){qa=setTimeout(a,b)};var V=function(){clearTimeout(qa)};var W=function(){return!1};f=X=function(){}}else{var Y=window.performance,sa=window.Date,Ma=window.setTimeout,Na=window.clearTimeout;"undefined"!==typeof console&&(f=window.cancelAnimationFrame,"function"!==typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),
"function"!==typeof f&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"));if("object"===typeof Y&&"function"===typeof Y.now)q=function(){return Y.now()};else{var Oa=sa.now();q=function(){return sa.now()-Oa}}var J=!1,K=null,Z=-1,ta=5,ua=0;W=function(){return q()>=ua};f=function(){};X=function(a){0>a||125<a?console.error("forceFrameRate takes a positive int between 0 and 125, forcing framerates higher than 125 fps is not unsupported"):
ta=0<a?Math.floor(1E3/a):5};var B=new MessageChannel,aa=B.port2;B.port1.onmessage=function(){if(null!==K){var a=q();ua=a+ta;try{K(!0,a)?aa.postMessage(null):(J=!1,K=null)}catch(b){throw aa.postMessage(null),b;}}else J=!1};z=function(a){K=a;J||(J=!0,aa.postMessage(null))};G=function(a,b){Z=Ma(function(){a(q())},b)};V=function(){Na(Z);Z=-1}}var p=[],u=[],Pa=1,l=null,m=3,H=!1,v=!1,y=!1,Qa=0;B={ReactCurrentDispatcher:na,ReactCurrentOwner:M,IsSomeRendererActing:{current:!1},assign:I};I(B,{Scheduler:{__proto__:null,
unstable_ImmediatePriority:1,unstable_UserBlockingPriority:2,unstable_NormalPriority:3,unstable_IdlePriority:5,unstable_LowPriority:4,unstable_runWithPriority:function(a,b){switch(a){case 1:case 2:case 3:case 4:case 5:break;default:a=3}var c=m;m=a;try{return b()}finally{m=c}},unstable_next:function(a){switch(m){case 1:case 2:case 3:var b=3;break;default:b=m}var c=m;m=b;try{return a()}finally{m=c}},unstable_scheduleCallback:function(a,b,c){var d=q();if("object"===typeof c&&null!==c){var e=c.delay;
e="number"===typeof e&&0<e?d+e:d;c="number"===typeof c.timeout?c.timeout:oa(a)}else c=oa(a),e=d;c=e+c;a={id:Pa++,callback:b,priorityLevel:a,startTime:e,expirationTime:c,sortIndex:-1};e>d?(a.sortIndex=e,S(u,a),null===n(p)&&a===n(u)&&(y?V():y=!0,G(T,e-d))):(a.sortIndex=c,S(p,a),v||H||(v=!0,z(U)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=m;return function(){var c=m;m=b;try{return a.apply(this,arguments)}finally{m=c}}},unstable_getCurrentPriorityLevel:function(){return m},
unstable_shouldYield:function(){var a=q();F(a);var b=n(p);return b!==l&&null!==l&&null!==b&&null!==b.callback&&b.startTime<=a&&b.expirationTime<l.expirationTime||W()},unstable_requestPaint:f,unstable_continueExecution:function(){v||H||(v=!0,z(U))},unstable_pauseExecution:function(){},unstable_getFirstCallbackNode:function(){return n(p)},get unstable_now(){return q},get unstable_forceFrameRate(){return X},unstable_Profiling:null},SchedulerTracing:{__proto__:null,__interactionsRef:null,__subscriberRef:null,
unstable_clear:function(a){return a()},unstable_getCurrent:function(){return null},unstable_getThreadID:function(){return++Qa},unstable_trace:function(a,b,c){return c()},unstable_wrap:function(a){return a},unstable_subscribe:function(a){},unstable_unsubscribe:function(a){}}});d.Children={map:function(a,b,c){if(null==a)return a;var d=[];R(a,d,null,b,c);return d},forEach:function(a,b,c){if(null==a)return a;b=ja(null,null,b,c);Q(a,ya,b);ka(b)},count:function(a){return Q(a,function(){return null},null)},
toArray:function(a){var b=[];R(a,b,null,function(a){return a});return b},only:function(a){if(!N(a))throw Error(r(143));return a}};d.Component=w;d.Fragment=Aa;d.Profiler=Ca;d.PureComponent=L;d.StrictMode=Ba;d.Suspense=Ga;d.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=B;d.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error(r(267,a));var d=I({},a.props),e=a.key,f=a.ref,m=a._owner;if(null!=b){void 0!==b.ref&&(f=b.ref,m=M.current);void 0!==b.key&&(e=""+b.key);if(a.type&&a.type.defaultProps)var h=
a.type.defaultProps;for(k in b)ha.call(b,k)&&!ia.hasOwnProperty(k)&&(d[k]=void 0===b[k]&&void 0!==h?h[k]:b[k])}var k=arguments.length-2;if(1===k)d.children=c;else if(1<k){h=Array(k);for(var l=0;l<k;l++)h[l]=arguments[l+2];d.children=h}return{$$typeof:x,type:a.type,key:e,ref:f,props:d,_owner:m}};d.createContext=function(a,b){void 0===b&&(b=null);a={$$typeof:Ea,_calculateChangedBits:b,_currentValue:a,_currentValue2:a,_threadCount:0,Provider:null,Consumer:null};a.Provider={$$typeof:Da,_context:a};return a.Consumer=
a};d.createElement=ea;d.createFactory=function(a){var b=ea.bind(null,a);b.type=a;return b};d.createRef=function(){return{current:null}};d.forwardRef=function(a){return{$$typeof:Fa,render:a}};d.isValidElement=N;d.lazy=function(a){return{$$typeof:Ia,_ctor:a,_status:-1,_result:null}};d.memo=function(a,b){return{$$typeof:Ha,type:a,compare:void 0===b?null:b}};d.useCallback=function(a,b){return t().useCallback(a,b)};d.useContext=function(a,b){return t().useContext(a,b)};d.useDebugValue=function(a,b){};
d.useEffect=function(a,b){return t().useEffect(a,b)};d.useImperativeHandle=function(a,b,c){return t().useImperativeHandle(a,b,c)};d.useLayoutEffect=function(a,b){return t().useLayoutEffect(a,b)};d.useMemo=function(a,b){return t().useMemo(a,b)};d.useReducer=function(a,b,c){return t().useReducer(a,b,c)};d.useRef=function(a){return t().useRef(a)};d.useState=function(a){return t().useState(a)};d.version="16.13.1"});
</script>
<script crossorigin>/** @license React v16.13.1
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
Modernizr 3.0.0pre (Custom Build) | MIT
*/
'use strict';(function(I,ea){"object"===typeof exports&&"undefined"!==typeof module?ea(exports,require("react")):"function"===typeof define&&define.amd?define(["exports","react"],ea):(I=I||self,ea(I.ReactDOM={},I.React))})(this,function(I,ea){function k(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;c<arguments.length;c++)b+="&args[]="+encodeURIComponent(arguments[c]);return"Minified React error #"+a+"; visit "+b+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}
function ji(a,b,c,d,e,f,g,h,m){yb=!1;gc=null;ki.apply(li,arguments)}function mi(a,b,c,d,e,f,g,h,m){ji.apply(this,arguments);if(yb){if(yb){var n=gc;yb=!1;gc=null}else throw Error(k(198));hc||(hc=!0,pd=n)}}function lf(a,b,c){var d=a.type||"unknown-event";a.currentTarget=mf(c);mi(d,b,void 0,a);a.currentTarget=null}function nf(){if(ic)for(var a in cb){var b=cb[a],c=ic.indexOf(a);if(!(-1<c))throw Error(k(96,a));if(!jc[c]){if(!b.extractEvents)throw Error(k(97,a));jc[c]=b;c=b.eventTypes;for(var d in c){var e=
void 0;var f=c[d],g=b,h=d;if(qd.hasOwnProperty(h))throw Error(k(99,h));qd[h]=f;var m=f.phasedRegistrationNames;if(m){for(e in m)m.hasOwnProperty(e)&&of(m[e],g,h);e=!0}else f.registrationName?(of(f.registrationName,g,h),e=!0):e=!1;if(!e)throw Error(k(98,d,a));}}}}function of(a,b,c){if(db[a])throw Error(k(100,a));db[a]=b;rd[a]=b.eventTypes[c].dependencies}function pf(a){var b=!1,c;for(c in a)if(a.hasOwnProperty(c)){var d=a[c];if(!cb.hasOwnProperty(c)||cb[c]!==d){if(cb[c])throw Error(k(102,c));cb[c]=
d;b=!0}}b&&nf()}function qf(a){if(a=rf(a)){if("function"!==typeof sd)throw Error(k(280));var b=a.stateNode;b&&(b=td(b),sd(a.stateNode,a.type,b))}}function sf(a){eb?fb?fb.push(a):fb=[a]:eb=a}function tf(){if(eb){var a=eb,b=fb;fb=eb=null;qf(a);if(b)for(a=0;a<b.length;a++)qf(b[a])}}function ud(){if(null!==eb||null!==fb)vd(),tf()}function uf(a,b,c){if(wd)return a(b,c);wd=!0;try{return vf(a,b,c)}finally{wd=!1,ud()}}function ni(a){if(wf.call(xf,a))return!0;if(wf.call(yf,a))return!1;if(oi.test(a))return xf[a]=
!0;yf[a]=!0;return!1}function pi(a,b,c,d){if(null!==c&&0===c.type)return!1;switch(typeof b){case "function":case "symbol":return!0;case "boolean":if(d)return!1;if(null!==c)return!c.acceptsBooleans;a=a.toLowerCase().slice(0,5);return"data-"!==a&&"aria-"!==a;default:return!1}}function qi(a,b,c,d){if(null===b||"undefined"===typeof b||pi(a,b,c,d))return!0;if(d)return!1;if(null!==c)switch(c.type){case 3:return!b;case 4:return!1===b;case 5:return isNaN(b);case 6:return isNaN(b)||1>b}return!1}function L(a,
b,c,d,e,f){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f}function xd(a,b,c,d){var e=E.hasOwnProperty(b)?E[b]:null;var f=null!==e?0===e.type:d?!1:!(2<b.length)||"o"!==b[0]&&"O"!==b[0]||"n"!==b[1]&&"N"!==b[1]?!1:!0;f||(qi(b,c,e,d)&&(c=null),d||null===e?ni(b)&&(null===c?a.removeAttribute(b):a.setAttribute(b,""+c)):e.mustUseProperty?a[e.propertyName]=null===c?3===e.type?!1:"":c:(b=e.attributeName,
d=e.attributeNamespace,null===c?a.removeAttribute(b):(e=e.type,c=3===e||4===e&&!0===c?"":""+c,d?a.setAttributeNS(d,b,c):a.setAttribute(b,c))))}function zb(a){if(null===a||"object"!==typeof a)return null;a=zf&&a[zf]||a["@@iterator"];return"function"===typeof a?a:null}function ri(a){if(-1===a._status){a._status=0;var b=a._ctor;b=b();a._result=b;b.then(function(b){0===a._status&&(b=b.default,a._status=1,a._result=b)},function(b){0===a._status&&(a._status=2,a._result=b)})}}function na(a){if(null==a)return null;
if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case Ma:return"Fragment";case gb:return"Portal";case kc:return"Profiler";case Af:return"StrictMode";case lc:return"Suspense";case yd:return"SuspenseList"}if("object"===typeof a)switch(a.$$typeof){case Bf:return"Context.Consumer";case Cf:return"Context.Provider";case zd:var b=a.render;b=b.displayName||b.name||"";return a.displayName||(""!==b?"ForwardRef("+b+")":"ForwardRef");case Ad:return na(a.type);
case Df:return na(a.render);case Ef:if(a=1===a._status?a._result:null)return na(a)}return null}function Bd(a){var b="";do{a:switch(a.tag){case 3:case 4:case 6:case 7:case 10:case 9:var c="";break a;default:var d=a._debugOwner,e=a._debugSource,f=na(a.type);c=null;d&&(c=na(d.type));d=f;f="";e?f=" (at "+e.fileName.replace(si,"")+":"+e.lineNumber+")":c&&(f=" (created by "+c+")");c="\n in "+(d||"Unknown")+f}b+=c;a=a.return}while(a);return b}function va(a){switch(typeof a){case "boolean":case "number":case "object":case "string":case "undefined":return a;
default:return""}}function Ff(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===b)}function ti(a){var b=Ff(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a,
b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker=null;delete a[b]}}}}function mc(a){a._valueTracker||(a._valueTracker=ti(a))}function Gf(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=Ff(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Cd(a,b){var c=b.checked;return M({},b,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=
c?c:a._wrapperState.initialChecked})}function Hf(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=va(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function If(a,b){b=b.checked;null!=b&&xd(a,"checked",b,!1)}function Dd(a,b){If(a,b);var c=va(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!=c)a.value=""+c}else a.value!==
""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?Ed(a,b.type,c):b.hasOwnProperty("defaultValue")&&Ed(a,b.type,va(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}function Jf(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d=b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue;c||b===a.value||(a.value=
b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)}function Ed(a,b,c){if("number"!==b||a.ownerDocument.activeElement!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}function ui(a){var b="";ea.Children.forEach(a,function(a){null!=a&&(b+=a)});return b}function Fd(a,b){a=M({children:void 0},b);if(b=ui(b.children))a.children=b;return a}function hb(a,b,c,d){a=a.options;if(b){b={};
for(var e=0;e<c.length;e++)b["$"+c[e]]=!0;for(c=0;c<a.length;c++)e=b.hasOwnProperty("$"+a[c].value),a[c].selected!==e&&(a[c].selected=e),e&&d&&(a[c].defaultSelected=!0)}else{c=""+va(c);b=null;for(e=0;e<a.length;e++){if(a[e].value===c){a[e].selected=!0;d&&(a[e].defaultSelected=!0);return}null!==b||a[e].disabled||(b=a[e])}null!==b&&(b.selected=!0)}}function Gd(a,b){if(null!=b.dangerouslySetInnerHTML)throw Error(k(91));return M({},b,{value:void 0,defaultValue:void 0,children:""+a._wrapperState.initialValue})}
function Kf(a,b){var c=b.value;if(null==c){c=b.children;b=b.defaultValue;if(null!=c){if(null!=b)throw Error(k(92));if(Array.isArray(c)){if(!(1>=c.length))throw Error(k(93));c=c[0]}b=c}null==b&&(b="");c=b}a._wrapperState={initialValue:va(c)}}function Lf(a,b){var c=va(b.value),d=va(b.defaultValue);null!=c&&(c=""+c,c!==a.value&&(a.value=c),null==b.defaultValue&&a.defaultValue!==c&&(a.defaultValue=c));null!=d&&(a.defaultValue=""+d)}function Mf(a,b){b=a.textContent;b===a._wrapperState.initialValue&&""!==
b&&null!==b&&(a.value=b)}function Nf(a){switch(a){case "svg":return"http://www.w3.org/2000/svg";case "math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function Hd(a,b){return null==a||"http://www.w3.org/1999/xhtml"===a?Nf(b):"http://www.w3.org/2000/svg"===a&&"foreignObject"===b?"http://www.w3.org/1999/xhtml":a}function nc(a,b){var c={};c[a.toLowerCase()]=b.toLowerCase();c["Webkit"+a]="webkit"+b;c["Moz"+a]="moz"+b;return c}function oc(a){if(Id[a])return Id[a];
if(!ib[a])return a;var b=ib[a],c;for(c in b)if(b.hasOwnProperty(c)&&c in Of)return Id[a]=b[c];return a}function Jd(a){var b=Pf.get(a);void 0===b&&(b=new Map,Pf.set(a,b));return b}function Na(a){var b=a,c=a;if(a.alternate)for(;b.return;)b=b.return;else{a=b;do b=a,0!==(b.effectTag&1026)&&(c=b.return),a=b.return;while(a)}return 3===b.tag?c:null}function Qf(a){if(13===a.tag){var b=a.memoizedState;null===b&&(a=a.alternate,null!==a&&(b=a.memoizedState));if(null!==b)return b.dehydrated}return null}function Rf(a){if(Na(a)!==
a)throw Error(k(188));}function vi(a){var b=a.alternate;if(!b){b=Na(a);if(null===b)throw Error(k(188));return b!==a?null:a}for(var c=a,d=b;;){var e=c.return;if(null===e)break;var f=e.alternate;if(null===f){d=e.return;if(null!==d){c=d;continue}break}if(e.child===f.child){for(f=e.child;f;){if(f===c)return Rf(e),a;if(f===d)return Rf(e),b;f=f.sibling}throw Error(k(188));}if(c.return!==d.return)c=e,d=f;else{for(var g=!1,h=e.child;h;){if(h===c){g=!0;c=e;d=f;break}if(h===d){g=!0;d=e;c=f;break}h=h.sibling}if(!g){for(h=
f.child;h;){if(h===c){g=!0;c=f;d=e;break}if(h===d){g=!0;d=f;c=e;break}h=h.sibling}if(!g)throw Error(k(189));}}if(c.alternate!==d)throw Error(k(190));}if(3!==c.tag)throw Error(k(188));return c.stateNode.current===c?a:b}function Sf(a){a=vi(a);if(!a)return null;for(var b=a;;){if(5===b.tag||6===b.tag)return b;if(b.child)b.child.return=b,b=b.child;else{if(b===a)break;for(;!b.sibling;){if(!b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}}return null}function jb(a,b){if(null==
b)throw Error(k(30));if(null==a)return b;if(Array.isArray(a)){if(Array.isArray(b))return a.push.apply(a,b),a;a.push(b);return a}return Array.isArray(b)?[a].concat(b):[a,b]}function Kd(a,b,c){Array.isArray(a)?a.forEach(b,c):a&&b.call(c,a)}function pc(a){null!==a&&(Ab=jb(Ab,a));a=Ab;Ab=null;if(a){Kd(a,wi);if(Ab)throw Error(k(95));if(hc)throw a=pd,hc=!1,pd=null,a;}}function Ld(a){a=a.target||a.srcElement||window;a.correspondingUseElement&&(a=a.correspondingUseElement);return 3===a.nodeType?a.parentNode:
a}function Tf(a){if(!wa)return!1;a="on"+a;var b=a in document;b||(b=document.createElement("div"),b.setAttribute(a,"return;"),b="function"===typeof b[a]);return b}function Uf(a){a.topLevelType=null;a.nativeEvent=null;a.targetInst=null;a.ancestors.length=0;10>qc.length&&qc.push(a)}function Vf(a,b,c,d){if(qc.length){var e=qc.pop();e.topLevelType=a;e.eventSystemFlags=d;e.nativeEvent=b;e.targetInst=c;return e}return{topLevelType:a,eventSystemFlags:d,nativeEvent:b,targetInst:c,ancestors:[]}}function Wf(a){var b=
a.targetInst,c=b;do{if(!c){a.ancestors.push(c);break}var d=c;if(3===d.tag)d=d.stateNode.containerInfo;else{for(;d.return;)d=d.return;d=3!==d.tag?null:d.stateNode.containerInfo}if(!d)break;b=c.tag;5!==b&&6!==b||a.ancestors.push(c);c=Bb(d)}while(c);for(c=0;c<a.ancestors.length;c++){b=a.ancestors[c];var e=Ld(a.nativeEvent);d=a.topLevelType;var f=a.nativeEvent,g=a.eventSystemFlags;0===c&&(g|=64);for(var h=null,m=0;m<jc.length;m++){var n=jc[m];n&&(n=n.extractEvents(d,b,f,e,g))&&(h=jb(h,n))}pc(h)}}function Md(a,
b,c){if(!c.has(a)){switch(a){case "scroll":Cb(b,"scroll",!0);break;case "focus":case "blur":Cb(b,"focus",!0);Cb(b,"blur",!0);c.set("blur",null);c.set("focus",null);break;case "cancel":case "close":Tf(a)&&Cb(b,a,!0);break;case "invalid":case "submit":case "reset":break;default:-1===Db.indexOf(a)&&w(a,b)}c.set(a,null)}}function xi(a,b){var c=Jd(b);Nd.forEach(function(a){Md(a,b,c)});yi.forEach(function(a){Md(a,b,c)})}function Od(a,b,c,d,e){return{blockedOn:a,topLevelType:b,eventSystemFlags:c|32,nativeEvent:e,
container:d}}function Xf(a,b){switch(a){case "focus":case "blur":xa=null;break;case "dragenter":case "dragleave":ya=null;break;case "mouseover":case "mouseout":za=null;break;case "pointerover":case "pointerout":Eb.delete(b.pointerId);break;case "gotpointercapture":case "lostpointercapture":Fb.delete(b.pointerId)}}function Gb(a,b,c,d,e,f){if(null===a||a.nativeEvent!==f)return a=Od(b,c,d,e,f),null!==b&&(b=Hb(b),null!==b&&Yf(b)),a;a.eventSystemFlags|=d;return a}function zi(a,b,c,d,e){switch(b){case "focus":return xa=
Gb(xa,a,b,c,d,e),!0;case "dragenter":return ya=Gb(ya,a,b,c,d,e),!0;case "mouseover":return za=Gb(za,a,b,c,d,e),!0;case "pointerover":var f=e.pointerId;Eb.set(f,Gb(Eb.get(f)||null,a,b,c,d,e));return!0;case "gotpointercapture":return f=e.pointerId,Fb.set(f,Gb(Fb.get(f)||null,a,b,c,d,e)),!0}return!1}function Ai(a){var b=Bb(a.target);if(null!==b){var c=Na(b);if(null!==c)if(b=c.tag,13===b){if(b=Qf(c),null!==b){a.blockedOn=b;Pd(a.priority,function(){Bi(c)});return}}else if(3===b&&c.stateNode.hydrate){a.blockedOn=
3===c.tag?c.stateNode.containerInfo:null;return}}a.blockedOn=null}function rc(a){if(null!==a.blockedOn)return!1;var b=Qd(a.topLevelType,a.eventSystemFlags,a.container,a.nativeEvent);if(null!==b){var c=Hb(b);null!==c&&Yf(c);a.blockedOn=b;return!1}return!0}function Zf(a,b,c){rc(a)&&c.delete(b)}function Ci(){for(Rd=!1;0<fa.length;){var a=fa[0];if(null!==a.blockedOn){a=Hb(a.blockedOn);null!==a&&Di(a);break}var b=Qd(a.topLevelType,a.eventSystemFlags,a.container,a.nativeEvent);null!==b?a.blockedOn=b:fa.shift()}null!==
xa&&rc(xa)&&(xa=null);null!==ya&&rc(ya)&&(ya=null);null!==za&&rc(za)&&(za=null);Eb.forEach(Zf);Fb.forEach(Zf)}function Ib(a,b){a.blockedOn===b&&(a.blockedOn=null,Rd||(Rd=!0,$f(ag,Ci)))}function bg(a){if(0<fa.length){Ib(fa[0],a);for(var b=1;b<fa.length;b++){var c=fa[b];c.blockedOn===a&&(c.blockedOn=null)}}null!==xa&&Ib(xa,a);null!==ya&&Ib(ya,a);null!==za&&Ib(za,a);b=function(b){return Ib(b,a)};Eb.forEach(b);Fb.forEach(b);for(b=0;b<Jb.length;b++)c=Jb[b],c.blockedOn===a&&(c.blockedOn=null);for(;0<Jb.length&&
(b=Jb[0],null===b.blockedOn);)Ai(b),null===b.blockedOn&&Jb.shift()}function Sd(a,b){for(var c=0;c<a.length;c+=2){var d=a[c],e=a[c+1],f="on"+(e[0].toUpperCase()+e.slice(1));f={phasedRegistrationNames:{bubbled:f,captured:f+"Capture"},dependencies:[d],eventPriority:b};Td.set(d,b);cg.set(d,f);dg[e]=f}}function w(a,b){Cb(b,a,!1)}function Cb(a,b,c){var d=Td.get(b);switch(void 0===d?2:d){case 0:d=Ei.bind(null,b,1,a);break;case 1:d=Fi.bind(null,b,1,a);break;default:d=sc.bind(null,b,1,a)}c?a.addEventListener(b,
d,!0):a.addEventListener(b,d,!1)}function Ei(a,b,c,d){Oa||vd();var e=sc,f=Oa;Oa=!0;try{eg(e,a,b,c,d)}finally{(Oa=f)||ud()}}function Fi(a,b,c,d){Gi(Hi,sc.bind(null,a,b,c,d))}function sc(a,b,c,d){if(tc)if(0<fa.length&&-1<Nd.indexOf(a))a=Od(null,a,b,c,d),fa.push(a);else{var e=Qd(a,b,c,d);if(null===e)Xf(a,d);else if(-1<Nd.indexOf(a))a=Od(e,a,b,c,d),fa.push(a);else if(!zi(e,a,b,c,d)){Xf(a,d);a=Vf(a,d,null,b);try{uf(Wf,a)}finally{Uf(a)}}}}function Qd(a,b,c,d){c=Ld(d);c=Bb(c);if(null!==c){var e=Na(c);if(null===
e)c=null;else{var f=e.tag;if(13===f){c=Qf(e);if(null!==c)return c;c=null}else if(3===f){if(e.stateNode.hydrate)return 3===e.tag?e.stateNode.containerInfo:null;c=null}else e!==c&&(c=null)}}a=Vf(a,d,c,b);try{uf(Wf,a)}finally{Uf(a)}return null}function fg(a,b,c){return null==b||"boolean"===typeof b||""===b?"":c||"number"!==typeof b||0===b||Kb.hasOwnProperty(a)&&Kb[a]?(""+b).trim():b+"px"}function gg(a,b){a=a.style;for(var c in b)if(b.hasOwnProperty(c)){var d=0===c.indexOf("--"),e=fg(c,b[c],d);"float"===
c&&(c="cssFloat");d?a.setProperty(c,e):a[c]=e}}function Ud(a,b){if(b){if(Ii[a]&&(null!=b.children||null!=b.dangerouslySetInnerHTML))throw Error(k(137,a,""));if(null!=b.dangerouslySetInnerHTML){if(null!=b.children)throw Error(k(60));if(!("object"===typeof b.dangerouslySetInnerHTML&&"__html"in b.dangerouslySetInnerHTML))throw Error(k(61));}if(null!=b.style&&"object"!==typeof b.style)throw Error(k(62,""));}}function Vd(a,b){if(-1===a.indexOf("-"))return"string"===typeof b.is;switch(a){case "annotation-xml":case "color-profile":case "font-face":case "font-face-src":case "font-face-uri":case "font-face-format":case "font-face-name":case "missing-glyph":return!1;
default:return!0}}function oa(a,b){a=9===a.nodeType||11===a.nodeType?a:a.ownerDocument;var c=Jd(a);b=rd[b];for(var d=0;d<b.length;d++)Md(b[d],a,c)}function uc(){}function Wd(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement||a.body}catch(b){return a.body}}function hg(a){for(;a&&a.firstChild;)a=a.firstChild;return a}function ig(a,b){var c=hg(a);a=0;for(var d;c;){if(3===c.nodeType){d=a+c.textContent.length;if(a<=b&&d>=b)return{node:c,
offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=hg(c)}}function jg(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?jg(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function kg(){for(var a=window,b=Wd();b instanceof a.HTMLIFrameElement;){try{var c="string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;b=Wd(a.document)}return b}
function Xd(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)}function lg(a,b){switch(a){case "button":case "input":case "select":case "textarea":return!!b.autoFocus}return!1}function Yd(a,b){return"textarea"===a||"option"===a||"noscript"===a||"string"===typeof b.children||"number"===typeof b.children||"object"===typeof b.dangerouslySetInnerHTML&&
null!==b.dangerouslySetInnerHTML&&null!=b.dangerouslySetInnerHTML.__html}function kb(a){for(;null!=a;a=a.nextSibling){var b=a.nodeType;if(1===b||3===b)break}return a}function mg(a){a=a.previousSibling;for(var b=0;a;){if(8===a.nodeType){var c=a.data;if(c===ng||c===Zd||c===$d){if(0===b)return a;b--}else c===og&&b++}a=a.previousSibling}return null}function Bb(a){var b=a[Aa];if(b)return b;for(var c=a.parentNode;c;){if(b=c[Lb]||c[Aa]){c=b.alternate;if(null!==b.child||null!==c&&null!==c.child)for(a=mg(a);null!==
a;){if(c=a[Aa])return c;a=mg(a)}return b}a=c;c=a.parentNode}return null}function Hb(a){a=a[Aa]||a[Lb];return!a||5!==a.tag&&6!==a.tag&&13!==a.tag&&3!==a.tag?null:a}function Pa(a){if(5===a.tag||6===a.tag)return a.stateNode;throw Error(k(33));}function ae(a){return a[vc]||null}function pa(a){do a=a.return;while(a&&5!==a.tag);return a?a:null}function pg(a,b){var c=a.stateNode;if(!c)return null;var d=td(c);if(!d)return null;c=d[b];a:switch(b){case "onClick":case "onClickCapture":case "onDoubleClick":case "onDoubleClickCapture":case "onMouseDown":case "onMouseDownCapture":case "onMouseMove":case "onMouseMoveCapture":case "onMouseUp":case "onMouseUpCapture":case "onMouseEnter":(d=
!d.disabled)||(a=a.type,d=!("button"===a||"input"===a||"select"===a||"textarea"===a));a=!d;break a;default:a=!1}if(a)return null;if(c&&"function"!==typeof c)throw Error(k(231,b,typeof c));return c}function qg(a,b,c){if(b=pg(a,c.dispatchConfig.phasedRegistrationNames[b]))c._dispatchListeners=jb(c._dispatchListeners,b),c._dispatchInstances=jb(c._dispatchInstances,a)}function Ji(a){if(a&&a.dispatchConfig.phasedRegistrationNames){for(var b=a._targetInst,c=[];b;)c.push(b),b=pa(b);for(b=c.length;0<b--;)qg(c[b],
"captured",a);for(b=0;b<c.length;b++)qg(c[b],"bubbled",a)}}function be(a,b,c){a&&c&&c.dispatchConfig.registrationName&&(b=pg(a,c.dispatchConfig.registrationName))&&(c._dispatchListeners=jb(c._dispatchListeners,b),c._dispatchInstances=jb(c._dispatchInstances,a))}function Ki(a){a&&a.dispatchConfig.registrationName&&be(a._targetInst,null,a)}function lb(a){Kd(a,Ji)}function rg(){if(wc)return wc;var a,b=ce,c=b.length,d,e="value"in Ba?Ba.value:Ba.textContent,f=e.length;for(a=0;a<c&&b[a]===e[a];a++);var g=
c-a;for(d=1;d<=g&&b[c-d]===e[f-d];d++);return wc=e.slice(a,1<d?1-d:void 0)}function xc(){return!0}function yc(){return!1}function R(a,b,c,d){this.dispatchConfig=a;this._targetInst=b;this.nativeEvent=c;a=this.constructor.Interface;for(var e in a)a.hasOwnProperty(e)&&((b=a[e])?this[e]=b(c):"target"===e?this.target=d:this[e]=c[e]);this.isDefaultPrevented=(null!=c.defaultPrevented?c.defaultPrevented:!1===c.returnValue)?xc:yc;this.isPropagationStopped=yc;return this}function Li(a,b,c,d){if(this.eventPool.length){var e=
this.eventPool.pop();this.call(e,a,b,c,d);return e}return new this(a,b,c,d)}function Mi(a){if(!(a instanceof this))throw Error(k(279));a.destructor();10>this.eventPool.length&&this.eventPool.push(a)}function sg(a){a.eventPool=[];a.getPooled=Li;a.release=Mi}function tg(a,b){switch(a){case "keyup":return-1!==Ni.indexOf(b.keyCode);case "keydown":return 229!==b.keyCode;case "keypress":case "mousedown":case "blur":return!0;default:return!1}}function ug(a){a=a.detail;return"object"===typeof a&&"data"in
a?a.data:null}function Oi(a,b){switch(a){case "compositionend":return ug(b);case "keypress":if(32!==b.which)return null;vg=!0;return wg;case "textInput":return a=b.data,a===wg&&vg?null:a;default:return null}}function Pi(a,b){if(mb)return"compositionend"===a||!de&&tg(a,b)?(a=rg(),wc=ce=Ba=null,mb=!1,a):null;switch(a){case "paste":return null;case "keypress":if(!(b.ctrlKey||b.altKey||b.metaKey)||b.ctrlKey&&b.altKey){if(b.char&&1<b.char.length)return b.char;if(b.which)return String.fromCharCode(b.which)}return null;
case "compositionend":return xg&&"ko"!==b.locale?null:b.data;default:return null}}function yg(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return"input"===b?!!Qi[a.type]:"textarea"===b?!0:!1}function zg(a,b,c){a=R.getPooled(Ag.change,a,b,c);a.type="change";sf(c);lb(a);return a}function Ri(a){pc(a)}function zc(a){var b=Pa(a);if(Gf(b))return a}function Si(a,b){if("change"===a)return b}function Bg(){Mb&&(Mb.detachEvent("onpropertychange",Cg),Nb=Mb=null)}function Cg(a){if("value"===a.propertyName&&
zc(Nb))if(a=zg(Nb,a,Ld(a)),Oa)pc(a);else{Oa=!0;try{ee(Ri,a)}finally{Oa=!1,ud()}}}function Ti(a,b,c){"focus"===a?(Bg(),Mb=b,Nb=c,Mb.attachEvent("onpropertychange",Cg)):"blur"===a&&Bg()}function Ui(a,b){if("selectionchange"===a||"keyup"===a||"keydown"===a)return zc(Nb)}function Vi(a,b){if("click"===a)return zc(b)}function Wi(a,b){if("input"===a||"change"===a)return zc(b)}function Xi(a){var b=this.nativeEvent;return b.getModifierState?b.getModifierState(a):(a=Yi[a])?!!b[a]:!1}function fe(a){return Xi}
function Zi(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}function Ob(a,b){if(Qa(a,b))return!0;if("object"!==typeof a||null===a||"object"!==typeof b||null===b)return!1;var c=Object.keys(a),d=Object.keys(b);if(c.length!==d.length)return!1;for(d=0;d<c.length;d++)if(!$i.call(b,c[d])||!Qa(a[c[d]],b[c[d]]))return!1;return!0}function Dg(a,b){var c=b.window===b?b.document:9===b.nodeType?b:b.ownerDocument;if(ge||null==nb||nb!==Wd(c))return null;c=nb;"selectionStart"in c&&Xd(c)?c={start:c.selectionStart,
end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset});return Pb&&Ob(Pb,c)?null:(Pb=c,a=R.getPooled(Eg.select,he,a,b),a.type="select",a.target=nb,lb(a),a)}function Ac(a){var b=a.keyCode;"charCode"in a?(a=a.charCode,0===a&&13===b&&(a=13)):a=b;10===a&&(a=13);return 32<=a||13===a?a:0}function q(a,b){0>ob||(a.current=ie[ob],ie[ob]=null,ob--)}function y(a,b,c){ob++;
ie[ob]=a.current;a.current=b}function pb(a,b){var c=a.type.contextTypes;if(!c)return Ca;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function N(a){a=a.childContextTypes;return null!==a&&void 0!==a}function Fg(a,b,c){if(B.current!==Ca)throw Error(k(168));y(B,b);y(G,c)}
function Gg(a,b,c){var d=a.stateNode;a=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in a))throw Error(k(108,na(b)||"Unknown",e));return M({},c,{},d)}function Bc(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||Ca;Ra=B.current;y(B,a);y(G,G.current);return!0}function Hg(a,b,c){var d=a.stateNode;if(!d)throw Error(k(169));c?(a=Gg(a,b,Ra),d.__reactInternalMemoizedMergedChildContext=a,q(G),q(B),y(B,a)):q(G);y(G,c)}function Cc(){switch(aj()){case Dc:return 99;
case Ig:return 98;case Jg:return 97;case Kg:return 96;case Lg:return 95;default:throw Error(k(332));}}function Mg(a){switch(a){case 99:return Dc;case 98:return Ig;case 97:return Jg;case 96:return Kg;case 95:return Lg;default:throw Error(k(332));}}function Da(a,b){a=Mg(a);return bj(a,b)}function Ng(a,b,c){a=Mg(a);return je(a,b,c)}function Og(a){null===qa?(qa=[a],Ec=je(Dc,Pg)):qa.push(a);return Qg}function ha(){if(null!==Ec){var a=Ec;Ec=null;Rg(a)}Pg()}function Pg(){if(!ke&&null!==qa){ke=!0;var a=0;
try{var b=qa;Da(99,function(){for(;a<b.length;a++){var c=b[a];do c=c(!0);while(null!==c)}});qa=null}catch(c){throw null!==qa&&(qa=qa.slice(a+1)),je(Dc,ha),c;}finally{ke=!1}}}function Fc(a,b,c){c/=10;return 1073741821-(((1073741821-a+b/10)/c|0)+1)*c}function aa(a,b){if(a&&a.defaultProps){b=M({},b);a=a.defaultProps;for(var c in a)void 0===b[c]&&(b[c]=a[c])}return b}function le(){Gc=qb=Hc=null}function me(a){var b=Ic.current;q(Ic);a.type._context._currentValue=b}function Sg(a,b){for(;null!==a;){var c=
a.alternate;if(a.childExpirationTime<b)a.childExpirationTime=b,null!==c&&c.childExpirationTime<b&&(c.childExpirationTime=b);else if(null!==c&&c.childExpirationTime<b)c.childExpirationTime=b;else break;a=a.return}}function rb(a,b){Hc=a;Gc=qb=null;a=a.dependencies;null!==a&&null!==a.firstContext&&(a.expirationTime>=b&&(ia=!0),a.firstContext=null)}function W(a,b){if(Gc!==a&&!1!==b&&0!==b){if("number"!==typeof b||1073741823===b)Gc=a,b=1073741823;b={context:a,observedBits:b,next:null};if(null===qb){if(null===
Hc)throw Error(k(308));qb=b;Hc.dependencies={expirationTime:0,firstContext:b,responders:null}}else qb=qb.next=b}return a._currentValue}function ne(a){a.updateQueue={baseState:a.memoizedState,baseQueue:null,shared:{pending:null},effects:null}}function oe(a,b){a=a.updateQueue;b.updateQueue===a&&(b.updateQueue={baseState:a.baseState,baseQueue:a.baseQueue,shared:a.shared,effects:a.effects})}function Ea(a,b){a={expirationTime:a,suspenseConfig:b,tag:Tg,payload:null,callback:null,next:null};return a.next=
a}function Fa(a,b){a=a.updateQueue;if(null!==a){a=a.shared;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}}function Ug(a,b){var c=a.alternate;null!==c&&oe(c,a);a=a.updateQueue;c=a.baseQueue;null===c?(a.baseQueue=b.next=b,b.next=b):(b.next=c.next,c.next=b)}function Qb(a,b,c,d){var e=a.updateQueue;Ga=!1;var f=e.baseQueue,g=e.shared.pending;if(null!==g){if(null!==f){var h=f.next;f.next=g.next;g.next=h}f=g;e.shared.pending=null;h=a.alternate;null!==h&&(h=h.updateQueue,null!==h&&
(h.baseQueue=g))}if(null!==f){h=f.next;var m=e.baseState,n=0,k=null,ba=null,l=null;if(null!==h){var p=h;do{g=p.expirationTime;if(g<d){var t={expirationTime:p.expirationTime,suspenseConfig:p.suspenseConfig,tag:p.tag,payload:p.payload,callback:p.callback,next:null};null===l?(ba=l=t,k=m):l=l.next=t;g>n&&(n=g)}else{null!==l&&(l=l.next={expirationTime:1073741823,suspenseConfig:p.suspenseConfig,tag:p.tag,payload:p.payload,callback:p.callback,next:null});Vg(g,p.suspenseConfig);a:{var q=a,r=p;g=b;t=c;switch(r.tag){case 1:q=
r.payload;if("function"===typeof q){m=q.call(t,m,g);break a}m=q;break a;case 3:q.effectTag=q.effectTag&-4097|64;case Tg:q=r.payload;g="function"===typeof q?q.call(t,m,g):q;if(null===g||void 0===g)break a;m=M({},m,g);break a;case Jc:Ga=!0}}null!==p.callback&&(a.effectTag|=32,g=e.effects,null===g?e.effects=[p]:g.push(p))}p=p.next;if(null===p||p===h)if(g=e.shared.pending,null===g)break;else p=f.next=g.next,g.next=h,e.baseQueue=f=g,e.shared.pending=null}while(1)}null===l?k=m:l.next=ba;e.baseState=k;e.baseQueue=
l;Kc(n);a.expirationTime=n;a.memoizedState=m}}function Wg(a,b,c){a=b.effects;b.effects=null;if(null!==a)for(b=0;b<a.length;b++){var d=a[b],e=d.callback;if(null!==e){d.callback=null;d=e;e=c;if("function"!==typeof d)throw Error(k(191,d));d.call(e)}}}function Lc(a,b,c,d){b=a.memoizedState;c=c(d,b);c=null===c||void 0===c?b:M({},b,c);a.memoizedState=c;0===a.expirationTime&&(a.updateQueue.baseState=c)}function Xg(a,b,c,d,e,f,g){a=a.stateNode;return"function"===typeof a.shouldComponentUpdate?a.shouldComponentUpdate(d,
f,g):b.prototype&&b.prototype.isPureReactComponent?!Ob(c,d)||!Ob(e,f):!0}function Yg(a,b,c){var d=!1,e=Ca;var f=b.contextType;"object"===typeof f&&null!==f?f=W(f):(e=N(b)?Ra:B.current,d=b.contextTypes,f=(d=null!==d&&void 0!==d)?pb(a,e):Ca);b=new b(c,f);a.memoizedState=null!==b.state&&void 0!==b.state?b.state:null;b.updater=Mc;a.stateNode=b;b._reactInternalFiber=a;d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=e,a.__reactInternalMemoizedMaskedChildContext=f);return b}function Zg(a,
b,c,d){a=b.state;"function"===typeof b.componentWillReceiveProps&&b.componentWillReceiveProps(c,d);"function"===typeof b.UNSAFE_componentWillReceiveProps&&b.UNSAFE_componentWillReceiveProps(c,d);b.state!==a&&Mc.enqueueReplaceState(b,b.state,null)}function pe(a,b,c,d){var e=a.stateNode;e.props=c;e.state=a.memoizedState;e.refs=$g;ne(a);var f=b.contextType;"object"===typeof f&&null!==f?e.context=W(f):(f=N(b)?Ra:B.current,e.context=pb(a,f));Qb(a,c,e,d);e.state=a.memoizedState;f=b.getDerivedStateFromProps;
"function"===typeof f&&(Lc(a,b,f,c),e.state=a.memoizedState);"function"===typeof b.getDerivedStateFromProps||"function"===typeof e.getSnapshotBeforeUpdate||"function"!==typeof e.UNSAFE_componentWillMount&&"function"!==typeof e.componentWillMount||(b=e.state,"function"===typeof e.componentWillMount&&e.componentWillMount(),"function"===typeof e.UNSAFE_componentWillMount&&e.UNSAFE_componentWillMount(),b!==e.state&&Mc.enqueueReplaceState(e,e.state,null),Qb(a,c,e,d),e.state=a.memoizedState);"function"===
typeof e.componentDidMount&&(a.effectTag|=4)}function Rb(a,b,c){a=c.ref;if(null!==a&&"function"!==typeof a&&"object"!==typeof a){if(c._owner){c=c._owner;if(c){if(1!==c.tag)throw Error(k(309));var d=c.stateNode}if(!d)throw Error(k(147,a));var e=""+a;if(null!==b&&null!==b.ref&&"function"===typeof b.ref&&b.ref._stringRef===e)return b.ref;b=function(a){var b=d.refs;b===$g&&(b=d.refs={});null===a?delete b[e]:b[e]=a};b._stringRef=e;return b}if("string"!==typeof a)throw Error(k(284));if(!c._owner)throw Error(k(290,
a));}return a}function Nc(a,b){if("textarea"!==a.type)throw Error(k(31,"[object Object]"===Object.prototype.toString.call(b)?"object with keys {"+Object.keys(b).join(", ")+"}":b,""));}function ah(a){function b(b,c){if(a){var d=b.lastEffect;null!==d?(d.nextEffect=c,b.lastEffect=c):b.firstEffect=b.lastEffect=c;c.nextEffect=null;c.effectTag=8}}function c(c,d){if(!a)return null;for(;null!==d;)b(c,d),d=d.sibling;return null}function d(a,b){for(a=new Map;null!==b;)null!==b.key?a.set(b.key,b):a.set(b.index,
b),b=b.sibling;return a}function e(a,b){a=Sa(a,b);a.index=0;a.sibling=null;return a}function f(b,c,d){b.index=d;if(!a)return c;d=b.alternate;if(null!==d)return d=d.index,d<c?(b.effectTag=2,c):d;b.effectTag=2;return c}function g(b){a&&null===b.alternate&&(b.effectTag=2);return b}function h(a,b,c,d){if(null===b||6!==b.tag)return b=qe(c,a.mode,d),b.return=a,b;b=e(b,c);b.return=a;return b}function m(a,b,c,d){if(null!==b&&b.elementType===c.type)return d=e(b,c.props),d.ref=Rb(a,b,c),d.return=a,d;d=Oc(c.type,
c.key,c.props,null,a.mode,d);d.ref=Rb(a,b,c);d.return=a;return d}function n(a,b,c,d){if(null===b||4!==b.tag||b.stateNode.containerInfo!==c.containerInfo||b.stateNode.implementation!==c.implementation)return b=re(c,a.mode,d),b.return=a,b;b=e(b,c.children||[]);b.return=a;return b}function l(a,b,c,d,f){if(null===b||7!==b.tag)return b=Ha(c,a.mode,d,f),b.return=a,b;b=e(b,c);b.return=a;return b}function ba(a,b,c){if("string"===typeof b||"number"===typeof b)return b=qe(""+b,a.mode,c),b.return=a,b;if("object"===
typeof b&&null!==b){switch(b.$$typeof){case Pc:return c=Oc(b.type,b.key,b.props,null,a.mode,c),c.ref=Rb(a,null,b),c.return=a,c;case gb:return b=re(b,a.mode,c),b.return=a,b}if(Qc(b)||zb(b))return b=Ha(b,a.mode,c,null),b.return=a,b;Nc(a,b)}return null}function p(a,b,c,d){var e=null!==b?b.key:null;if("string"===typeof c||"number"===typeof c)return null!==e?null:h(a,b,""+c,d);if("object"===typeof c&&null!==c){switch(c.$$typeof){case Pc:return c.key===e?c.type===Ma?l(a,b,c.props.children,d,e):m(a,b,c,
d):null;case gb:return c.key===e?n(a,b,c,d):null}if(Qc(c)||zb(c))return null!==e?null:l(a,b,c,d,null);Nc(a,c)}return null}function t(a,b,c,d,e){if("string"===typeof d||"number"===typeof d)return a=a.get(c)||null,h(b,a,""+d,e);if("object"===typeof d&&null!==d){switch(d.$$typeof){case Pc:return a=a.get(null===d.key?c:d.key)||null,d.type===Ma?l(b,a,d.props.children,e,d.key):m(b,a,d,e);case gb:return a=a.get(null===d.key?c:d.key)||null,n(b,a,d,e)}if(Qc(d)||zb(d))return a=a.get(c)||null,l(b,a,d,e,null);
Nc(b,d)}return null}function q(e,g,h,m){for(var n=null,k=null,l=g,r=g=0,C=null;null!==l&&r<h.length;r++){l.index>r?(C=l,l=null):C=l.sibling;var O=p(e,l,h[r],m);if(null===O){null===l&&(l=C);break}a&&l&&null===O.alternate&&b(e,l);g=f(O,g,r);null===k?n=O:k.sibling=O;k=O;l=C}if(r===h.length)return c(e,l),n;if(null===l){for(;r<h.length;r++)l=ba(e,h[r],m),null!==l&&(g=f(l,g,r),null===k?n=l:k.sibling=l,k=l);return n}for(l=d(e,l);r<h.length;r++)C=t(l,e,r,h[r],m),null!==C&&(a&&null!==C.alternate&&l.delete(null===
C.key?r:C.key),g=f(C,g,r),null===k?n=C:k.sibling=C,k=C);a&&l.forEach(function(a){return b(e,a)});return n}function w(e,g,h,n){var m=zb(h);if("function"!==typeof m)throw Error(k(150));h=m.call(h);if(null==h)throw Error(k(151));for(var l=m=null,r=g,C=g=0,O=null,v=h.next();null!==r&&!v.done;C++,v=h.next()){r.index>C?(O=r,r=null):O=r.sibling;var q=p(e,r,v.value,n);if(null===q){null===r&&(r=O);break}a&&r&&null===q.alternate&&b(e,r);g=f(q,g,C);null===l?m=q:l.sibling=q;l=q;r=O}if(v.done)return c(e,r),m;
if(null===r){for(;!v.done;C++,v=h.next())v=ba(e,v.value,n),null!==v&&(g=f(v,g,C),null===l?m=v:l.sibling=v,l=v);return m}for(r=d(e,r);!v.done;C++,v=h.next())v=t(r,e,C,v.value,n),null!==v&&(a&&null!==v.alternate&&r.delete(null===v.key?C:v.key),g=f(v,g,C),null===l?m=v:l.sibling=v,l=v);a&&r.forEach(function(a){return b(e,a)});return m}return function(a,d,f,h){var m="object"===typeof f&&null!==f&&f.type===Ma&&null===f.key;m&&(f=f.props.children);var n="object"===typeof f&&null!==f;if(n)switch(f.$$typeof){case Pc:a:{n=
f.key;for(m=d;null!==m;){if(m.key===n){switch(m.tag){case 7:if(f.type===Ma){c(a,m.sibling);d=e(m,f.props.children);d.return=a;a=d;break a}break;default:if(m.elementType===f.type){c(a,m.sibling);d=e(m,f.props);d.ref=Rb(a,m,f);d.return=a;a=d;break a}}c(a,m);break}else b(a,m);m=m.sibling}f.type===Ma?(d=Ha(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=Oc(f.type,f.key,f.props,null,a.mode,h),h.ref=Rb(a,d,f),h.return=a,a=h)}return g(a);case gb:a:{for(m=f.key;null!==d;){if(d.key===m)if(4===d.tag&&d.stateNode.containerInfo===
f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=re(f,a.mode,h);d.return=a;a=d}return g(a)}if("string"===typeof f||"number"===typeof f)return f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):(c(a,d),d=qe(f,a.mode,h),d.return=a,a=d),g(a);if(Qc(f))return q(a,d,f,h);if(zb(f))return w(a,d,f,h);n&&Nc(a,f);if("undefined"===typeof f&&!m)switch(a.tag){case 1:case 0:throw a=
a.type,Error(k(152,a.displayName||a.name||"Component"));}return c(a,d)}}function Ta(a){if(a===Sb)throw Error(k(174));return a}function se(a,b){y(Tb,b);y(Ub,a);y(ja,Sb);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:Hd(null,"");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=Hd(b,a)}q(ja);y(ja,b)}function tb(a){q(ja);q(Ub);q(Tb)}function bh(a){Ta(Tb.current);var b=Ta(ja.current);var c=Hd(b,a.type);b!==c&&(y(Ub,a),y(ja,c))}function te(a){Ub.current===
a&&(q(ja),q(Ub))}function Rc(a){for(var b=a;null!==b;){if(13===b.tag){var c=b.memoizedState;if(null!==c&&(c=c.dehydrated,null===c||c.data===$d||c.data===Zd))return b}else if(19===b.tag&&void 0!==b.memoizedProps.revealOrder){if(0!==(b.effectTag&64))return b}else if(null!==b.child){b.child.return=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}return null}function ue(a,b){return{responder:a,props:b}}
function S(){throw Error(k(321));}function ve(a,b){if(null===b)return!1;for(var c=0;c<b.length&&c<a.length;c++)if(!Qa(a[c],b[c]))return!1;return!0}function we(a,b,c,d,e,f){Ia=f;z=b;b.memoizedState=null;b.updateQueue=null;b.expirationTime=0;Sc.current=null===a||null===a.memoizedState?dj:ej;a=c(d,e);if(b.expirationTime===Ia){f=0;do{b.expirationTime=0;if(!(25>f))throw Error(k(301));f+=1;J=K=null;b.updateQueue=null;Sc.current=fj;a=c(d,e)}while(b.expirationTime===Ia)}Sc.current=Tc;b=null!==K&&null!==K.next;
Ia=0;J=K=z=null;Uc=!1;if(b)throw Error(k(300));return a}function ub(){var a={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};null===J?z.memoizedState=J=a:J=J.next=a;return J}function vb(){if(null===K){var a=z.alternate;a=null!==a?a.memoizedState:null}else a=K.next;var b=null===J?z.memoizedState:J.next;if(null!==b)J=b,K=a;else{if(null===a)throw Error(k(310));K=a;a={memoizedState:K.memoizedState,baseState:K.baseState,baseQueue:K.baseQueue,queue:K.queue,next:null};null===J?z.memoizedState=
J=a:J=J.next=a}return J}function Ua(a,b){return"function"===typeof b?b(a):b}function Vc(a,b,c){b=vb();c=b.queue;if(null===c)throw Error(k(311));c.lastRenderedReducer=a;var d=K,e=d.baseQueue,f=c.pending;if(null!==f){if(null!==e){var g=e.next;e.next=f.next;f.next=g}d.baseQueue=e=f;c.pending=null}if(null!==e){e=e.next;d=d.baseState;var h=g=f=null,m=e;do{var n=m.expirationTime;if(n<Ia){var l={expirationTime:m.expirationTime,suspenseConfig:m.suspenseConfig,action:m.action,eagerReducer:m.eagerReducer,eagerState:m.eagerState,
next:null};null===h?(g=h=l,f=d):h=h.next=l;n>z.expirationTime&&(z.expirationTime=n,Kc(n))}else null!==h&&(h=h.next={expirationTime:1073741823,suspenseConfig:m.suspenseConfig,action:m.action,eagerReducer:m.eagerReducer,eagerState:m.eagerState,next:null}),Vg(n,m.suspenseConfig),d=m.eagerReducer===a?m.eagerState:a(d,m.action);m=m.next}while(null!==m&&m!==e);null===h?f=d:h.next=g;Qa(d,b.memoizedState)||(ia=!0);b.memoizedState=d;b.baseState=f;b.baseQueue=h;c.lastRenderedState=d}return[b.memoizedState,
c.dispatch]}function Wc(a,b,c){b=vb();c=b.queue;if(null===c)throw Error(k(311));c.lastRenderedReducer=a;var d=c.dispatch,e=c.pending,f=b.memoizedState;if(null!==e){c.pending=null;var g=e=e.next;do f=a(f,g.action),g=g.next;while(g!==e);Qa(f,b.memoizedState)||(ia=!0);b.memoizedState=f;null===b.baseQueue&&(b.baseState=f);c.lastRenderedState=f}return[f,d]}function xe(a){var b=ub();"function"===typeof a&&(a=a());b.memoizedState=b.baseState=a;a=b.queue={pending:null,dispatch:null,lastRenderedReducer:Ua,
lastRenderedState:a};a=a.dispatch=ch.bind(null,z,a);return[b.memoizedState,a]}function ye(a,b,c,d){a={tag:a,create:b,destroy:c,deps:d,next:null};b=z.updateQueue;null===b?(b={lastEffect:null},z.updateQueue=b,b.lastEffect=a.next=a):(c=b.lastEffect,null===c?b.lastEffect=a.next=a:(d=c.next,c.next=a,a.next=d,b.lastEffect=a));return a}function dh(a){return vb().memoizedState}function ze(a,b,c,d){var e=ub();z.effectTag|=a;e.memoizedState=ye(1|b,c,void 0,void 0===d?null:d)}function Ae(a,b,c,d){var e=vb();
d=void 0===d?null:d;var f=void 0;if(null!==K){var g=K.memoizedState;f=g.destroy;if(null!==d&&ve(d,g.deps)){ye(b,c,f,d);return}}z.effectTag|=a;e.memoizedState=ye(1|b,c,f,d)}function eh(a,b){return ze(516,4,a,b)}function Xc(a,b){return Ae(516,4,a,b)}function fh(a,b){return Ae(4,2,a,b)}function gh(a,b){if("function"===typeof b)return a=a(),b(a),function(){b(null)};if(null!==b&&void 0!==b)return a=a(),b.current=a,function(){b.current=null}}function hh(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;
return Ae(4,2,gh.bind(null,b,a),c)}function Be(a,b){}function ih(a,b){ub().memoizedState=[a,void 0===b?null:b];return a}function Yc(a,b){var c=vb();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&ve(b,d[1]))return d[0];c.memoizedState=[a,b];return a}function jh(a,b){var c=vb();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&ve(b,d[1]))return d[0];a=a();c.memoizedState=[a,b];return a}function Ce(a,b,c){var d=Cc();Da(98>d?98:d,function(){a(!0)});Da(97<d?97:d,function(){var d=
X.suspense;X.suspense=void 0===b?null:b;try{a(!1),c()}finally{X.suspense=d}})}function ch(a,b,c){var d=ka(),e=Vb.suspense;d=Va(d,a,e);e={expirationTime:d,suspenseConfig:e,action:c,eagerReducer:null,eagerState:null,next:null};var f=b.pending;null===f?e.next=e:(e.next=f.next,f.next=e);b.pending=e;f=a.alternate;if(a===z||null!==f&&f===z)Uc=!0,e.expirationTime=Ia,z.expirationTime=Ia;else{if(0===a.expirationTime&&(null===f||0===f.expirationTime)&&(f=b.lastRenderedReducer,null!==f))try{var g=b.lastRenderedState,
h=f(g,c);e.eagerReducer=f;e.eagerState=h;if(Qa(h,g))return}catch(m){}finally{}Ja(a,d)}}function kh(a,b){var c=la(5,null,null,0);c.elementType="DELETED";c.type="DELETED";c.stateNode=b;c.return=a;c.effectTag=8;null!==a.lastEffect?(a.lastEffect.nextEffect=c,a.lastEffect=c):a.firstEffect=a.lastEffect=c}function lh(a,b){switch(a.tag){case 5:var c=a.type;b=1!==b.nodeType||c.toLowerCase()!==b.nodeName.toLowerCase()?null:b;return null!==b?(a.stateNode=b,!0):!1;case 6:return b=""===a.pendingProps||3!==b.nodeType?
null:b,null!==b?(a.stateNode=b,!0):!1;case 13:return!1;default:return!1}}function De(a){if(Wa){var b=Ka;if(b){var c=b;if(!lh(a,b)){b=kb(c.nextSibling);if(!b||!lh(a,b)){a.effectTag=a.effectTag&-1025|2;Wa=!1;ra=a;return}kh(ra,c)}ra=a;Ka=kb(b.firstChild)}else a.effectTag=a.effectTag&-1025|2,Wa=!1,ra=a}}function mh(a){for(a=a.return;null!==a&&5!==a.tag&&3!==a.tag&&13!==a.tag;)a=a.return;ra=a}function Zc(a){if(a!==ra)return!1;if(!Wa)return mh(a),Wa=!0,!1;var b=a.type;if(5!==a.tag||"head"!==b&&"body"!==
b&&!Yd(b,a.memoizedProps))for(b=Ka;b;)kh(a,b),b=kb(b.nextSibling);mh(a);if(13===a.tag){a=a.memoizedState;a=null!==a?a.dehydrated:null;if(!a)throw Error(k(317));a:{a=a.nextSibling;for(b=0;a;){if(8===a.nodeType){var c=a.data;if(c===og){if(0===b){Ka=kb(a.nextSibling);break a}b--}else c!==ng&&c!==Zd&&c!==$d||b++}a=a.nextSibling}Ka=null}}else Ka=ra?kb(a.stateNode.nextSibling):null;return!0}function Ee(){Ka=ra=null;Wa=!1}function T(a,b,c,d){b.child=null===a?Fe(b,null,c,d):wb(b,a.child,c,d)}function nh(a,
b,c,d,e){c=c.render;var f=b.ref;rb(b,e);d=we(a,b,c,d,f,e);if(null!==a&&!ia)return b.updateQueue=a.updateQueue,b.effectTag&=-517,a.expirationTime<=e&&(a.expirationTime=0),sa(a,b,e);b.effectTag|=1;T(a,b,d,e);return b.child}function oh(a,b,c,d,e,f){if(null===a){var g=c.type;if("function"===typeof g&&!Ge(g)&&void 0===g.defaultProps&&null===c.compare&&void 0===c.defaultProps)return b.tag=15,b.type=g,ph(a,b,g,d,e,f);a=Oc(c.type,null,d,null,b.mode,f);a.ref=b.ref;a.return=b;return b.child=a}g=a.child;if(e<
f&&(e=g.memoizedProps,c=c.compare,c=null!==c?c:Ob,c(e,d)&&a.ref===b.ref))return sa(a,b,f);b.effectTag|=1;a=Sa(g,d);a.ref=b.ref;a.return=b;return b.child=a}function ph(a,b,c,d,e,f){return null!==a&&Ob(a.memoizedProps,d)&&a.ref===b.ref&&(ia=!1,e<f)?(b.expirationTime=a.expirationTime,sa(a,b,f)):He(a,b,c,d,f)}function qh(a,b){var c=b.ref;if(null===a&&null!==c||null!==a&&a.ref!==c)b.effectTag|=128}function He(a,b,c,d,e){var f=N(c)?Ra:B.current;f=pb(b,f);rb(b,e);c=we(a,b,c,d,f,e);if(null!==a&&!ia)return b.updateQueue=
a.updateQueue,b.effectTag&=-517,a.expirationTime<=e&&(a.expirationTime=0),sa(a,b,e);b.effectTag|=1;T(a,b,c,e);return b.child}function rh(a,b,c,d,e){if(N(c)){var f=!0;Bc(b)}else f=!1;rb(b,e);if(null===b.stateNode)null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2),Yg(b,c,d),pe(b,c,d,e),d=!0;else if(null===a){var g=b.stateNode,h=b.memoizedProps;g.props=h;var m=g.context,n=c.contextType;"object"===typeof n&&null!==n?n=W(n):(n=N(c)?Ra:B.current,n=pb(b,n));var l=c.getDerivedStateFromProps,k="function"===
typeof l||"function"===typeof g.getSnapshotBeforeUpdate;k||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||m!==n)&&Zg(b,g,d,n);Ga=!1;var p=b.memoizedState;g.state=p;Qb(b,d,g,e);m=b.memoizedState;h!==d||p!==m||G.current||Ga?("function"===typeof l&&(Lc(b,c,l,d),m=b.memoizedState),(h=Ga||Xg(b,c,h,d,p,m,n))?(k||"function"!==typeof g.UNSAFE_componentWillMount&&"function"!==typeof g.componentWillMount||("function"===typeof g.componentWillMount&&
g.componentWillMount(),"function"===typeof g.UNSAFE_componentWillMount&&g.UNSAFE_componentWillMount()),"function"===typeof g.componentDidMount&&(b.effectTag|=4)):("function"===typeof g.componentDidMount&&(b.effectTag|=4),b.memoizedProps=d,b.memoizedState=m),g.props=d,g.state=m,g.context=n,d=h):("function"===typeof g.componentDidMount&&(b.effectTag|=4),d=!1)}else g=b.stateNode,oe(a,b),h=b.memoizedProps,g.props=b.type===b.elementType?h:aa(b.type,h),m=g.context,n=c.contextType,"object"===typeof n&&null!==
n?n=W(n):(n=N(c)?Ra:B.current,n=pb(b,n)),l=c.getDerivedStateFromProps,(k="function"===typeof l||"function"===typeof g.getSnapshotBeforeUpdate)||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||m!==n)&&Zg(b,g,d,n),Ga=!1,m=b.memoizedState,g.state=m,Qb(b,d,g,e),p=b.memoizedState,h!==d||m!==p||G.current||Ga?("function"===typeof l&&(Lc(b,c,l,d),p=b.memoizedState),(l=Ga||Xg(b,c,h,d,m,p,n))?(k||"function"!==typeof g.UNSAFE_componentWillUpdate&&
"function"!==typeof g.componentWillUpdate||("function"===typeof g.componentWillUpdate&&g.componentWillUpdate(d,p,n),"function"===typeof g.UNSAFE_componentWillUpdate&&g.UNSAFE_componentWillUpdate(d,p,n)),"function"===typeof g.componentDidUpdate&&(b.effectTag|=4),"function"===typeof g.getSnapshotBeforeUpdate&&(b.effectTag|=256)):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&m===a.memoizedState||(b.effectTag|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&m===
a.memoizedState||(b.effectTag|=256),b.memoizedProps=d,b.memoizedState=p),g.props=d,g.state=p,g.context=n,d=l):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&m===a.memoizedState||(b.effectTag|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&m===a.memoizedState||(b.effectTag|=256),d=!1);return Ie(a,b,c,d,f,e)}function Ie(a,b,c,d,e,f){qh(a,b);var g=0!==(b.effectTag&64);if(!d&&!g)return e&&Hg(b,c,!1),sa(a,b,f);d=b.stateNode;gj.current=b;var h=g&&"function"!==typeof c.getDerivedStateFromError?
null:d.render();b.effectTag|=1;null!==a&&g?(b.child=wb(b,a.child,null,f),b.child=wb(b,null,h,f)):T(a,b,h,f);b.memoizedState=d.state;e&&Hg(b,c,!0);return b.child}function sh(a){var b=a.stateNode;b.pendingContext?Fg(a,b.pendingContext,b.pendingContext!==b.context):b.context&&Fg(a,b.context,!1);se(a,b.containerInfo)}function th(a,b,c){var d=b.mode,e=b.pendingProps,f=D.current,g=!1,h;(h=0!==(b.effectTag&64))||(h=0!==(f&2)&&(null===a||null!==a.memoizedState));h?(g=!0,b.effectTag&=-65):null!==a&&null===
a.memoizedState||void 0===e.fallback||!0===e.unstable_avoidThisFallback||(f|=1);y(D,f&1);if(null===a){void 0!==e.fallback&&De(b);if(g){g=e.fallback;e=Ha(null,d,0,null);e.return=b;if(0===(b.mode&2))for(a=null!==b.memoizedState?b.child.child:b.child,e.child=a;null!==a;)a.return=e,a=a.sibling;c=Ha(g,d,c,null);c.return=b;e.sibling=c;b.memoizedState=Je;b.child=e;return c}d=e.children;b.memoizedState=null;return b.child=Fe(b,null,d,c)}if(null!==a.memoizedState){a=a.child;d=a.sibling;if(g){e=e.fallback;
c=Sa(a,a.pendingProps);c.return=b;if(0===(b.mode&2)&&(g=null!==b.memoizedState?b.child.child:b.child,g!==a.child))for(c.child=g;null!==g;)g.return=c,g=g.sibling;d=Sa(d,e);d.return=b;c.sibling=d;c.childExpirationTime=0;b.memoizedState=Je;b.child=c;return d}c=wb(b,a.child,e.children,c);b.memoizedState=null;return b.child=c}a=a.child;if(g){g=e.fallback;e=Ha(null,d,0,null);e.return=b;e.child=a;null!==a&&(a.return=e);if(0===(b.mode&2))for(a=null!==b.memoizedState?b.child.child:b.child,e.child=a;null!==
a;)a.return=e,a=a.sibling;c=Ha(g,d,c,null);c.return=b;e.sibling=c;c.effectTag|=2;e.childExpirationTime=0;b.memoizedState=Je;b.child=e;return c}b.memoizedState=null;return b.child=wb(b,a,e.children,c)}function uh(a,b){a.expirationTime<b&&(a.expirationTime=b);var c=a.alternate;null!==c&&c.expirationTime<b&&(c.expirationTime=b);Sg(a.return,b)}function Ke(a,b,c,d,e,f){var g=a.memoizedState;null===g?a.memoizedState={isBackwards:b,rendering:null,renderingStartTime:0,last:d,tail:c,tailExpiration:0,tailMode:e,
lastEffect:f}:(g.isBackwards=b,g.rendering=null,g.renderingStartTime=0,g.last=d,g.tail=c,g.tailExpiration=0,g.tailMode=e,g.lastEffect=f)}function vh(a,b,c){var d=b.pendingProps,e=d.revealOrder,f=d.tail;T(a,b,d.children,c);d=D.current;if(0!==(d&2))d=d&1|2,b.effectTag|=64;else{if(null!==a&&0!==(a.effectTag&64))a:for(a=b.child;null!==a;){if(13===a.tag)null!==a.memoizedState&&uh(a,c);else if(19===a.tag)uh(a,c);else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===b)break a;for(;null===a.sibling;){if(null===
a.return||a.return===b)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}d&=1}y(D,d);if(0===(b.mode&2))b.memoizedState=null;else switch(e){case "forwards":c=b.child;for(e=null;null!==c;)a=c.alternate,null!==a&&null===Rc(a)&&(e=c),c=c.sibling;c=e;null===c?(e=b.child,b.child=null):(e=c.sibling,c.sibling=null);Ke(b,!1,e,c,f,b.lastEffect);break;case "backwards":c=null;e=b.child;for(b.child=null;null!==e;){a=e.alternate;if(null!==a&&null===Rc(a)){b.child=e;break}a=e.sibling;e.sibling=c;c=e;e=a}Ke(b,
!0,c,null,f,b.lastEffect);break;case "together":Ke(b,!1,null,null,void 0,b.lastEffect);break;default:b.memoizedState=null}return b.child}function sa(a,b,c){null!==a&&(b.dependencies=a.dependencies);var d=b.expirationTime;0!==d&&Kc(d);if(b.childExpirationTime<c)return null;if(null!==a&&b.child!==a.child)throw Error(k(153));if(null!==b.child){a=b.child;c=Sa(a,a.pendingProps);b.child=c;for(c.return=b;null!==a.sibling;)a=a.sibling,c=c.sibling=Sa(a,a.pendingProps),c.return=b;c.sibling=null}return b.child}
function $c(a,b){switch(a.tailMode){case "hidden":b=a.tail;for(var c=null;null!==b;)null!==b.alternate&&(c=b),b=b.sibling;null===c?a.tail=null:c.sibling=null;break;case "collapsed":c=a.tail;for(var d=null;null!==c;)null!==c.alternate&&(d=c),c=c.sibling;null===d?b||null===a.tail?a.tail=null:a.tail.sibling=null:d.sibling=null}}function hj(a,b,c){var d=b.pendingProps;switch(b.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return null;case 1:return N(b.type)&&(q(G),q(B)),
null;case 3:return tb(),q(G),q(B),c=b.stateNode,c.pendingContext&&(c.context=c.pendingContext,c.pendingContext=null),null!==a&&null!==a.child||!Zc(b)||(b.effectTag|=4),wh(b),null;case 5:te(b);c=Ta(Tb.current);var e=b.type;if(null!==a&&null!=b.stateNode)ij(a,b,e,d,c),a.ref!==b.ref&&(b.effectTag|=128);else{if(!d){if(null===b.stateNode)throw Error(k(166));return null}a=Ta(ja.current);if(Zc(b)){d=b.stateNode;e=b.type;var f=b.memoizedProps;d[Aa]=b;d[vc]=f;switch(e){case "iframe":case "object":case "embed":w("load",
d);break;case "video":case "audio":for(a=0;a<Db.length;a++)w(Db[a],d);break;case "source":w("error",d);break;case "img":case "image":case "link":w("error",d);w("load",d);break;case "form":w("reset",d);w("submit",d);break;case "details":w("toggle",d);break;case "input":Hf(d,f);w("invalid",d);oa(c,"onChange");break;case "select":d._wrapperState={wasMultiple:!!f.multiple};w("invalid",d);oa(c,"onChange");break;case "textarea":Kf(d,f),w("invalid",d),oa(c,"onChange")}Ud(e,f);a=null;for(var g in f)if(f.hasOwnProperty(g)){var h=
f[g];"children"===g?"string"===typeof h?d.textContent!==h&&(a=["children",h]):"number"===typeof h&&d.textContent!==""+h&&(a=["children",""+h]):db.hasOwnProperty(g)&&null!=h&&oa(c,g)}switch(e){case "input":mc(d);Jf(d,f,!0);break;case "textarea":mc(d);Mf(d);break;case "select":case "option":break;default:"function"===typeof f.onClick&&(d.onclick=uc)}c=a;b.updateQueue=c;null!==c&&(b.effectTag|=4)}else{g=9===c.nodeType?c:c.ownerDocument;"http://www.w3.org/1999/xhtml"===a&&(a=Nf(e));"http://www.w3.org/1999/xhtml"===
a?"script"===e?(a=g.createElement("div"),a.innerHTML="<script>\x3c/script>",a=a.removeChild(a.firstChild)):"string"===typeof d.is?a=g.createElement(e,{is:d.is}):(a=g.createElement(e),"select"===e&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,e);a[Aa]=b;a[vc]=d;jj(a,b,!1,!1);b.stateNode=a;g=Vd(e,d);switch(e){case "iframe":case "object":case "embed":w("load",a);h=d;break;case "video":case "audio":for(h=0;h<Db.length;h++)w(Db[h],a);h=d;break;case "source":w("error",a);
h=d;break;case "img":case "image":case "link":w("error",a);w("load",a);h=d;break;case "form":w("reset",a);w("submit",a);h=d;break;case "details":w("toggle",a);h=d;break;case "input":Hf(a,d);h=Cd(a,d);w("invalid",a);oa(c,"onChange");break;case "option":h=Fd(a,d);break;case "select":a._wrapperState={wasMultiple:!!d.multiple};h=M({},d,{value:void 0});w("invalid",a);oa(c,"onChange");break;case "textarea":Kf(a,d);h=Gd(a,d);w("invalid",a);oa(c,"onChange");break;default:h=d}Ud(e,h);var m=h;for(f in m)if(m.hasOwnProperty(f)){var n=
m[f];"style"===f?gg(a,n):"dangerouslySetInnerHTML"===f?(n=n?n.__html:void 0,null!=n&&xh(a,n)):"children"===f?"string"===typeof n?("textarea"!==e||""!==n)&&Wb(a,n):"number"===typeof n&&Wb(a,""+n):"suppressContentEditableWarning"!==f&&"suppressHydrationWarning"!==f&&"autoFocus"!==f&&(db.hasOwnProperty(f)?null!=n&&oa(c,f):null!=n&&xd(a,f,n,g))}switch(e){case "input":mc(a);Jf(a,d,!1);break;case "textarea":mc(a);Mf(a);break;case "option":null!=d.value&&a.setAttribute("value",""+va(d.value));break;case "select":a.multiple=
!!d.multiple;c=d.value;null!=c?hb(a,!!d.multiple,c,!1):null!=d.defaultValue&&hb(a,!!d.multiple,d.defaultValue,!0);break;default:"function"===typeof h.onClick&&(a.onclick=uc)}lg(e,d)&&(b.effectTag|=4)}null!==b.ref&&(b.effectTag|=128)}return null;case 6:if(a&&null!=b.stateNode)kj(a,b,a.memoizedProps,d);else{if("string"!==typeof d&&null===b.stateNode)throw Error(k(166));c=Ta(Tb.current);Ta(ja.current);Zc(b)?(c=b.stateNode,d=b.memoizedProps,c[Aa]=b,c.nodeValue!==d&&(b.effectTag|=4)):(c=(9===c.nodeType?
c:c.ownerDocument).createTextNode(d),c[Aa]=b,b.stateNode=c)}return null;case 13:q(D);d=b.memoizedState;if(0!==(b.effectTag&64))return b.expirationTime=c,b;c=null!==d;d=!1;null===a?void 0!==b.memoizedProps.fallback&&Zc(b):(e=a.memoizedState,d=null!==e,c||null===e||(e=a.child.sibling,null!==e&&(f=b.firstEffect,null!==f?(b.firstEffect=e,e.nextEffect=f):(b.firstEffect=b.lastEffect=e,e.nextEffect=null),e.effectTag=8)));if(c&&!d&&0!==(b.mode&2))if(null===a&&!0!==b.memoizedProps.unstable_avoidThisFallback||
0!==(D.current&1))F===Xa&&(F=ad);else{if(F===Xa||F===ad)F=bd;0!==Xb&&null!==U&&(Ya(U,P),yh(U,Xb))}if(c||d)b.effectTag|=4;return null;case 4:return tb(),wh(b),null;case 10:return me(b),null;case 17:return N(b.type)&&(q(G),q(B)),null;case 19:q(D);d=b.memoizedState;if(null===d)return null;e=0!==(b.effectTag&64);f=d.rendering;if(null===f)if(e)$c(d,!1);else{if(F!==Xa||null!==a&&0!==(a.effectTag&64))for(f=b.child;null!==f;){a=Rc(f);if(null!==a){b.effectTag|=64;$c(d,!1);e=a.updateQueue;null!==e&&(b.updateQueue=
e,b.effectTag|=4);null===d.lastEffect&&(b.firstEffect=null);b.lastEffect=d.lastEffect;for(d=b.child;null!==d;)e=d,f=c,e.effectTag&=2,e.nextEffect=null,e.firstEffect=null,e.lastEffect=null,a=e.alternate,null===a?(e.childExpirationTime=0,e.expirationTime=f,e.child=null,e.memoizedProps=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null):(e.childExpirationTime=a.childExpirationTime,e.expirationTime=a.expirationTime,e.child=a.child,e.memoizedProps=a.memoizedProps,e.memoizedState=a.memoizedState,
e.updateQueue=a.updateQueue,f=a.dependencies,e.dependencies=null===f?null:{expirationTime:f.expirationTime,firstContext:f.firstContext,responders:f.responders}),d=d.sibling;y(D,D.current&1|2);return b.child}f=f.sibling}}else{if(!e)if(a=Rc(f),null!==a){if(b.effectTag|=64,e=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.effectTag|=4),$c(d,!0),null===d.tail&&"hidden"===d.tailMode&&!f.alternate)return b=b.lastEffect=d.lastEffect,null!==b&&(b.nextEffect=null),null}else 2*Y()-d.renderingStartTime>d.tailExpiration&&
1<c&&(b.effectTag|=64,e=!0,$c(d,!1),b.expirationTime=b.childExpirationTime=c-1);d.isBackwards?(f.sibling=b.child,b.child=f):(c=d.last,null!==c?c.sibling=f:b.child=f,d.last=f)}return null!==d.tail?(0===d.tailExpiration&&(d.tailExpiration=Y()+500),c=d.tail,d.rendering=c,d.tail=c.sibling,d.lastEffect=b.lastEffect,d.renderingStartTime=Y(),c.sibling=null,b=D.current,y(D,e?b&1|2:b&1),c):null}throw Error(k(156,b.tag));}function lj(a,b){switch(a.tag){case 1:return N(a.type)&&(q(G),q(B)),b=a.effectTag,b&4096?
(a.effectTag=b&-4097|64,a):null;case 3:tb();q(G);q(B);b=a.effectTag;if(0!==(b&64))throw Error(k(285));a.effectTag=b&-4097|64;return a;case 5:return te(a),null;case 13:return q(D),b=a.effectTag,b&4096?(a.effectTag=b&-4097|64,a):null;case 19:return q(D),null;case 4:return tb(),null;case 10:return me(a),null;default:return null}}function Le(a,b){return{value:a,source:b,stack:Bd(b)}}function Me(a,b){var c=b.source,d=b.stack;null===d&&null!==c&&(d=Bd(c));null!==c&&na(c.type);b=b.value;null!==a&&1===a.tag&&
na(a.type);try{console.error(b)}catch(e){setTimeout(function(){throw e;})}}function mj(a,b){try{b.props=a.memoizedProps,b.state=a.memoizedState,b.componentWillUnmount()}catch(c){Za(a,c)}}function zh(a){var b=a.ref;if(null!==b)if("function"===typeof b)try{b(null)}catch(c){Za(a,c)}else b.current=null}function nj(a,b){switch(b.tag){case 0:case 11:case 15:case 22:return;case 1:if(b.effectTag&256&&null!==a){var c=a.memoizedProps,d=a.memoizedState;a=b.stateNode;b=a.getSnapshotBeforeUpdate(b.elementType===
b.type?c:aa(b.type,c),d);a.__reactInternalSnapshotBeforeUpdate=b}return;case 3:case 5:case 6:case 4:case 17:return}throw Error(k(163));}function Ah(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.destroy;c.destroy=void 0;void 0!==d&&d()}c=c.next}while(c!==b)}}function Bh(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.create;c.destroy=d()}c=c.next}while(c!==b)}}function oj(a,b,c,d){switch(c.tag){case 0:case 11:case 15:case 22:Bh(3,
c);return;case 1:a=c.stateNode;c.effectTag&4&&(null===b?a.componentDidMount():(d=c.elementType===c.type?b.memoizedProps:aa(c.type,b.memoizedProps),a.componentDidUpdate(d,b.memoizedState,a.__reactInternalSnapshotBeforeUpdate)));b=c.updateQueue;null!==b&&Wg(c,b,a);return;case 3:b=c.updateQueue;if(null!==b){a=null;if(null!==c.child)switch(c.child.tag){case 5:a=c.child.stateNode;break;case 1:a=c.child.stateNode}Wg(c,b,a)}return;case 5:a=c.stateNode;null===b&&c.effectTag&4&&lg(c.type,c.memoizedProps)&&
a.focus();return;case 6:return;case 4:return;case 12:return;case 13:null===c.memoizedState&&(c=c.alternate,null!==c&&(c=c.memoizedState,null!==c&&(c=c.dehydrated,null!==c&&bg(c))));return;case 19:case 17:case 20:case 21:return}throw Error(k(163));}function Ch(a,b,c){"function"===typeof Ne&&Ne(b);switch(b.tag){case 0:case 11:case 14:case 15:case 22:a=b.updateQueue;if(null!==a&&(a=a.lastEffect,null!==a)){var d=a.next;Da(97<c?97:c,function(){var a=d;do{var c=a.destroy;if(void 0!==c){var g=b;try{c()}catch(h){Za(g,
h)}}a=a.next}while(a!==d)})}break;case 1:zh(b);c=b.stateNode;"function"===typeof c.componentWillUnmount&&mj(b,c);break;case 5:zh(b);break;case 4:Dh(a,b,c)}}function Eh(a){var b=a.alternate;a.return=null;a.child=null;a.memoizedState=null;a.updateQueue=null;a.dependencies=null;a.alternate=null;a.firstEffect=null;a.lastEffect=null;a.pendingProps=null;a.memoizedProps=null;a.stateNode=null;null!==b&&Eh(b)}function Fh(a){return 5===a.tag||3===a.tag||4===a.tag}function Gh(a){a:{for(var b=a.return;null!==
b;){if(Fh(b)){var c=b;break a}b=b.return}throw Error(k(160));}b=c.stateNode;switch(c.tag){case 5:var d=!1;break;case 3:b=b.containerInfo;d=!0;break;case 4:b=b.containerInfo;d=!0;break;default:throw Error(k(161));}c.effectTag&16&&(Wb(b,""),c.effectTag&=-17);a:b:for(c=a;;){for(;null===c.sibling;){if(null===c.return||Fh(c.return)){c=null;break a}c=c.return}c.sibling.return=c.return;for(c=c.sibling;5!==c.tag&&6!==c.tag&&18!==c.tag;){if(c.effectTag&2)continue b;if(null===c.child||4===c.tag)continue b;
else c.child.return=c,c=c.child}if(!(c.effectTag&2)){c=c.stateNode;break a}}d?Oe(a,c,b):Pe(a,c,b)}function Oe(a,b,c){var d=a.tag,e=5===d||6===d;if(e)a=e?a.stateNode:a.stateNode.instance,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=uc));else if(4!==d&&(a=a.child,null!==a))for(Oe(a,b,c),a=a.sibling;null!==a;)Oe(a,b,c),a=a.sibling}
function Pe(a,b,c){var d=a.tag,e=5===d||6===d;if(e)a=e?a.stateNode:a.stateNode.instance,b?c.insertBefore(a,b):c.appendChild(a);else if(4!==d&&(a=a.child,null!==a))for(Pe(a,b,c),a=a.sibling;null!==a;)Pe(a,b,c),a=a.sibling}function Dh(a,b,c){for(var d=b,e=!1,f,g;;){if(!e){e=d.return;a:for(;;){if(null===e)throw Error(k(160));f=e.stateNode;switch(e.tag){case 5:g=!1;break a;case 3:f=f.containerInfo;g=!0;break a;case 4:f=f.containerInfo;g=!0;break a}e=e.return}e=!0}if(5===d.tag||6===d.tag){a:for(var h=
a,m=d,n=c,l=m;;)if(Ch(h,l,n),null!==l.child&&4!==l.tag)l.child.return=l,l=l.child;else{if(l===m)break a;for(;null===l.sibling;){if(null===l.return||l.return===m)break a;l=l.return}l.sibling.return=l.return;l=l.sibling}g?(h=f,m=d.stateNode,8===h.nodeType?h.parentNode.removeChild(m):h.removeChild(m)):f.removeChild(d.stateNode)}else if(4===d.tag){if(null!==d.child){f=d.stateNode.containerInfo;g=!0;d.child.return=d;d=d.child;continue}}else if(Ch(a,d,c),null!==d.child){d.child.return=d;d=d.child;continue}if(d===
b)break;for(;null===d.sibling;){if(null===d.return||d.return===b)return;d=d.return;4===d.tag&&(e=!1)}d.sibling.return=d.return;d=d.sibling}}function Qe(a,b){switch(b.tag){case 0:case 11:case 14:case 15:case 22:Ah(3,b);return;case 1:return;case 5:var c=b.stateNode;if(null!=c){var d=b.memoizedProps,e=null!==a?a.memoizedProps:d;a=b.type;var f=b.updateQueue;b.updateQueue=null;if(null!==f){c[vc]=d;"input"===a&&"radio"===d.type&&null!=d.name&&If(c,d);Vd(a,e);b=Vd(a,d);for(e=0;e<f.length;e+=2){var g=f[e],
h=f[e+1];"style"===g?gg(c,h):"dangerouslySetInnerHTML"===g?xh(c,h):"children"===g?Wb(c,h):xd(c,g,h,b)}switch(a){case "input":Dd(c,d);break;case "textarea":Lf(c,d);break;case "select":b=c._wrapperState.wasMultiple,c._wrapperState.wasMultiple=!!d.multiple,a=d.value,null!=a?hb(c,!!d.multiple,a,!1):b!==!!d.multiple&&(null!=d.defaultValue?hb(c,!!d.multiple,d.defaultValue,!0):hb(c,!!d.multiple,d.multiple?[]:"",!1))}}}return;case 6:if(null===b.stateNode)throw Error(k(162));b.stateNode.nodeValue=b.memoizedProps;
return;case 3:b=b.stateNode;b.hydrate&&(b.hydrate=!1,bg(b.containerInfo));return;case 12:return;case 13:c=b;null===b.memoizedState?d=!1:(d=!0,c=b.child,Re=Y());if(null!==c)a:for(a=c;;){if(5===a.tag)f=a.stateNode,d?(f=f.style,"function"===typeof f.setProperty?f.setProperty("display","none","important"):f.display="none"):(f=a.stateNode,e=a.memoizedProps.style,e=void 0!==e&&null!==e&&e.hasOwnProperty("display")?e.display:null,f.style.display=fg("display",e));else if(6===a.tag)a.stateNode.nodeValue=d?
"":a.memoizedProps;else if(13===a.tag&&null!==a.memoizedState&&null===a.memoizedState.dehydrated){f=a.child.sibling;f.return=a;a=f;continue}else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===c)break;for(;null===a.sibling;){if(null===a.return||a.return===c)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}Hh(b);return;case 19:Hh(b);return;case 17:return}throw Error(k(163));}function Hh(a){var b=a.updateQueue;if(null!==b){a.updateQueue=null;var c=a.stateNode;null===c&&(c=a.stateNode=
new pj);b.forEach(function(b){var d=qj.bind(null,a,b);c.has(b)||(c.add(b),b.then(d,d))})}}function Ih(a,b,c){c=Ea(c,null);c.tag=3;c.payload={element:null};var d=b.value;c.callback=function(){cd||(cd=!0,Se=d);Me(a,b)};return c}function Jh(a,b,c){c=Ea(c,null);c.tag=3;var d=a.type.getDerivedStateFromError;if("function"===typeof d){var e=b.value;c.payload=function(){Me(a,b);return d(e)}}var f=a.stateNode;null!==f&&"function"===typeof f.componentDidCatch&&(c.callback=function(){"function"!==typeof d&&
(null===La?La=new Set([this]):La.add(this),Me(a,b));var c=b.stack;this.componentDidCatch(b.value,{componentStack:null!==c?c:""})});return c}function ka(){return(p&(ca|ma))!==H?1073741821-(Y()/10|0):0!==dd?dd:dd=1073741821-(Y()/10|0)}function Va(a,b,c){b=b.mode;if(0===(b&2))return 1073741823;var d=Cc();if(0===(b&4))return 99===d?1073741823:1073741822;if((p&ca)!==H)return P;if(null!==c)a=Fc(a,c.timeoutMs|0||5E3,250);else switch(d){case 99:a=1073741823;break;case 98:a=Fc(a,150,100);break;case 97:case 96:a=
Fc(a,5E3,250);break;case 95:a=2;break;default:throw Error(k(326));}null!==U&&a===P&&--a;return a}function ed(a,b){a.expirationTime<b&&(a.expirationTime=b);var c=a.alternate;null!==c&&c.expirationTime<b&&(c.expirationTime=b);var d=a.return,e=null;if(null===d&&3===a.tag)e=a.stateNode;else for(;null!==d;){c=d.alternate;d.childExpirationTime<b&&(d.childExpirationTime=b);null!==c&&c.childExpirationTime<b&&(c.childExpirationTime=b);if(null===d.return&&3===d.tag){e=d.stateNode;break}d=d.return}null!==e&&
(U===e&&(Kc(b),F===bd&&Ya(e,P)),yh(e,b));return e}function fd(a){var b=a.lastExpiredTime;if(0!==b)return b;b=a.firstPendingTime;if(!Kh(a,b))return b;var c=a.lastPingedTime;a=a.nextKnownPendingLevel;a=c>a?c:a;return 2>=a&&b!==a?0:a}function V(a){if(0!==a.lastExpiredTime)a.callbackExpirationTime=1073741823,a.callbackPriority=99,a.callbackNode=Og(Te.bind(null,a));else{var b=fd(a),c=a.callbackNode;if(0===b)null!==c&&(a.callbackNode=null,a.callbackExpirationTime=0,a.callbackPriority=90);else{var d=ka();
1073741823===b?d=99:1===b||2===b?d=95:(d=10*(1073741821-b)-10*(1073741821-d),d=0>=d?99:250>=d?98:5250>=d?97:95);if(null!==c){var e=a.callbackPriority;if(a.callbackExpirationTime===b&&e>=d)return;c!==Qg&&Rg(c)}a.callbackExpirationTime=b;a.callbackPriority=d;b=1073741823===b?Og(Te.bind(null,a)):Ng(d,Lh.bind(null,a),{timeout:10*(1073741821-b)-Y()});a.callbackNode=b}}}function Lh(a,b){dd=0;if(b)return b=ka(),Ue(a,b),V(a),null;var c=fd(a);if(0!==c){b=a.callbackNode;if((p&(ca|ma))!==H)throw Error(k(327));
xb();a===U&&c===P||$a(a,c);if(null!==t){var d=p;p|=ca;var e=Mh();do try{rj();break}catch(h){Nh(a,h)}while(1);le();p=d;gd.current=e;if(F===hd)throw b=id,$a(a,c),Ya(a,c),V(a),b;if(null===t)switch(e=a.finishedWork=a.current.alternate,a.finishedExpirationTime=c,d=F,U=null,d){case Xa:case hd:throw Error(k(345));case Oh:Ue(a,2<c?2:c);break;case ad:Ya(a,c);d=a.lastSuspendedTime;c===d&&(a.nextKnownPendingLevel=Ve(e));if(1073741823===ta&&(e=Re+Ph-Y(),10<e)){if(jd){var f=a.lastPingedTime;if(0===f||f>=c){a.lastPingedTime=
c;$a(a,c);break}}f=fd(a);if(0!==f&&f!==c)break;if(0!==d&&d!==c){a.lastPingedTime=d;break}a.timeoutHandle=We(ab.bind(null,a),e);break}ab(a);break;case bd:Ya(a,c);d=a.lastSuspendedTime;c===d&&(a.nextKnownPendingLevel=Ve(e));if(jd&&(e=a.lastPingedTime,0===e||e>=c)){a.lastPingedTime=c;$a(a,c);break}e=fd(a);if(0!==e&&e!==c)break;if(0!==d&&d!==c){a.lastPingedTime=d;break}1073741823!==Yb?d=10*(1073741821-Yb)-Y():1073741823===ta?d=0:(d=10*(1073741821-ta)-5E3,e=Y(),c=10*(1073741821-c)-e,d=e-d,0>d&&(d=0),d=
(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*sj(d/1960))-d,c<d&&(d=c));if(10<d){a.timeoutHandle=We(ab.bind(null,a),d);break}ab(a);break;case Xe:if(1073741823!==ta&&null!==kd){f=ta;var g=kd;d=g.busyMinDurationMs|0;0>=d?d=0:(e=g.busyDelayMs|0,f=Y()-(10*(1073741821-f)-(g.timeoutMs|0||5E3)),d=f<=e?0:e+d-f);if(10<d){Ya(a,c);a.timeoutHandle=We(ab.bind(null,a),d);break}}ab(a);break;default:throw Error(k(329));}V(a);if(a.callbackNode===b)return Lh.bind(null,a)}}return null}function Te(a){var b=
a.lastExpiredTime;b=0!==b?b:1073741823;if((p&(ca|ma))!==H)throw Error(k(327));xb();a===U&&b===P||$a(a,b);if(null!==t){var c=p;p|=ca;var d=Mh();do try{tj();break}catch(e){Nh(a,e)}while(1);le();p=c;gd.current=d;if(F===hd)throw c=id,$a(a,b),Ya(a,b),V(a),c;if(null!==t)throw Error(k(261));a.finishedWork=a.current.alternate;a.finishedExpirationTime=b;U=null;ab(a);V(a)}return null}function uj(){if(null!==bb){var a=bb;bb=null;a.forEach(function(a,c){Ue(c,a);V(c)});ha()}}function Qh(a,b){var c=p;p|=1;try{return a(b)}finally{p=
c,p===H&&ha()}}function Rh(a,b){var c=p;p&=-2;p|=Ye;try{return a(b)}finally{p=c,p===H&&ha()}}function $a(a,b){a.finishedWork=null;a.finishedExpirationTime=0;var c=a.timeoutHandle;-1!==c&&(a.timeoutHandle=-1,vj(c));if(null!==t)for(c=t.return;null!==c;){var d=c;switch(d.tag){case 1:d=d.type.childContextTypes;null!==d&&void 0!==d&&(q(G),q(B));break;case 3:tb();q(G);q(B);break;case 5:te(d);break;case 4:tb();break;case 13:q(D);break;case 19:q(D);break;case 10:me(d)}c=c.return}U=a;t=Sa(a.current,null);
P=b;F=Xa;id=null;Yb=ta=1073741823;kd=null;Xb=0;jd=!1}function Nh(a,b){do{try{le();Sc.current=Tc;if(Uc)for(var c=z.memoizedState;null!==c;){var d=c.queue;null!==d&&(d.pending=null);c=c.next}Ia=0;J=K=z=null;Uc=!1;if(null===t||null===t.return)return F=hd,id=b,t=null;a:{var e=a,f=t.return,g=t,h=b;b=P;g.effectTag|=2048;g.firstEffect=g.lastEffect=null;if(null!==h&&"object"===typeof h&&"function"===typeof h.then){var m=h;if(0===(g.mode&2)){var n=g.alternate;n?(g.updateQueue=n.updateQueue,g.memoizedState=
n.memoizedState,g.expirationTime=n.expirationTime):(g.updateQueue=null,g.memoizedState=null)}var l=0!==(D.current&1),k=f;do{var p;if(p=13===k.tag){var q=k.memoizedState;if(null!==q)p=null!==q.dehydrated?!0:!1;else{var w=k.memoizedProps;p=void 0===w.fallback?!1:!0!==w.unstable_avoidThisFallback?!0:l?!1:!0}}if(p){var y=k.updateQueue;if(null===y){var r=new Set;r.add(m);k.updateQueue=r}else y.add(m);if(0===(k.mode&2)){k.effectTag|=64;g.effectTag&=-2981;if(1===g.tag)if(null===g.alternate)g.tag=17;else{var O=
Ea(1073741823,null);O.tag=Jc;Fa(g,O)}g.expirationTime=1073741823;break a}h=void 0;g=b;var v=e.pingCache;null===v?(v=e.pingCache=new wj,h=new Set,v.set(m,h)):(h=v.get(m),void 0===h&&(h=new Set,v.set(m,h)));if(!h.has(g)){h.add(g);var x=xj.bind(null,e,m,g);m.then(x,x)}k.effectTag|=4096;k.expirationTime=b;break a}k=k.return}while(null!==k);h=Error((na(g.type)||"A React component")+" suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display."+
Bd(g))}F!==Xe&&(F=Oh);h=Le(h,g);k=f;do{switch(k.tag){case 3:m=h;k.effectTag|=4096;k.expirationTime=b;var A=Ih(k,m,b);Ug(k,A);break a;case 1:m=h;var u=k.type,B=k.stateNode;if(0===(k.effectTag&64)&&("function"===typeof u.getDerivedStateFromError||null!==B&&"function"===typeof B.componentDidCatch&&(null===La||!La.has(B)))){k.effectTag|=4096;k.expirationTime=b;var H=Jh(k,m,b);Ug(k,H);break a}}k=k.return}while(null!==k)}t=Sh(t)}catch(cj){b=cj;continue}break}while(1)}function Mh(a){a=gd.current;gd.current=
Tc;return null===a?Tc:a}function Vg(a,b){a<ta&&2<a&&(ta=a);null!==b&&a<Yb&&2<a&&(Yb=a,kd=b)}function Kc(a){a>Xb&&(Xb=a)}function tj(){for(;null!==t;)t=Th(t)}function rj(){for(;null!==t&&!yj();)t=Th(t)}function Th(a){var b=zj(a.alternate,a,P);a.memoizedProps=a.pendingProps;null===b&&(b=Sh(a));Uh.current=null;return b}function Sh(a){t=a;do{var b=t.alternate;a=t.return;if(0===(t.effectTag&2048)){b=hj(b,t,P);if(1===P||1!==t.childExpirationTime){for(var c=0,d=t.child;null!==d;){var e=d.expirationTime,
f=d.childExpirationTime;e>c&&(c=e);f>c&&(c=f);d=d.sibling}t.childExpirationTime=c}if(null!==b)return b;null!==a&&0===(a.effectTag&2048)&&(null===a.firstEffect&&(a.firstEffect=t.firstEffect),null!==t.lastEffect&&(null!==a.lastEffect&&(a.lastEffect.nextEffect=t.firstEffect),a.lastEffect=t.lastEffect),1<t.effectTag&&(null!==a.lastEffect?a.lastEffect.nextEffect=t:a.firstEffect=t,a.lastEffect=t))}else{b=lj(t);if(null!==b)return b.effectTag&=2047,b;null!==a&&(a.firstEffect=a.lastEffect=null,a.effectTag|=
2048)}b=t.sibling;if(null!==b)return b;t=a}while(null!==t);F===Xa&&(F=Xe);return null}function Ve(a){var b=a.expirationTime;a=a.childExpirationTime;return b>a?b:a}function ab(a){var b=Cc();Da(99,Aj.bind(null,a,b));return null}function Aj(a,b){do xb();while(null!==Zb);if((p&(ca|ma))!==H)throw Error(k(327));var c=a.finishedWork,d=a.finishedExpirationTime;if(null===c)return null;a.finishedWork=null;a.finishedExpirationTime=0;if(c===a.current)throw Error(k(177));a.callbackNode=null;a.callbackExpirationTime=
0;a.callbackPriority=90;a.nextKnownPendingLevel=0;var e=Ve(c);a.firstPendingTime=e;d<=a.lastSuspendedTime?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:d<=a.firstSuspendedTime&&(a.firstSuspendedTime=d-1);d<=a.lastPingedTime&&(a.lastPingedTime=0);d<=a.lastExpiredTime&&(a.lastExpiredTime=0);a===U&&(t=U=null,P=0);1<c.effectTag?null!==c.lastEffect?(c.lastEffect.nextEffect=c,e=c.firstEffect):e=c:e=c.firstEffect;if(null!==e){var f=p;p|=ma;Uh.current=null;Ze=tc;var g=kg();if(Xd(g)){if("selectionStart"in
g)var h={start:g.selectionStart,end:g.selectionEnd};else a:{h=(h=g.ownerDocument)&&h.defaultView||window;var m=h.getSelection&&h.getSelection();if(m&&0!==m.rangeCount){h=m.anchorNode;var n=m.anchorOffset,q=m.focusNode;m=m.focusOffset;try{h.nodeType,q.nodeType}catch(sb){h=null;break a}var ba=0,w=-1,y=-1,B=0,D=0,r=g,z=null;b:for(;;){for(var v;;){r!==h||0!==n&&3!==r.nodeType||(w=ba+n);r!==q||0!==m&&3!==r.nodeType||(y=ba+m);3===r.nodeType&&(ba+=r.nodeValue.length);if(null===(v=r.firstChild))break;z=r;
r=v}for(;;){if(r===g)break b;z===h&&++B===n&&(w=ba);z===q&&++D===m&&(y=ba);if(null!==(v=r.nextSibling))break;r=z;z=r.parentNode}r=v}h=-1===w||-1===y?null:{start:w,end:y}}else h=null}h=h||{start:0,end:0}}else h=null;$e={activeElementDetached:null,focusedElem:g,selectionRange:h};tc=!1;l=e;do try{Bj()}catch(sb){if(null===l)throw Error(k(330));Za(l,sb);l=l.nextEffect}while(null!==l);l=e;do try{for(g=a,h=b;null!==l;){var x=l.effectTag;x&16&&Wb(l.stateNode,"");if(x&128){var A=l.alternate;if(null!==A){var u=
A.ref;null!==u&&("function"===typeof u?u(null):u.current=null)}}switch(x&1038){case 2:Gh(l);l.effectTag&=-3;break;case 6:Gh(l);l.effectTag&=-3;Qe(l.alternate,l);break;case 1024:l.effectTag&=-1025;break;case 1028:l.effectTag&=-1025;Qe(l.alternate,l);break;case 4:Qe(l.alternate,l);break;case 8:n=l,Dh(g,n,h),Eh(n)}l=l.nextEffect}}catch(sb){if(null===l)throw Error(k(330));Za(l,sb);l=l.nextEffect}while(null!==l);u=$e;A=kg();x=u.focusedElem;h=u.selectionRange;if(A!==x&&x&&x.ownerDocument&&jg(x.ownerDocument.documentElement,
x)){null!==h&&Xd(x)&&(A=h.start,u=h.end,void 0===u&&(u=A),"selectionStart"in x?(x.selectionStart=A,x.selectionEnd=Math.min(u,x.value.length)):(u=(A=x.ownerDocument||document)&&A.defaultView||window,u.getSelection&&(u=u.getSelection(),n=x.textContent.length,g=Math.min(h.start,n),h=void 0===h.end?g:Math.min(h.end,n),!u.extend&&g>h&&(n=h,h=g,g=n),n=ig(x,g),q=ig(x,h),n&&q&&(1!==u.rangeCount||u.anchorNode!==n.node||u.anchorOffset!==n.offset||u.focusNode!==q.node||u.focusOffset!==q.offset)&&(A=A.createRange(),
A.setStart(n.node,n.offset),u.removeAllRanges(),g>h?(u.addRange(A),u.extend(q.node,q.offset)):(A.setEnd(q.node,q.offset),u.addRange(A))))));A=[];for(u=x;u=u.parentNode;)1===u.nodeType&&A.push({element:u,left:u.scrollLeft,top:u.scrollTop});"function"===typeof x.focus&&x.focus();for(x=0;x<A.length;x++)u=A[x],u.element.scrollLeft=u.left,u.element.scrollTop=u.top}tc=!!Ze;$e=Ze=null;a.current=c;l=e;do try{for(x=a;null!==l;){var F=l.effectTag;F&36&&oj(x,l.alternate,l);if(F&128){A=void 0;var E=l.ref;if(null!==
E){var G=l.stateNode;switch(l.tag){case 5:A=G;break;default:A=G}"function"===typeof E?E(A):E.current=A}}l=l.nextEffect}}catch(sb){if(null===l)throw Error(k(330));Za(l,sb);l=l.nextEffect}while(null!==l);l=null;Cj();p=f}else a.current=c;if(ld)ld=!1,Zb=a,$b=b;else for(l=e;null!==l;)b=l.nextEffect,l.nextEffect=null,l=b;b=a.firstPendingTime;0===b&&(La=null);1073741823===b?a===af?ac++:(ac=0,af=a):ac=0;"function"===typeof bf&&bf(c.stateNode,d);V(a);if(cd)throw cd=!1,a=Se,Se=null,a;if((p&Ye)!==H)return null;
ha();return null}function Bj(){for(;null!==l;){var a=l.effectTag;0!==(a&256)&&nj(l.alternate,l);0===(a&512)||ld||(ld=!0,Ng(97,function(){xb();return null}));l=l.nextEffect}}function xb(){if(90!==$b){var a=97<$b?97:$b;$b=90;return Da(a,Dj)}}function Dj(){if(null===Zb)return!1;var a=Zb;Zb=null;if((p&(ca|ma))!==H)throw Error(k(331));var b=p;p|=ma;for(a=a.current.firstEffect;null!==a;){try{var c=a;if(0!==(c.effectTag&512))switch(c.tag){case 0:case 11:case 15:case 22:Ah(5,c),Bh(5,c)}}catch(d){if(null===
a)throw Error(k(330));Za(a,d)}c=a.nextEffect;a.nextEffect=null;a=c}p=b;ha();return!0}function Vh(a,b,c){b=Le(c,b);b=Ih(a,b,1073741823);Fa(a,b);a=ed(a,1073741823);null!==a&&V(a)}function Za(a,b){if(3===a.tag)Vh(a,a,b);else for(var c=a.return;null!==c;){if(3===c.tag){Vh(c,a,b);break}else if(1===c.tag){var d=c.stateNode;if("function"===typeof c.type.getDerivedStateFromError||"function"===typeof d.componentDidCatch&&(null===La||!La.has(d))){a=Le(b,a);a=Jh(c,a,1073741823);Fa(c,a);c=ed(c,1073741823);null!==
c&&V(c);break}}c=c.return}}function xj(a,b,c){var d=a.pingCache;null!==d&&d.delete(b);U===a&&P===c?F===bd||F===ad&&1073741823===ta&&Y()-Re<Ph?$a(a,P):jd=!0:Kh(a,c)&&(b=a.lastPingedTime,0!==b&&b<c||(a.lastPingedTime=c,V(a)))}function qj(a,b){var c=a.stateNode;null!==c&&c.delete(b);b=0;0===b&&(b=ka(),b=Va(b,a,null));a=ed(a,b);null!==a&&V(a)}function Ej(a){if("undefined"===typeof __REACT_DEVTOOLS_GLOBAL_HOOK__)return!1;var b=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(b.isDisabled||!b.supportsFiber)return!0;try{var c=
b.inject(a);bf=function(a,e){try{b.onCommitFiberRoot(c,a,void 0,64===(a.current.effectTag&64))}catch(f){}};Ne=function(a){try{b.onCommitFiberUnmount(c,a)}catch(e){}}}catch(d){}return!0}function Fj(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.effectTag=0;this.lastEffect=this.firstEffect=this.nextEffect=
null;this.childExpirationTime=this.expirationTime=0;this.alternate=null}function Ge(a){a=a.prototype;return!(!a||!a.isReactComponent)}function Gj(a){if("function"===typeof a)return Ge(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===zd)return 11;if(a===Ad)return 14}return 2}function Sa(a,b){var c=a.alternate;null===c?(c=la(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.effectTag=0,c.nextEffect=null,c.firstEffect=
null,c.lastEffect=null);c.childExpirationTime=a.childExpirationTime;c.expirationTime=a.expirationTime;c.child=a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue=a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{expirationTime:b.expirationTime,firstContext:b.firstContext,responders:b.responders};c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c}function Oc(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)Ge(a)&&(g=1);else if("string"===typeof a)g=
5;else a:switch(a){case Ma:return Ha(c.children,e,f,b);case Hj:g=8;e|=7;break;case Af:g=8;e|=1;break;case kc:return a=la(12,c,b,e|8),a.elementType=kc,a.type=kc,a.expirationTime=f,a;case lc:return a=la(13,c,b,e),a.type=lc,a.elementType=lc,a.expirationTime=f,a;case yd:return a=la(19,c,b,e),a.elementType=yd,a.expirationTime=f,a;default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case Cf:g=10;break a;case Bf:g=9;break a;case zd:g=11;break a;case Ad:g=14;break a;case Ef:g=16;d=null;break a;case Df:g=
22;break a}throw Error(k(130,null==a?a:typeof a,""));}b=la(g,c,b,e);b.elementType=a;b.type=d;b.expirationTime=f;return b}function Ha(a,b,c,d){a=la(7,a,d,b);a.expirationTime=c;return a}function qe(a,b,c){a=la(6,a,null,b);a.expirationTime=c;return a}function re(a,b,c){b=la(4,null!==a.children?a.children:[],a.key,b);b.expirationTime=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}function Ij(a,b,c){this.tag=b;this.current=null;this.containerInfo=
a;this.pingCache=this.pendingChildren=null;this.finishedExpirationTime=0;this.finishedWork=null;this.timeoutHandle=-1;this.pendingContext=this.context=null;this.hydrate=c;this.callbackNode=null;this.callbackPriority=90;this.lastExpiredTime=this.lastPingedTime=this.nextKnownPendingLevel=this.lastSuspendedTime=this.firstSuspendedTime=this.firstPendingTime=0}function Kh(a,b){var c=a.firstSuspendedTime;a=a.lastSuspendedTime;return 0!==c&&c>=b&&a<=b}function Ya(a,b){var c=a.firstSuspendedTime,d=a.lastSuspendedTime;
c<b&&(a.firstSuspendedTime=b);if(d>b||0===c)a.lastSuspendedTime=b;b<=a.lastPingedTime&&(a.lastPingedTime=0);b<=a.lastExpiredTime&&(a.lastExpiredTime=0)}function yh(a,b){b>a.firstPendingTime&&(a.firstPendingTime=b);var c=a.firstSuspendedTime;0!==c&&(b>=c?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:b>=a.lastSuspendedTime&&(a.lastSuspendedTime=b+1),b>a.nextKnownPendingLevel&&(a.nextKnownPendingLevel=b))}function Ue(a,b){var c=a.lastExpiredTime;if(0===c||c>b)a.lastExpiredTime=b}
function md(a,b,c,d){var e=b.current,f=ka(),g=Vb.suspense;f=Va(f,e,g);a:if(c){c=c._reactInternalFiber;b:{if(Na(c)!==c||1!==c.tag)throw Error(k(170));var h=c;do{switch(h.tag){case 3:h=h.stateNode.context;break b;case 1:if(N(h.type)){h=h.stateNode.__reactInternalMemoizedMergedChildContext;break b}}h=h.return}while(null!==h);throw Error(k(171));}if(1===c.tag){var m=c.type;if(N(m)){c=Gg(c,m,h);break a}}c=h}else c=Ca;null===b.context?b.context=c:b.pendingContext=c;b=Ea(f,g);b.payload={element:a};d=void 0===
d?null:d;null!==d&&(b.callback=d);Fa(e,b);Ja(e,f);return f}function cf(a){a=a.current;if(!a.child)return null;switch(a.child.tag){case 5:return a.child.stateNode;default:return a.child.stateNode}}function Wh(a,b){a=a.memoizedState;null!==a&&null!==a.dehydrated&&a.retryTime<b&&(a.retryTime=b)}function df(a,b){Wh(a,b);(a=a.alternate)&&Wh(a,b)}function ef(a,b,c){c=null!=c&&!0===c.hydrate;var d=new Ij(a,b,c),e=la(3,null,null,2===b?7:1===b?3:0);d.current=e;e.stateNode=d;ne(e);a[Lb]=d.current;c&&0!==b&&
xi(a,9===a.nodeType?a:a.ownerDocument);this._internalRoot=d}function bc(a){return!(!a||1!==a.nodeType&&9!==a.nodeType&&11!==a.nodeType&&(8!==a.nodeType||" react-mount-point-unstable "!==a.nodeValue))}function Jj(a,b){b||(b=a?9===a.nodeType?a.documentElement:a.firstChild:null,b=!(!b||1!==b.nodeType||!b.hasAttribute("data-reactroot")));if(!b)for(var c;c=a.lastChild;)a.removeChild(c);return new ef(a,0,b?{hydrate:!0}:void 0)}function nd(a,b,c,d,e){var f=c._reactRootContainer;if(f){var g=f._internalRoot;
if("function"===typeof e){var h=e;e=function(){var a=cf(g);h.call(a)}}md(b,g,a,e)}else{f=c._reactRootContainer=Jj(c,d);g=f._internalRoot;if("function"===typeof e){var m=e;e=function(){var a=cf(g);m.call(a)}}Rh(function(){md(b,g,a,e)})}return cf(g)}function Kj(a,b,c){var d=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:gb,key:null==d?null:""+d,children:a,containerInfo:b,implementation:c}}function Xh(a,b){var c=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;
if(!bc(b))throw Error(k(200));return Kj(a,b,null,c)}if(!ea)throw Error(k(227));var ki=function(a,b,c,d,e,f,g,h,m){var n=Array.prototype.slice.call(arguments,3);try{b.apply(c,n)}catch(C){this.onError(C)}},yb=!1,gc=null,hc=!1,pd=null,li={onError:function(a){yb=!0;gc=a}},td=null,rf=null,mf=null,ic=null,cb={},jc=[],qd={},db={},rd={},wa=!("undefined"===typeof window||"undefined"===typeof window.document||"undefined"===typeof window.document.createElement),M=ea.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.assign,
sd=null,eb=null,fb=null,ee=function(a,b){return a(b)},eg=function(a,b,c,d,e){return a(b,c,d,e)},vd=function(){},vf=ee,Oa=!1,wd=!1,Z=ea.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler,Lj=Z.unstable_cancelCallback,ff=Z.unstable_now,$f=Z.unstable_scheduleCallback,Mj=Z.unstable_shouldYield,Yh=Z.unstable_requestPaint,Pd=Z.unstable_runWithPriority,Nj=Z.unstable_getCurrentPriorityLevel,Oj=Z.unstable_ImmediatePriority,Zh=Z.unstable_UserBlockingPriority,ag=Z.unstable_NormalPriority,Pj=Z.unstable_LowPriority,
Qj=Z.unstable_IdlePriority,oi=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,wf=Object.prototype.hasOwnProperty,yf={},xf={},E={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(a){E[a]=
new L(a,0,!1,a,null,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(a){var b=a[0];E[b]=new L(b,1,!1,a[1],null,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(a){E[a]=new L(a,2,!1,a.toLowerCase(),null,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(a){E[a]=new L(a,2,!1,a,null,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(a){E[a]=
new L(a,3,!1,a.toLowerCase(),null,!1)});["checked","multiple","muted","selected"].forEach(function(a){E[a]=new L(a,3,!0,a,null,!1)});["capture","download"].forEach(function(a){E[a]=new L(a,4,!1,a,null,!1)});["cols","rows","size","span"].forEach(function(a){E[a]=new L(a,6,!1,a,null,!1)});["rowSpan","start"].forEach(function(a){E[a]=new L(a,5,!1,a.toLowerCase(),null,!1)});var gf=/[\-:]([a-z])/g,hf=function(a){return a[1].toUpperCase()};"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(a){var b=
a.replace(gf,hf);E[b]=new L(b,1,!1,a,null,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(a){var b=a.replace(gf,hf);E[b]=new L(b,1,!1,a,"http://www.w3.org/1999/xlink",!1)});["xml:base","xml:lang","xml:space"].forEach(function(a){var b=a.replace(gf,hf);E[b]=new L(b,1,!1,a,"http://www.w3.org/XML/1998/namespace",!1)});["tabIndex","crossOrigin"].forEach(function(a){E[a]=new L(a,1,!1,a.toLowerCase(),null,!1)});E.xlinkHref=new L("xlinkHref",1,
!1,"xlink:href","http://www.w3.org/1999/xlink",!0);["src","href","action","formAction"].forEach(function(a){E[a]=new L(a,1,!1,a.toLowerCase(),null,!0)});var da=ea.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;da.hasOwnProperty("ReactCurrentDispatcher")||(da.ReactCurrentDispatcher={current:null});da.hasOwnProperty("ReactCurrentBatchConfig")||(da.ReactCurrentBatchConfig={suspense:null});var si=/^(.*)[\\\/]/,Q="function"===typeof Symbol&&Symbol.for,Pc=Q?Symbol.for("react.element"):60103,gb=Q?Symbol.for("react.portal"):
60106,Ma=Q?Symbol.for("react.fragment"):60107,Af=Q?Symbol.for("react.strict_mode"):60108,kc=Q?Symbol.for("react.profiler"):60114,Cf=Q?Symbol.for("react.provider"):60109,Bf=Q?Symbol.for("react.context"):60110,Hj=Q?Symbol.for("react.concurrent_mode"):60111,zd=Q?Symbol.for("react.forward_ref"):60112,lc=Q?Symbol.for("react.suspense"):60113,yd=Q?Symbol.for("react.suspense_list"):60120,Ad=Q?Symbol.for("react.memo"):60115,Ef=Q?Symbol.for("react.lazy"):60116,Df=Q?Symbol.for("react.block"):60121,zf="function"===
typeof Symbol&&Symbol.iterator,od,xh=function(a){return"undefined"!==typeof MSApp&&MSApp.execUnsafeLocalFunction?function(b,c,d,e){MSApp.execUnsafeLocalFunction(function(){return a(b,c,d,e)})}:a}(function(a,b){if("http://www.w3.org/2000/svg"!==a.namespaceURI||"innerHTML"in a)a.innerHTML=b;else{od=od||document.createElement("div");od.innerHTML="<svg>"+b.valueOf().toString()+"</svg>";for(b=od.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}}),Wb=function(a,
b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b},ib={animationend:nc("Animation","AnimationEnd"),animationiteration:nc("Animation","AnimationIteration"),animationstart:nc("Animation","AnimationStart"),transitionend:nc("Transition","TransitionEnd")},Id={},Of={};wa&&(Of=document.createElement("div").style,"AnimationEvent"in window||(delete ib.animationend.animation,delete ib.animationiteration.animation,delete ib.animationstart.animation),"TransitionEvent"in
window||delete ib.transitionend.transition);var $h=oc("animationend"),ai=oc("animationiteration"),bi=oc("animationstart"),ci=oc("transitionend"),Db="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Pf=new ("function"===typeof WeakMap?WeakMap:Map),Ab=null,wi=function(a){if(a){var b=a._dispatchListeners,c=a._dispatchInstances;
if(Array.isArray(b))for(var d=0;d<b.length&&!a.isPropagationStopped();d++)lf(a,b[d],c[d]);else b&&lf(a,b,c);a._dispatchListeners=null;a._dispatchInstances=null;a.isPersistent()||a.constructor.release(a)}},qc=[],Rd=!1,fa=[],xa=null,ya=null,za=null,Eb=new Map,Fb=new Map,Jb=[],Nd="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput close cancel copy cut paste click change contextmenu reset submit".split(" "),
yi="focus blur dragenter dragleave mouseover mouseout pointerover pointerout gotpointercapture lostpointercapture".split(" "),dg={},cg=new Map,Td=new Map,Rj=["abort","abort",$h,"animationEnd",ai,"animationIteration",bi,"animationStart","canplay","canPlay","canplaythrough","canPlayThrough","durationchange","durationChange","emptied","emptied","encrypted","encrypted","ended","ended","error","error","gotpointercapture","gotPointerCapture","load","load","loadeddata","loadedData","loadedmetadata","loadedMetadata",
"loadstart","loadStart","lostpointercapture","lostPointerCapture","playing","playing","progress","progress","seeking","seeking","stalled","stalled","suspend","suspend","timeupdate","timeUpdate",ci,"transitionEnd","waiting","waiting"];Sd("blur blur cancel cancel click click close close contextmenu contextMenu copy copy cut cut auxclick auxClick dblclick doubleClick dragend dragEnd dragstart dragStart drop drop focus focus input input invalid invalid keydown keyDown keypress keyPress keyup keyUp mousedown mouseDown mouseup mouseUp paste paste pause pause play play pointercancel pointerCancel pointerdown pointerDown pointerup pointerUp ratechange rateChange reset reset seeked seeked submit submit touchcancel touchCancel touchend touchEnd touchstart touchStart volumechange volumeChange".split(" "),
0);Sd("drag drag dragenter dragEnter dragexit dragExit dragleave dragLeave dragover dragOver mousemove mouseMove mouseout mouseOut mouseover mouseOver pointermove pointerMove pointerout pointerOut pointerover pointerOver scroll scroll toggle toggle touchmove touchMove wheel wheel".split(" "),1);Sd(Rj,2);(function(a,b){for(var c=0;c<a.length;c++)Td.set(a[c],b)})("change selectionchange textInput compositionstart compositionend compositionupdate".split(" "),0);var Hi=Zh,Gi=Pd,tc=!0,Kb={animationIterationCount:!0,
borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,
strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Sj=["Webkit","ms","Moz","O"];Object.keys(Kb).forEach(function(a){Sj.forEach(function(b){b=b+a.charAt(0).toUpperCase()+a.substring(1);Kb[b]=Kb[a]})});var Ii=M({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),ng="$",og="/$",$d="$?",Zd="$!",Ze=null,$e=null,We="function"===typeof setTimeout?setTimeout:void 0,vj="function"===
typeof clearTimeout?clearTimeout:void 0,jf=Math.random().toString(36).slice(2),Aa="__reactInternalInstance$"+jf,vc="__reactEventHandlers$"+jf,Lb="__reactContainere$"+jf,Ba=null,ce=null,wc=null;M(R.prototype,{preventDefault:function(){this.defaultPrevented=!0;var a=this.nativeEvent;a&&(a.preventDefault?a.preventDefault():"unknown"!==typeof a.returnValue&&(a.returnValue=!1),this.isDefaultPrevented=xc)},stopPropagation:function(){var a=this.nativeEvent;a&&(a.stopPropagation?a.stopPropagation():"unknown"!==
typeof a.cancelBubble&&(a.cancelBubble=!0),this.isPropagationStopped=xc)},persist:function(){this.isPersistent=xc},isPersistent:yc,destructor:function(){var a=this.constructor.Interface,b;for(b in a)this[b]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null;this.isPropagationStopped=this.isDefaultPrevented=yc;this._dispatchInstances=this._dispatchListeners=null}});R.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(a){return a.timeStamp||
Date.now()},defaultPrevented:null,isTrusted:null};R.extend=function(a){function b(){return c.apply(this,arguments)}var c=this,d=function(){};d.prototype=c.prototype;d=new d;M(d,b.prototype);b.prototype=d;b.prototype.constructor=b;b.Interface=M({},c.Interface,a);b.extend=c.extend;sg(b);return b};sg(R);var Tj=R.extend({data:null}),Uj=R.extend({data:null}),Ni=[9,13,27,32],de=wa&&"CompositionEvent"in window,cc=null;wa&&"documentMode"in document&&(cc=document.documentMode);var Vj=wa&&"TextEvent"in window&&
!cc,xg=wa&&(!de||cc&&8<cc&&11>=cc),wg=String.fromCharCode(32),ua={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},
dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},vg=!1,mb=!1,Wj={eventTypes:ua,extractEvents:function(a,b,c,d,e){var f;if(de)b:{switch(a){case "compositionstart":var g=ua.compositionStart;break b;case "compositionend":g=ua.compositionEnd;break b;case "compositionupdate":g=
ua.compositionUpdate;break b}g=void 0}else mb?tg(a,c)&&(g=ua.compositionEnd):"keydown"===a&&229===c.keyCode&&(g=ua.compositionStart);g?(xg&&"ko"!==c.locale&&(mb||g!==ua.compositionStart?g===ua.compositionEnd&&mb&&(f=rg()):(Ba=d,ce="value"in Ba?Ba.value:Ba.textContent,mb=!0)),e=Tj.getPooled(g,b,c,d),f?e.data=f:(f=ug(c),null!==f&&(e.data=f)),lb(e),f=e):f=null;(a=Vj?Oi(a,c):Pi(a,c))?(b=Uj.getPooled(ua.beforeInput,b,c,d),b.data=a,lb(b)):b=null;return null===f?b:null===b?f:[f,b]}},Qi={color:!0,date:!0,
datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0},Ag={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:"blur change click focus input keydown keyup selectionchange".split(" ")}},Mb=null,Nb=null,kf=!1;wa&&(kf=Tf("input")&&(!document.documentMode||9<document.documentMode));var Xj={eventTypes:Ag,_isInputEventSupported:kf,extractEvents:function(a,b,c,d,e){e=b?Pa(b):window;var f=
e.nodeName&&e.nodeName.toLowerCase();if("select"===f||"input"===f&&"file"===e.type)var g=Si;else if(yg(e))if(kf)g=Wi;else{g=Ui;var h=Ti}else(f=e.nodeName)&&"input"===f.toLowerCase()&&("checkbox"===e.type||"radio"===e.type)&&(g=Vi);if(g&&(g=g(a,b)))return zg(g,c,d);h&&h(a,e,b);"blur"===a&&(a=e._wrapperState)&&a.controlled&&"number"===e.type&&Ed(e,"number",e.value)}},dc=R.extend({view:null,detail:null}),Yi={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"},di=0,ei=0,fi=!1,gi=!1,ec=dc.extend({screenX:null,
screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:fe,button:null,buttons:null,relatedTarget:function(a){return a.relatedTarget||(a.fromElement===a.srcElement?a.toElement:a.fromElement)},movementX:function(a){if("movementX"in a)return a.movementX;var b=di;di=a.screenX;return fi?"mousemove"===a.type?a.screenX-b:0:(fi=!0,0)},movementY:function(a){if("movementY"in a)return a.movementY;var b=ei;ei=a.screenY;return gi?"mousemove"===
a.type?a.screenY-b:0:(gi=!0,0)}}),hi=ec.extend({pointerId:null,width:null,height:null,pressure:null,tangentialPressure:null,tiltX:null,tiltY:null,twist:null,pointerType:null,isPrimary:null}),fc={mouseEnter:{registrationName:"onMouseEnter",dependencies:["mouseout","mouseover"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["mouseout","mouseover"]},pointerEnter:{registrationName:"onPointerEnter",dependencies:["pointerout","pointerover"]},pointerLeave:{registrationName:"onPointerLeave",dependencies:["pointerout",
"pointerover"]}},Yj={eventTypes:fc,extractEvents:function(a,b,c,d,e){var f="mouseover"===a||"pointerover"===a,g="mouseout"===a||"pointerout"===a;if(f&&0===(e&32)&&(c.relatedTarget||c.fromElement)||!g&&!f)return null;f=d.window===d?d:(f=d.ownerDocument)?f.defaultView||f.parentWindow:window;if(g){if(g=b,b=(b=c.relatedTarget||c.toElement)?Bb(b):null,null!==b){var h=Na(b);if(b!==h||5!==b.tag&&6!==b.tag)b=null}}else g=null;if(g===b)return null;if("mouseout"===a||"mouseover"===a){var m=ec;var n=fc.mouseLeave;
var l=fc.mouseEnter;var k="mouse"}else if("pointerout"===a||"pointerover"===a)m=hi,n=fc.pointerLeave,l=fc.pointerEnter,k="pointer";a=null==g?f:Pa(g);f=null==b?f:Pa(b);n=m.getPooled(n,g,c,d);n.type=k+"leave";n.target=a;n.relatedTarget=f;c=m.getPooled(l,b,c,d);c.type=k+"enter";c.target=f;c.relatedTarget=a;d=g;k=b;if(d&&k)a:{m=d;l=k;g=0;for(a=m;a;a=pa(a))g++;a=0;for(b=l;b;b=pa(b))a++;for(;0<g-a;)m=pa(m),g--;for(;0<a-g;)l=pa(l),a--;for(;g--;){if(m===l||m===l.alternate)break a;m=pa(m);l=pa(l)}m=null}else m=
null;l=m;for(m=[];d&&d!==l;){g=d.alternate;if(null!==g&&g===l)break;m.push(d);d=pa(d)}for(d=[];k&&k!==l;){g=k.alternate;if(null!==g&&g===l)break;d.push(k);k=pa(k)}for(k=0;k<m.length;k++)be(m[k],"bubbled",n);for(k=d.length;0<k--;)be(d[k],"captured",c);return 0===(e&64)?[n]:[n,c]}},Qa="function"===typeof Object.is?Object.is:Zi,$i=Object.prototype.hasOwnProperty,Zj=wa&&"documentMode"in document&&11>=document.documentMode,Eg={select:{phasedRegistrationNames:{bubbled:"onSelect",captured:"onSelectCapture"},
dependencies:"blur contextmenu dragend focus keydown keyup mousedown mouseup selectionchange".split(" ")}},nb=null,he=null,Pb=null,ge=!1,ak={eventTypes:Eg,extractEvents:function(a,b,c,d,e,f){e=f||(d.window===d?d.document:9===d.nodeType?d:d.ownerDocument);if(!(f=!e)){a:{e=Jd(e);f=rd.onSelect;for(var g=0;g<f.length;g++)if(!e.has(f[g])){e=!1;break a}e=!0}f=!e}if(f)return null;e=b?Pa(b):window;switch(a){case "focus":if(yg(e)||"true"===e.contentEditable)nb=e,he=b,Pb=null;break;case "blur":Pb=he=nb=null;
break;case "mousedown":ge=!0;break;case "contextmenu":case "mouseup":case "dragend":return ge=!1,Dg(c,d);case "selectionchange":if(Zj)break;case "keydown":case "keyup":return Dg(c,d)}return null}},bk=R.extend({animationName:null,elapsedTime:null,pseudoElement:null}),ck=R.extend({clipboardData:function(a){return"clipboardData"in a?a.clipboardData:window.clipboardData}}),dk=dc.extend({relatedTarget:null}),ek={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",
Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},fk={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",
224:"Meta"},gk=dc.extend({key:function(a){if(a.key){var b=ek[a.key]||a.key;if("Unidentified"!==b)return b}return"keypress"===a.type?(a=Ac(a),13===a?"Enter":String.fromCharCode(a)):"keydown"===a.type||"keyup"===a.type?fk[a.keyCode]||"Unidentified":""},location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:fe,charCode:function(a){return"keypress"===a.type?Ac(a):0},keyCode:function(a){return"keydown"===a.type||"keyup"===a.type?a.keyCode:0},which:function(a){return"keypress"===
a.type?Ac(a):"keydown"===a.type||"keyup"===a.type?a.keyCode:0}}),hk=ec.extend({dataTransfer:null}),ik=dc.extend({touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:fe}),jk=R.extend({propertyName:null,elapsedTime:null,pseudoElement:null}),kk=ec.extend({deltaX:function(a){return"deltaX"in a?a.deltaX:"wheelDeltaX"in a?-a.wheelDeltaX:0},deltaY:function(a){return"deltaY"in a?a.deltaY:"wheelDeltaY"in a?-a.wheelDeltaY:"wheelDelta"in a?
-a.wheelDelta:0},deltaZ:null,deltaMode:null}),lk={eventTypes:dg,extractEvents:function(a,b,c,d,e){e=cg.get(a);if(!e)return null;switch(a){case "keypress":if(0===Ac(c))return null;case "keydown":case "keyup":a=gk;break;case "blur":case "focus":a=dk;break;case "click":if(2===c.button)return null;case "auxclick":case "dblclick":case "mousedown":case "mousemove":case "mouseup":case "mouseout":case "mouseover":case "contextmenu":a=ec;break;case "drag":case "dragend":case "dragenter":case "dragexit":case "dragleave":case "dragover":case "dragstart":case "drop":a=
hk;break;case "touchcancel":case "touchend":case "touchmove":case "touchstart":a=ik;break;case $h:case ai:case bi:a=bk;break;case ci:a=jk;break;case "scroll":a=dc;break;case "wheel":a=kk;break;case "copy":case "cut":case "paste":a=ck;break;case "gotpointercapture":case "lostpointercapture":case "pointercancel":case "pointerdown":case "pointermove":case "pointerout":case "pointerover":case "pointerup":a=hi;break;default:a=R}b=a.getPooled(e,b,c,d);lb(b);return b}};(function(a){if(ic)throw Error(k(101));
ic=Array.prototype.slice.call(a);nf()})("ResponderEventPlugin SimpleEventPlugin EnterLeaveEventPlugin ChangeEventPlugin SelectEventPlugin BeforeInputEventPlugin".split(" "));(function(a,b,c){td=a;rf=b;mf=c})(ae,Hb,Pa);pf({SimpleEventPlugin:lk,EnterLeaveEventPlugin:Yj,ChangeEventPlugin:Xj,SelectEventPlugin:ak,BeforeInputEventPlugin:Wj});var ie=[],ob=-1,Ca={},B={current:Ca},G={current:!1},Ra=Ca,bj=Pd,je=$f,Rg=Lj,aj=Nj,Dc=Oj,Ig=Zh,Jg=ag,Kg=Pj,Lg=Qj,Qg={},yj=Mj,Cj=void 0!==Yh?Yh:function(){},qa=null,
Ec=null,ke=!1,ii=ff(),Y=1E4>ii?ff:function(){return ff()-ii},Ic={current:null},Hc=null,qb=null,Gc=null,Tg=0,Jc=2,Ga=!1,Vb=da.ReactCurrentBatchConfig,$g=(new ea.Component).refs,Mc={isMounted:function(a){return(a=a._reactInternalFiber)?Na(a)===a:!1},enqueueSetState:function(a,b,c){a=a._reactInternalFiber;var d=ka(),e=Vb.suspense;d=Va(d,a,e);e=Ea(d,e);e.payload=b;void 0!==c&&null!==c&&(e.callback=c);Fa(a,e);Ja(a,d)},enqueueReplaceState:function(a,b,c){a=a._reactInternalFiber;var d=ka(),e=Vb.suspense;
d=Va(d,a,e);e=Ea(d,e);e.tag=1;e.payload=b;void 0!==c&&null!==c&&(e.callback=c);Fa(a,e);Ja(a,d)},enqueueForceUpdate:function(a,b){a=a._reactInternalFiber;var c=ka(),d=Vb.suspense;c=Va(c,a,d);d=Ea(c,d);d.tag=Jc;void 0!==b&&null!==b&&(d.callback=b);Fa(a,d);Ja(a,c)}},Qc=Array.isArray,wb=ah(!0),Fe=ah(!1),Sb={},ja={current:Sb},Ub={current:Sb},Tb={current:Sb},D={current:0},Sc=da.ReactCurrentDispatcher,X=da.ReactCurrentBatchConfig,Ia=0,z=null,K=null,J=null,Uc=!1,Tc={readContext:W,useCallback:S,useContext:S,
useEffect:S,useImperativeHandle:S,useLayoutEffect:S,useMemo:S,useReducer:S,useRef:S,useState:S,useDebugValue:S,useResponder:S,useDeferredValue:S,useTransition:S},dj={readContext:W,useCallback:ih,useContext:W,useEffect:eh,useImperativeHandle:function(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return ze(4,2,gh.bind(null,b,a),c)},useLayoutEffect:function(a,b){return ze(4,2,a,b)},useMemo:function(a,b){var c=ub();b=void 0===b?null:b;a=a();c.memoizedState=[a,b];return a},useReducer:function(a,b,c){var d=
ub();b=void 0!==c?c(b):b;d.memoizedState=d.baseState=b;a=d.queue={pending:null,dispatch:null,lastRenderedReducer:a,lastRenderedState:b};a=a.dispatch=ch.bind(null,z,a);return[d.memoizedState,a]},useRef:function(a){var b=ub();a={current:a};return b.memoizedState=a},useState:xe,useDebugValue:Be,useResponder:ue,useDeferredValue:function(a,b){var c=xe(a),d=c[0],e=c[1];eh(function(){var c=X.suspense;X.suspense=void 0===b?null:b;try{e(a)}finally{X.suspense=c}},[a,b]);return d},useTransition:function(a){var b=
xe(!1),c=b[0];b=b[1];return[ih(Ce.bind(null,b,a),[b,a]),c]}},ej={readContext:W,useCallback:Yc,useContext:W,useEffect:Xc,useImperativeHandle:hh,useLayoutEffect:fh,useMemo:jh,useReducer:Vc,useRef:dh,useState:function(a){return Vc(Ua)},useDebugValue:Be,useResponder:ue,useDeferredValue:function(a,b){var c=Vc(Ua),d=c[0],e=c[1];Xc(function(){var c=X.suspense;X.suspense=void 0===b?null:b;try{e(a)}finally{X.suspense=c}},[a,b]);return d},useTransition:function(a){var b=Vc(Ua),c=b[0];b=b[1];return[Yc(Ce.bind(null,
b,a),[b,a]),c]}},fj={readContext:W,useCallback:Yc,useContext:W,useEffect:Xc,useImperativeHandle:hh,useLayoutEffect:fh,useMemo:jh,useReducer:Wc,useRef:dh,useState:function(a){return Wc(Ua)},useDebugValue:Be,useResponder:ue,useDeferredValue:function(a,b){var c=Wc(Ua),d=c[0],e=c[1];Xc(function(){var c=X.suspense;X.suspense=void 0===b?null:b;try{e(a)}finally{X.suspense=c}},[a,b]);return d},useTransition:function(a){var b=Wc(Ua),c=b[0];b=b[1];return[Yc(Ce.bind(null,b,a),[b,a]),c]}},ra=null,Ka=null,Wa=
!1,gj=da.ReactCurrentOwner,ia=!1,Je={dehydrated:null,retryTime:0};var jj=function(a,b,c,d){for(c=b.child;null!==c;){if(5===c.tag||6===c.tag)a.appendChild(c.stateNode);else if(4!==c.tag&&null!==c.child){c.child.return=c;c=c.child;continue}if(c===b)break;for(;null===c.sibling;){if(null===c.return||c.return===b)return;c=c.return}c.sibling.return=c.return;c=c.sibling}};var wh=function(a){};var ij=function(a,b,c,d,e){var f=a.memoizedProps;if(f!==d){var g=b.stateNode;Ta(ja.current);a=null;switch(c){case "input":f=
Cd(g,f);d=Cd(g,d);a=[];break;case "option":f=Fd(g,f);d=Fd(g,d);a=[];break;case "select":f=M({},f,{value:void 0});d=M({},d,{value:void 0});a=[];break;case "textarea":f=Gd(g,f);d=Gd(g,d);a=[];break;default:"function"!==typeof f.onClick&&"function"===typeof d.onClick&&(g.onclick=uc)}Ud(c,d);var h,m;c=null;for(h in f)if(!d.hasOwnProperty(h)&&f.hasOwnProperty(h)&&null!=f[h])if("style"===h)for(m in g=f[h],g)g.hasOwnProperty(m)&&(c||(c={}),c[m]="");else"dangerouslySetInnerHTML"!==h&&"children"!==h&&"suppressContentEditableWarning"!==
h&&"suppressHydrationWarning"!==h&&"autoFocus"!==h&&(db.hasOwnProperty(h)?a||(a=[]):(a=a||[]).push(h,null));for(h in d){var k=d[h];g=null!=f?f[h]:void 0;if(d.hasOwnProperty(h)&&k!==g&&(null!=k||null!=g))if("style"===h)if(g){for(m in g)!g.hasOwnProperty(m)||k&&k.hasOwnProperty(m)||(c||(c={}),c[m]="");for(m in k)k.hasOwnProperty(m)&&g[m]!==k[m]&&(c||(c={}),c[m]=k[m])}else c||(a||(a=[]),a.push(h,c)),c=k;else"dangerouslySetInnerHTML"===h?(k=k?k.__html:void 0,g=g?g.__html:void 0,null!=k&&g!==k&&(a=a||
[]).push(h,k)):"children"===h?g===k||"string"!==typeof k&&"number"!==typeof k||(a=a||[]).push(h,""+k):"suppressContentEditableWarning"!==h&&"suppressHydrationWarning"!==h&&(db.hasOwnProperty(h)?(null!=k&&oa(e,h),a||g===k||(a=[])):(a=a||[]).push(h,k))}c&&(a=a||[]).push("style",c);e=a;if(b.updateQueue=e)b.effectTag|=4}};var kj=function(a,b,c,d){c!==d&&(b.effectTag|=4)};var pj="function"===typeof WeakSet?WeakSet:Set,wj="function"===typeof WeakMap?WeakMap:Map,sj=Math.ceil,gd=da.ReactCurrentDispatcher,
Uh=da.ReactCurrentOwner,H=0,Ye=8,ca=16,ma=32,Xa=0,hd=1,Oh=2,ad=3,bd=4,Xe=5,p=H,U=null,t=null,P=0,F=Xa,id=null,ta=1073741823,Yb=1073741823,kd=null,Xb=0,jd=!1,Re=0,Ph=500,l=null,cd=!1,Se=null,La=null,ld=!1,Zb=null,$b=90,bb=null,ac=0,af=null,dd=0,Ja=function(a,b){if(50<ac)throw ac=0,af=null,Error(k(185));a=ed(a,b);if(null!==a){var c=Cc();1073741823===b?(p&Ye)!==H&&(p&(ca|ma))===H?Te(a):(V(a),p===H&&ha()):V(a);(p&4)===H||98!==c&&99!==c||(null===bb?bb=new Map([[a,b]]):(c=bb.get(a),(void 0===c||c>b)&&bb.set(a,
b)))}};var zj=function(a,b,c){var d=b.expirationTime;if(null!==a){var e=b.pendingProps;if(a.memoizedProps!==e||G.current)ia=!0;else{if(d<c){ia=!1;switch(b.tag){case 3:sh(b);Ee();break;case 5:bh(b);if(b.mode&4&&1!==c&&e.hidden)return b.expirationTime=b.childExpirationTime=1,null;break;case 1:N(b.type)&&Bc(b);break;case 4:se(b,b.stateNode.containerInfo);break;case 10:d=b.memoizedProps.value;e=b.type._context;y(Ic,e._currentValue);e._currentValue=d;break;case 13:if(null!==b.memoizedState){d=b.child.childExpirationTime;
if(0!==d&&d>=c)return th(a,b,c);y(D,D.current&1);b=sa(a,b,c);return null!==b?b.sibling:null}y(D,D.current&1);break;case 19:d=b.childExpirationTime>=c;if(0!==(a.effectTag&64)){if(d)return vh(a,b,c);b.effectTag|=64}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null);y(D,D.current);if(!d)return null}return sa(a,b,c)}ia=!1}}else ia=!1;b.expirationTime=0;switch(b.tag){case 2:d=b.type;null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2);a=b.pendingProps;e=pb(b,B.current);rb(b,c);e=we(null,
b,d,a,e,c);b.effectTag|=1;if("object"===typeof e&&null!==e&&"function"===typeof e.render&&void 0===e.$$typeof){b.tag=1;b.memoizedState=null;b.updateQueue=null;if(N(d)){var f=!0;Bc(b)}else f=!1;b.memoizedState=null!==e.state&&void 0!==e.state?e.state:null;ne(b);var g=d.getDerivedStateFromProps;"function"===typeof g&&Lc(b,d,g,a);e.updater=Mc;b.stateNode=e;e._reactInternalFiber=b;pe(b,d,a,c);b=Ie(null,b,d,!0,f,c)}else b.tag=0,T(null,b,e,c),b=b.child;return b;case 16:a:{e=b.elementType;null!==a&&(a.alternate=
null,b.alternate=null,b.effectTag|=2);a=b.pendingProps;ri(e);if(1!==e._status)throw e._result;e=e._result;b.type=e;f=b.tag=Gj(e);a=aa(e,a);switch(f){case 0:b=He(null,b,e,a,c);break a;case 1:b=rh(null,b,e,a,c);break a;case 11:b=nh(null,b,e,a,c);break a;case 14:b=oh(null,b,e,aa(e.type,a),d,c);break a}throw Error(k(306,e,""));}return b;case 0:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),He(a,b,d,e,c);case 1:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),rh(a,b,d,e,c);
case 3:sh(b);d=b.updateQueue;if(null===a||null===d)throw Error(k(282));d=b.pendingProps;e=b.memoizedState;e=null!==e?e.element:null;oe(a,b);Qb(b,d,null,c);d=b.memoizedState.element;if(d===e)Ee(),b=sa(a,b,c);else{if(e=b.stateNode.hydrate)Ka=kb(b.stateNode.containerInfo.firstChild),ra=b,e=Wa=!0;if(e)for(c=Fe(b,null,d,c),b.child=c;c;)c.effectTag=c.effectTag&-3|1024,c=c.sibling;else T(a,b,d,c),Ee();b=b.child}return b;case 5:return bh(b),null===a&&De(b),d=b.type,e=b.pendingProps,f=null!==a?a.memoizedProps:
null,g=e.children,Yd(d,e)?g=null:null!==f&&Yd(d,f)&&(b.effectTag|=16),qh(a,b),b.mode&4&&1!==c&&e.hidden?(b.expirationTime=b.childExpirationTime=1,b=null):(T(a,b,g,c),b=b.child),b;case 6:return null===a&&De(b),null;case 13:return th(a,b,c);case 4:return se(b,b.stateNode.containerInfo),d=b.pendingProps,null===a?b.child=wb(b,null,d,c):T(a,b,d,c),b.child;case 11:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),nh(a,b,d,e,c);case 7:return T(a,b,b.pendingProps,c),b.child;case 8:return T(a,
b,b.pendingProps.children,c),b.child;case 12:return T(a,b,b.pendingProps.children,c),b.child;case 10:a:{d=b.type._context;e=b.pendingProps;g=b.memoizedProps;f=e.value;var h=b.type._context;y(Ic,h._currentValue);h._currentValue=f;if(null!==g)if(h=g.value,f=Qa(h,f)?0:("function"===typeof d._calculateChangedBits?d._calculateChangedBits(h,f):1073741823)|0,0===f){if(g.children===e.children&&!G.current){b=sa(a,b,c);break a}}else for(h=b.child,null!==h&&(h.return=b);null!==h;){var m=h.dependencies;if(null!==
m){g=h.child;for(var l=m.firstContext;null!==l;){if(l.context===d&&0!==(l.observedBits&f)){1===h.tag&&(l=Ea(c,null),l.tag=Jc,Fa(h,l));h.expirationTime<c&&(h.expirationTime=c);l=h.alternate;null!==l&&l.expirationTime<c&&(l.expirationTime=c);Sg(h.return,c);m.expirationTime<c&&(m.expirationTime=c);break}l=l.next}}else g=10===h.tag?h.type===b.type?null:h.child:h.child;if(null!==g)g.return=h;else for(g=h;null!==g;){if(g===b){g=null;break}h=g.sibling;if(null!==h){h.return=g.return;g=h;break}g=g.return}h=
g}T(a,b,e.children,c);b=b.child}return b;case 9:return e=b.type,f=b.pendingProps,d=f.children,rb(b,c),e=W(e,f.unstable_observedBits),d=d(e),b.effectTag|=1,T(a,b,d,c),b.child;case 14:return e=b.type,f=aa(e,b.pendingProps),f=aa(e.type,f),oh(a,b,e,f,d,c);case 15:return ph(a,b,b.type,b.pendingProps,d,c);case 17:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2),b.tag=1,N(d)?(a=!0,Bc(b)):a=!1,rb(b,c),Yg(b,d,e),pe(b,d,e,c),Ie(null,
b,d,!0,a,c);case 19:return vh(a,b,c)}throw Error(k(156,b.tag));};var bf=null,Ne=null,la=function(a,b,c,d){return new Fj(a,b,c,d)};ef.prototype.render=function(a){md(a,this._internalRoot,null,null)};ef.prototype.unmount=function(){var a=this._internalRoot,b=a.containerInfo;md(null,a,null,function(){b[Lb]=null})};var Di=function(a){if(13===a.tag){var b=Fc(ka(),150,100);Ja(a,b);df(a,b)}};var Yf=function(a){13===a.tag&&(Ja(a,3),df(a,3))};var Bi=function(a){if(13===a.tag){var b=ka();b=Va(b,a,null);Ja(a,
b);df(a,b)}};sd=function(a,b,c){switch(b){case "input":Dd(a,c);b=c.name;if("radio"===c.type&&null!=b){for(c=a;c.parentNode;)c=c.parentNode;c=c.querySelectorAll("input[name="+JSON.stringify(""+b)+'][type="radio"]');for(b=0;b<c.length;b++){var d=c[b];if(d!==a&&d.form===a.form){var e=ae(d);if(!e)throw Error(k(90));Gf(d);Dd(d,e)}}}break;case "textarea":Lf(a,c);break;case "select":b=c.value,null!=b&&hb(a,!!c.multiple,b,!1)}};(function(a,b,c,d){ee=a;eg=b;vd=c;vf=d})(Qh,function(a,b,c,d,e){var f=p;p|=4;
try{return Da(98,a.bind(null,b,c,d,e))}finally{p=f,p===H&&ha()}},function(){(p&(1|ca|ma))===H&&(uj(),xb())},function(a,b){var c=p;p|=2;try{return a(b)}finally{p=c,p===H&&ha()}});var mk={Events:[Hb,Pa,ae,pf,qd,lb,function(a){Kd(a,Ki)},sf,tf,sc,pc,xb,{current:!1}]};(function(a){var b=a.findFiberByHostInstance;return Ej(M({},a,{overrideHookState:null,overrideProps:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:da.ReactCurrentDispatcher,findHostInstanceByFiber:function(a){a=Sf(a);
return null===a?null:a.stateNode},findFiberByHostInstance:function(a){return b?b(a):null},findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null}))})({findFiberByHostInstance:Bb,bundleType:0,version:"16.13.1",rendererPackageName:"react-dom"});I.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=mk;I.createPortal=Xh;I.findDOMNode=function(a){if(null==a)return null;if(1===a.nodeType)return a;var b=a._reactInternalFiber;if(void 0===
b){if("function"===typeof a.render)throw Error(k(188));throw Error(k(268,Object.keys(a)));}a=Sf(b);a=null===a?null:a.stateNode;return a};I.flushSync=function(a,b){if((p&(ca|ma))!==H)throw Error(k(187));var c=p;p|=1;try{return Da(99,a.bind(null,b))}finally{p=c,ha()}};I.hydrate=function(a,b,c){if(!bc(b))throw Error(k(200));return nd(null,a,b,!0,c)};I.render=function(a,b,c){if(!bc(b))throw Error(k(200));return nd(null,a,b,!1,c)};I.unmountComponentAtNode=function(a){if(!bc(a))throw Error(k(40));return a._reactRootContainer?
(Rh(function(){nd(null,null,a,!1,function(){a._reactRootContainer=null;a[Lb]=null})}),!0):!1};I.unstable_batchedUpdates=Qh;I.unstable_createPortal=function(a,b){return Xh(a,b,2<arguments.length&&void 0!==arguments[2]?arguments[2]:null)};I.unstable_renderSubtreeIntoContainer=function(a,b,c,d){if(!bc(c))throw Error(k(200));if(null==a||void 0===a._reactInternalFiber)throw Error(k(38));return nd(a,b,c,!1,d)};I.version="16.13.1"});
</script>
<script>const e = React.createElement;
function pathToString(path) {
if (path[0] === '/') {
return '/' + path.slice(1).join('/');
} else {
return path.join('/');
}
}
function findCommonPath(files) {
if (!files || !files.length) {
return [];
}
function isPrefix(arr, prefix) {
if (arr.length < prefix.length) {
return false;
}
for (let i = prefix.length - 1; i >= 0; --i) {
if (arr[i] !== prefix[i]) {
return false;
}
}
return true;
}
let commonPath = files[0].path.slice(0, -1);
while (commonPath.length) {
if (files.every(file => isPrefix(file.path, commonPath))) {
break;
}
commonPath.pop();
}
return commonPath;
}
function findFolders(files) {
if (!files || !files.length) {
return [];
}
let folders = files.filter(file => file.path.length > 1).map(file => file.path[0]);
folders = [...new Set(folders)]; // unique
folders.sort();
folders = folders.map(folder => {
let filesInFolder = files
.filter(file => file.path[0] === folder)
.map(file => ({
...file,
path: file.path.slice(1),
parent: [...file.parent, file.path[0]],
}));
const children = findFolders(filesInFolder); // recursion
return {
is_folder: true,
path: [folder],
parent: files[0].parent,
children,
covered: children.reduce((sum, file) => sum + file.covered, 0),
coverable: children.reduce((sum, file) => sum + file.coverable, 0),
prevRun: {
covered: children.reduce((sum, file) => sum + file.prevRun.covered, 0),
coverable: children.reduce((sum, file) => sum + file.prevRun.coverable, 0),
},
};
});
return [...folders, ...files.filter(file => file.path.length === 1)];
}
class App extends React.Component {
constructor(...args) {
super(...args);
this.state = {
current: [],
};
}
componentDidMount() {
this.updateStateFromLocation();
window.addEventListener('hashchange', () => this.updateStateFromLocation(), false);
}
updateStateFromLocation() {
if (window.location.hash.length > 1) {
const current = window.location.hash.slice(1).split('/');
this.setState({current});
} else {
this.setState({current: []});
}
}
getCurrentPath() {
let file = this.props.root;
let path = [file];
for (let p of this.state.current) {
file = file.children.find(file => file.path[0] === p);
if (!file) {
return path;
}
path.push(file);
}
return path;
}
render() {
const path = this.getCurrentPath();
const file = path[path.length - 1];
let w = null;
if (file.is_folder) {
w = e(FilesList, {
folder: file,
onSelectFile: this.selectFile.bind(this),
onBack: path.length > 1 ? this.back.bind(this) : null,
});
} else {
w = e(DisplayFile, {
file,
onBack: this.back.bind(this),
});
}
return e('div', {className: 'app'}, w);
}
selectFile(file) {
this.setState(
({current}) => {
return {current: [...current, file.path[0]]};
},
() => this.updateHash(),
);
}
back(file) {
this.setState(
({current}) => {
return {current: current.slice(0, current.length - 1)};
},
() => this.updateHash(),
);
}
updateHash() {
if (!this.state.current || !this.state.current.length) {
window.location = '#';
} else {
window.location = '#' + this.state.current.join('/');
}
}
}
function FilesList({folder, onSelectFile, onBack}) {
let files = folder.children;
return e(
'div',
{className: 'display-folder'},
e(FileHeader, {file: folder, onBack}),
e(
'table',
{className: 'files-list'},
e('thead', {className: 'files-list__head'}, e('tr', null, e('th', null, 'Path'), e('th', null, 'Coverage'))),
e(
'tbody',
{className: 'files-list__body'},
files.map(file => e(File, {file, onClick: onSelectFile})),
),
),
);
}
function File({file, onClick}) {
const coverage = file.coverable ? (file.covered / file.coverable) * 100 : -1;
const coverageDelta =
file.prevRun && (file.covered / file.coverable) * 100 - (file.prevRun.covered / file.prevRun.coverable) * 100;
return e(
'tr',
{
className:
'files-list__file' +
(coverage >= 0 && coverage < 50 ? ' files-list__file_low' : '') +
(coverage >= 50 && coverage < 80 ? ' files-list__file_medium' : '') +
(coverage >= 80 ? ' files-list__file_high' : '') +
(file.is_folder ? ' files-list__file_folder' : ''),
onClick: () => onClick(file),
},
e('td', null, e('a', null, pathToString(file.path))),
e(
'td',
null,
file.covered + ' / ' + file.coverable + (coverage >= 0 ? ' (' + coverage.toFixed(2) + '%)' : ''),
e(
'span',
{title: 'Change from the previous run'},
coverageDelta ? ` (${coverageDelta > 0 ? '+' : ''}${coverageDelta.toFixed(2)}%)` : '',
),
),
);
}
function DisplayFile({file, onBack}) {
return e('div', {className: 'display-file'}, e(FileHeader, {file, onBack}), e(FileContent, {file}));
}
function FileHeader({file, onBack}) {
const coverage = (file.covered / file.coverable) * 100;
const coverageDelta = file.prevRun && coverage - (file.prevRun.covered / file.prevRun.coverable) * 100;
return e(
'div',
{className: 'file-header'},
onBack ? e('a', {className: 'file-header__back', onClick: onBack}, 'Back') : null,
e('div', {className: 'file-header__name'}, pathToString([...file.parent, ...file.path])),
e(
'div',
{className: 'file-header__stat'},
'Covered: ' + file.covered + ' of ' + file.coverable + (file.coverable ? ' (' + coverage.toFixed(2) + '%)' : ''),
e(
'span',
{title: 'Change from the previous run'},
coverageDelta ? ` (${coverageDelta > 0 ? '+' : ''}${coverageDelta.toFixed(2)}%)` : '',
),
e('input', {id: 'theme-toggle', type: 'checkbox', hidden: true}),
e('label', {for: 'theme-toggle', id: 'theme-toggle-label'}, '🌙'),
),
);
}
function FileContent({file}) {
return e(
'pre',
{className: 'file-content'},
file.content.split(/\r?\n/).map((line, index) => {
const trace = file.traces.find(trace => trace.line === index + 1);
const covered = trace && trace.stats.Line;
const uncovered = trace && !trace.stats.Line;
return e(
'code',
{
className: 'code-line' + (covered ? ' code-line_covered' : '') + (uncovered ? ' code-line_uncovered' : ''),
title: trace ? JSON.stringify(trace.stats, null, 2) : null,
},
line,
);
}),
);
}
(function () {
const commonPath = findCommonPath(data.files);
const prevFilesMap = new Map();
previousData &&
previousData.files.forEach(file => {
const path = file.path.slice(commonPath.length).join('/');
prevFilesMap.set(path, file);
});
const files = data.files.map(file => {
const path = file.path.slice(commonPath.length);
const {covered = 0, coverable = 0} = prevFilesMap.get(path.join('/')) || {};
return {
...file,
path,
parent: commonPath,
prevRun: {covered, coverable},
};
});
const children = findFolders(files);
const root = {
is_folder: true,
children,
path: commonPath,
parent: [],
covered: children.reduce((sum, file) => sum + file.covered, 0),
coverable: children.reduce((sum, file) => sum + file.coverable, 0),
prevRun: {
covered: children.reduce((sum, file) => sum + file.prevRun.covered, 0),
coverable: children.reduce((sum, file) => sum + file.prevRun.coverable, 0),
},
};
ReactDOM.render(e(App, {root, prevFilesMap}), document.getElementById('root'));
const toggle = document.getElementById('theme-toggle');
const label = document.getElementById('theme-toggle-label');
label.textContent = '🌙';
toggle.addEventListener('change', () => {
if (toggle.checked) {
document.documentElement.setAttribute('data-theme', 'dark');
label.textContent = '☀️';
} else {
document.documentElement.removeAttribute('data-theme');
label.textContent = '🌙';
}
});
})();
</script>
</body>
</html>