use thiserror::Error;
use crate::types::{Blockhash, MessageType, NetworkState, OrderLevel, PoolUpdate, PriorityFees};
use crate::ws::client::DecodedMessage;
#[derive(Debug, Error)]
pub enum DecodeError {
#[error("Payload too short: expected {expected}, got {actual}")]
PayloadTooShort { expected: usize, actual: usize },
#[error("Invalid UTF-8: {0}")]
InvalidUtf8(#[from] std::string::FromUtf8Error),
#[error("Invalid message type: {0}")]
InvalidMessageType(u8),
#[error("Invalid network state: {0}")]
InvalidNetworkState(u8),
}
pub fn decode_message(msg_type: u8, payload: &[u8]) -> Result<Option<DecodedMessage>, DecodeError> {
let msg_type = MessageType::try_from(msg_type).map_err(DecodeError::InvalidMessageType)?;
match msg_type {
MessageType::PoolUpdate => {
let update = decode_pool_update(payload)?;
Ok(Some(DecodedMessage::PoolUpdate(update)))
}
MessageType::PoolUpdateBatch => {
let updates = decode_pool_update_batch(payload)?;
Ok(Some(DecodedMessage::PoolUpdateBatch(updates)))
}
MessageType::PriorityFees => {
let fees = decode_priority_fees(payload)?;
Ok(Some(DecodedMessage::PriorityFees(fees)))
}
MessageType::Blockhash => {
let bh = decode_blockhash(payload)?;
Ok(Some(DecodedMessage::Blockhash(bh)))
}
MessageType::Error => {
let msg = String::from_utf8(payload.to_vec())?;
Ok(Some(DecodedMessage::Error(msg)))
}
MessageType::Pong => Ok(None),
_ => Ok(None),
}
}
fn decode_pool_update(data: &[u8]) -> Result<PoolUpdate, DecodeError> {
let mut offset = 0;
let state_len = read_u64(data, &mut offset)?;
if offset + state_len as usize > data.len() {
return Err(DecodeError::PayloadTooShort {
expected: offset + state_len as usize,
actual: data.len(),
});
}
let serialized_state = data[offset..offset + state_len as usize].to_vec();
offset += state_len as usize;
let sequence = read_u64(data, &mut offset)?;
let slot = read_u64(data, &mut offset)?;
let write_version = read_u64(data, &mut offset)?;
let name_len = read_u64(data, &mut offset)?;
if offset + name_len as usize > data.len() {
return Err(DecodeError::PayloadTooShort {
expected: offset + name_len as usize,
actual: data.len(),
});
}
let protocol_name = String::from_utf8(data[offset..offset + name_len as usize].to_vec())?;
offset += name_len as usize;
if offset + 32 > data.len() {
return Err(DecodeError::PayloadTooShort {
expected: offset + 32,
actual: data.len(),
});
}
let pool_address = bs58::encode(&data[offset..offset + 32]).into_string();
offset += 32;
let num_mints = read_u64(data, &mut offset)?;
if offset + (num_mints as usize) * 32 > data.len() {
return Err(DecodeError::PayloadTooShort {
expected: offset + (num_mints as usize) * 32,
actual: data.len(),
});
}
let mut token_mints = Vec::with_capacity(num_mints as usize);
for _ in 0..num_mints {
token_mints.push(bs58::encode(&data[offset..offset + 32]).into_string());
offset += 32;
}
let num_balances = read_u64(data, &mut offset)?;
let mut token_balances = Vec::with_capacity(num_balances as usize);
for _ in 0..num_balances {
token_balances.push(read_u64(data, &mut offset)?);
}
let num_decimals = read_u64(data, &mut offset)?;
let mut token_decimals = Vec::with_capacity(num_decimals as usize);
for _ in 0..num_decimals {
token_decimals.push(read_i32(data, &mut offset)?);
}
let best_bid = decode_optional_order_level(data, &mut offset)?;
let best_ask = decode_optional_order_level(data, &mut offset)?;
Ok(PoolUpdate {
sequence,
slot,
write_version,
protocol_name,
pool_address,
token_mints,
token_balances,
token_decimals,
best_bid,
best_ask,
serialized_state,
})
}
fn decode_optional_order_level(data: &[u8], offset: &mut usize) -> Result<Option<OrderLevel>, DecodeError> {
if *offset >= data.len() {
return Err(DecodeError::PayloadTooShort {
expected: *offset + 1,
actual: data.len(),
});
}
if data[*offset] == 0 {
*offset += 1;
return Ok(None);
}
*offset += 1;
let price = read_u64(data, offset)?;
let size = read_u64(data, offset)?;
Ok(Some(OrderLevel { price, size }))
}
fn decode_pool_update_batch(data: &[u8]) -> Result<Vec<PoolUpdate>, DecodeError> {
let mut offset = 0;
let count = read_u16(data, &mut offset)?;
let mut updates = Vec::with_capacity(count as usize);
for _ in 0..count {
let length = read_u32(data, &mut offset)?;
if offset + length as usize > data.len() {
return Err(DecodeError::PayloadTooShort {
expected: offset + length as usize,
actual: data.len(),
});
}
let update = decode_pool_update(&data[offset..offset + length as usize])?;
updates.push(update);
offset += length as usize;
}
Ok(updates)
}
fn decode_priority_fees(data: &[u8]) -> Result<PriorityFees, DecodeError> {
if data.len() < 119 {
return Err(DecodeError::PayloadTooShort {
expected: 119,
actual: data.len(),
});
}
let mut offset = 0;
Ok(PriorityFees {
slot: read_u64(data, &mut offset)?,
timestamp_ms: read_u64(data, &mut offset)?,
recommended: read_u64(data, &mut offset)?,
state: NetworkState::try_from(data[offset]).map_err(DecodeError::InvalidNetworkState)?,
is_stale: {
offset += 1;
let v = data[offset] != 0;
offset += 1;
v
},
swap_p50: read_u64(data, &mut offset)?,
swap_p75: read_u64(data, &mut offset)?,
swap_p90: read_u64(data, &mut offset)?,
swap_p99: read_u64(data, &mut offset)?,
swap_samples: read_u32(data, &mut offset)?,
landing_p50_fee: read_u64(data, &mut offset)?,
landing_p75_fee: read_u64(data, &mut offset)?,
landing_p90_fee: read_u64(data, &mut offset)?,
landing_p99_fee: read_u64(data, &mut offset)?,
top_10_fee: read_u64(data, &mut offset)?,
top_25_fee: read_u64(data, &mut offset)?,
spike_detected: {
let v = data[offset] != 0;
offset += 1;
v
},
spike_fee: read_u64(data, &mut offset)?,
})
}
fn decode_blockhash(data: &[u8]) -> Result<Blockhash, DecodeError> {
if data.len() < 65 {
return Err(DecodeError::PayloadTooShort {
expected: 65,
actual: data.len(),
});
}
let mut offset = 0;
let slot = read_u64(data, &mut offset)?;
let timestamp_ms = read_u64(data, &mut offset)?;
let blockhash = bs58::encode(&data[offset..offset + 32]).into_string();
offset += 32;
let block_height = read_u64(data, &mut offset)?;
let last_valid_block_height = read_u64(data, &mut offset)?;
let is_stale = data[offset] != 0;
Ok(Blockhash {
slot,
timestamp_ms,
blockhash,
block_height,
last_valid_block_height,
is_stale,
})
}
fn read_u64(data: &[u8], offset: &mut usize) -> Result<u64, DecodeError> {
if *offset + 8 > data.len() {
return Err(DecodeError::PayloadTooShort {
expected: *offset + 8,
actual: data.len(),
});
}
let value = u64::from_le_bytes(data[*offset..*offset + 8].try_into().unwrap());
*offset += 8;
Ok(value)
}
fn read_u32(data: &[u8], offset: &mut usize) -> Result<u32, DecodeError> {
if *offset + 4 > data.len() {
return Err(DecodeError::PayloadTooShort {
expected: *offset + 4,
actual: data.len(),
});
}
let value = u32::from_le_bytes(data[*offset..*offset + 4].try_into().unwrap());
*offset += 4;
Ok(value)
}
fn read_u16(data: &[u8], offset: &mut usize) -> Result<u16, DecodeError> {
if *offset + 2 > data.len() {
return Err(DecodeError::PayloadTooShort {
expected: *offset + 2,
actual: data.len(),
});
}
let value = u16::from_le_bytes(data[*offset..*offset + 2].try_into().unwrap());
*offset += 2;
Ok(value)
}
fn read_i32(data: &[u8], offset: &mut usize) -> Result<i32, DecodeError> {
if *offset + 4 > data.len() {
return Err(DecodeError::PayloadTooShort {
expected: *offset + 4,
actual: data.len(),
});
}
let value = i32::from_le_bytes(data[*offset..*offset + 4].try_into().unwrap());
*offset += 4;
Ok(value)
}