feat(ws): align auth and event_type parsing

This commit is contained in:
floor-licker
2026-01-29 18:35:02 -05:00
parent 703bd9f509
commit a7ca8e4341
5 changed files with 506 additions and 305 deletions
+58 -49
View File
@@ -26,7 +26,7 @@ use polyfill_rs::{
types::*,
// Utility functions
utils::{address, crypto, math, rate_limit, retry, time, url},
utils::{address, math, rate_limit, retry, time, url},
// Configuration
ClientConfig,
@@ -43,7 +43,7 @@ use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::time::Duration;
use tokio::time::sleep;
use tracing::{debug, error, info};
use tracing::{error, info};
/// Demo showcasing polyfill-rs functionality
#[allow(dead_code)]
@@ -592,10 +592,9 @@ impl PolyfillDemo {
initial_dump: Some(true),
custom_feature_enabled: None,
auth: Some(WssAuth {
address: "0x1234567890123456789012345678901234567890".to_string(),
signature: "mock_signature".to_string(),
timestamp: time::now_secs(),
nonce: crypto::generate_nonce().to_string(),
api_key: "test-api-key".to_string(),
secret: "test-secret".to_string(),
passphrase: "test-passphrase".to_string(),
}),
};
@@ -603,33 +602,33 @@ impl PolyfillDemo {
// Simulate receiving stream messages
let messages = vec![
StreamMessage::Heartbeat {
timestamp: chrono::Utc::now(),
},
StreamMessage::BookUpdate {
data: OrderDelta {
token_id: "12345".to_string(),
timestamp: chrono::Utc::now(),
side: Side::BUY,
StreamMessage::Book(BookUpdate {
asset_id: "12345".to_string(),
market: "market1".to_string(),
timestamp: time::now_millis(),
bids: vec![OrderSummary {
price: dec!(0.75),
size: dec!(100.0),
sequence: 1,
},
},
StreamMessage::Trade {
data: FillEvent {
id: "fill1".to_string(),
order_id: "order1".to_string(),
token_id: "12345".to_string(),
side: Side::BUY,
price: dec!(0.75),
}],
asks: vec![OrderSummary {
price: dec!(0.76),
size: dec!(50.0),
timestamp: chrono::Utc::now(),
maker_address: alloy_primitives::Address::ZERO,
taker_address: alloy_primitives::Address::ZERO,
fee: dec!(0.375),
},
},
}],
hash: None,
}),
StreamMessage::Trade(TradeMessage {
id: "fill1".to_string(),
market: "market1".to_string(),
asset_id: "12345".to_string(),
side: Side::BUY,
size: dec!(50.0),
price: dec!(0.75),
status: Some("MATCHED".to_string()),
msg_type: None,
last_update: None,
matchtime: None,
timestamp: None,
}),
];
for message in messages {
@@ -638,31 +637,41 @@ impl PolyfillDemo {
// Process message based on type
match &message {
StreamMessage::BookUpdate { data } => {
info!(" Processing book update for token: {}", data.token_id);
if let Err(e) = self.book_manager.apply_delta(data.clone()) {
error!(" Failed to apply book update: {}", e);
self.stats.errors += 1;
StreamMessage::Book(book) => {
info!(" Processing book update for asset: {}", book.asset_id);
// This is a demo: apply snapshot levels as deltas.
for level in &book.bids {
let _ = self.book_manager.apply_delta(OrderDelta {
token_id: book.asset_id.clone(),
timestamp: chrono::Utc::now(),
side: Side::BUY,
price: level.price,
size: level.size,
sequence: book.timestamp,
});
}
},
StreamMessage::Trade { data } => {
for level in &book.asks {
let _ = self.book_manager.apply_delta(OrderDelta {
token_id: book.asset_id.clone(),
timestamp: chrono::Utc::now(),
side: Side::SELL,
price: level.price,
size: level.size,
sequence: book.timestamp,
});
}
}
StreamMessage::Trade(trade) => {
info!(
" Processing trade: {} {} @ {}",
data.side.as_str(),
data.size,
data.price
trade.side.as_str(),
trade.size,
trade.price
);
if let Err(e) = self.fill_processor.process_fill(data.clone()) {
error!(" Failed to process fill: {}", e);
self.stats.errors += 1;
}
},
StreamMessage::Heartbeat { timestamp } => {
debug!(" Received heartbeat at: {}", timestamp);
},
}
_ => {
info!(" Unhandled message type");
},
}
}
}
+79 -41
View File
@@ -101,43 +101,86 @@ impl SnipeStrategy {
/// Process a market data update
pub fn process_update(&mut self, message: StreamMessage) -> Result<()> {
match message {
StreamMessage::BookUpdate { data } => {
if data.token_id == self.token_id {
self.process_book_update(data)?;
StreamMessage::Book(book) => {
if book.asset_id == self.token_id {
self.process_book_update(book)?;
}
},
StreamMessage::Trade { data } => {
if data.token_id == self.token_id {
self.process_trade(data)?;
StreamMessage::Trade(trade) => {
if trade.asset_id == self.token_id {
self.process_trade(trade)?;
}
},
StreamMessage::Heartbeat { timestamp: _ } => {
self.check_stale_quotes()?;
},
_ => {},
}
// Opportunistically check for staleness on any incoming update.
self.check_stale_quotes()?;
Ok(())
}
/// Process order book update
fn process_book_update(&mut self, delta: OrderDelta) -> Result<()> {
fn process_book_update(&mut self, book: BookUpdate) -> Result<()> {
// Ensure book exists
self.book_manager.get_or_create_book(&self.token_id)?;
// Update local order book
self.book_manager.apply_delta(delta.clone())?;
// Clear the existing book and rebuild from the snapshot.
if let Ok(current) = self.book_manager.get_book(&self.token_id) {
for level in &current.bids {
let _ = self.book_manager.apply_delta(OrderDelta {
token_id: self.token_id.clone(),
timestamp: chrono::Utc::now(),
side: Side::BUY,
price: level.price,
size: Decimal::ZERO,
sequence: book.timestamp,
});
}
// Get current book state
let book = self.book_manager.get_book(&self.token_id)?;
for level in &current.asks {
let _ = self.book_manager.apply_delta(OrderDelta {
token_id: self.token_id.clone(),
timestamp: chrono::Utc::now(),
side: Side::SELL,
price: level.price,
size: Decimal::ZERO,
sequence: book.timestamp,
});
}
}
// Update best prices
if let Some(best_bid) = book.bids.first() {
self.last_best_bid = Some(best_bid.price);
let ts = chrono::DateTime::from_timestamp(
(book.timestamp / 1000) as i64,
((book.timestamp % 1000) * 1_000_000) as u32,
)
.unwrap_or_else(chrono::Utc::now);
for level in &book.bids {
let _ = self.book_manager.apply_delta(OrderDelta {
token_id: self.token_id.clone(),
timestamp: ts,
side: Side::BUY,
price: level.price,
size: level.size,
sequence: book.timestamp,
});
}
if let Some(best_ask) = book.asks.first() {
self.last_best_ask = Some(best_ask.price);
for level in &book.asks {
let _ = self.book_manager.apply_delta(OrderDelta {
token_id: self.token_id.clone(),
timestamp: ts,
side: Side::SELL,
price: level.price,
size: level.size,
sequence: book.timestamp,
});
}
// Update best prices directly from the snapshot
self.last_best_bid = book.bids.first().map(|l| l.price);
self.last_best_ask = book.asks.first().map(|l| l.price);
self.last_update = time::now_secs();
// Check for trading opportunities
@@ -147,17 +190,17 @@ impl SnipeStrategy {
}
/// Process trade update
fn process_trade(&mut self, fill: FillEvent) -> Result<()> {
fn process_trade(&mut self, trade: TradeMessage) -> Result<()> {
info!(
"Trade: {} {} @ {} (size: {})",
fill.side.as_str(),
fill.token_id,
fill.price,
fill.size
trade.side.as_str(),
trade.asset_id,
trade.price,
trade.size
);
// Update statistics
self.stats.total_volume += fill.size;
self.stats.total_volume += trade.size;
// Calculate P&L if this was our trade
// (In a real implementation, you'd track your own orders)
@@ -327,24 +370,19 @@ impl MockMarketData {
let price_change = random_factor * Decimal::from(2) * self.volatility;
let new_price = self.base_price * (Decimal::from(1) + price_change);
// Generate order book update
let side = if rand::random::<bool>() {
Side::BUY
} else {
Side::SELL
};
// Generate a simple orderbook snapshot update
let size = Decimal::from(rand::random::<u64>() % 1000 + 100);
let bid = new_price - dec!(0.01);
let ask = new_price + dec!(0.01);
StreamMessage::BookUpdate {
data: OrderDelta {
token_id: self.token_id.clone(),
timestamp: chrono::Utc::now(),
side,
price: new_price,
size,
sequence: self.sequence,
},
}
StreamMessage::Book(BookUpdate {
asset_id: self.token_id.clone(),
market: "0xmock".to_string(),
timestamp: time::now_millis(),
bids: vec![OrderSummary { price: bid, size }],
asks: vec![OrderSummary { price: ask, size }],
hash: None,
})
}
}