# Pyth Lazer Rust Client
A high-performance Rust client for connecting to [Pyth Lazer](https://pyth.network/) real-time data streams. This client provides reliable, low-latency access to Pyth's oracle price feeds with built-in redundancy and automatic failover.
## Features
- **Multiple redundant WebSocket connections** - Maintains 4 concurrent connections by default for high availability
- **Automatic message deduplication** - Uses TTL-based caching to eliminate duplicate messages across connections
- **Exponential backoff reconnection** - Automatically handles connection failures with configurable retry logic
- **Flexible subscription options** - Support for multiple data formats (EVM, Solana, etc.) and delivery channels
- **History API client** - Fetch symbol metadata and historical price information
- **Type-safe API** - Strongly-typed Rust interface with comprehensive error handling
## Installation
Add the following to your `Cargo.toml`:
```toml
[dependencies]
pyth-lazer-client = "8.2.2"
pyth-lazer-protocol = "0.16.0"
tokio = { version = "1", features = ["full"] }
```
## Authentication
To use the Pyth Lazer client, you need an access token. Set your access token via the `LAZER_ACCESS_TOKEN` environment variable:
```bash
export LAZER_ACCESS_TOKEN="your_access_token_here"
```
Or provide it directly in your code:
```rust
use pyth_lazer_client::stream_client::PythLazerStreamClientBuilder;
let access_token = std::env::var("LAZER_ACCESS_TOKEN")
.expect("LAZER_ACCESS_TOKEN not set");
let client = PythLazerStreamClientBuilder::new(access_token)
.build()?;
```
## Quick Start
Here's a minimal example to get started with streaming price feeds:
```rust
use pyth_lazer_client::stream_client::PythLazerStreamClientBuilder;
use pyth_lazer_protocol::api::{SubscribeRequest, SubscriptionParams, SubscriptionParamsRepr, Channel};
use pyth_lazer_protocol::{PriceFeedId, PriceFeedProperty};
use pyth_lazer_protocol::time::FixedRate;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create and start the client
let mut client = PythLazerStreamClientBuilder::new(
std::env::var("LAZER_ACCESS_TOKEN")?
).build()?;
let mut receiver = client.start().await?;
// Subscribe to price feeds
let subscribe_request = SubscribeRequest {
subscription_id: pyth_lazer_protocol::api::SubscriptionId(1),
params: SubscriptionParams::new(SubscriptionParamsRepr {
price_feed_ids: Some(vec![PriceFeedId(1), PriceFeedId(2)]),
symbols: None,
properties: vec![PriceFeedProperty::Price, PriceFeedProperty::Exponent],
formats: vec![pyth_lazer_protocol::api::Format::Solana],
delivery_format: pyth_lazer_protocol::api::DeliveryFormat::Json,
json_binary_encoding: pyth_lazer_protocol::api::JsonBinaryEncoding::Base64,
parsed: true,
channel: Channel::FixedRate(FixedRate::RATE_200_MS),
ignore_invalid_feeds: false,
})?,
};
client.subscribe(subscribe_request).await?;
// Process incoming messages
while let Some(response) = receiver.recv().await {
println!("Received update: {:?}", response);
}
Ok(())
}
```
## Configuration
The `PythLazerStreamClientBuilder` provides several configuration options:
### Custom Endpoints
Override the default production endpoints:
```rust
let client = PythLazerStreamClientBuilder::new(access_token)
.with_endpoints(vec![
"wss://pyth-lazer-0.dourolabs.app/v1/stream".parse()?,
"wss://pyth-lazer-1.dourolabs.app/v1/stream".parse()?,
])
.build()?;
```
### Number of Connections
Set the number of concurrent WebSocket connections (default: 4):
```rust
let client = PythLazerStreamClientBuilder::new(access_token)
.with_num_connections(2)
.build()?;
```
### Connection Timeout
Configure the timeout for WebSocket operations (default: 5 seconds):
```rust
use std::time::Duration;
let client = PythLazerStreamClientBuilder::new(access_token)
.with_timeout(Duration::from_secs(10))
.build()?;
```
### Exponential Backoff
Customize the reconnection backoff strategy:
```rust
use pyth_lazer_client::backoff::PythLazerExponentialBackoffBuilder;
let backoff = PythLazerExponentialBackoffBuilder::default()
.build();
let client = PythLazerStreamClientBuilder::new(access_token)
.with_backoff(backoff)
.build()?;
```
### Channel Capacity
Set the internal message buffer size (default: 1000):
```rust
let client = PythLazerStreamClientBuilder::new(access_token)
.with_channel_capacity(5000)
.build()?;
```
## Subscription Options
### Channels
Choose the update frequency for your price feeds:
- **`Channel::RealTime`** - Receive updates as soon as they're available
- **`Channel::FixedRate(FixedRate::RATE_50_MS)`** - Updates every 50ms
- **`Channel::FixedRate(FixedRate::RATE_200_MS)`** - Updates every 200ms (recommended for most use cases)
- **`Channel::FixedRate(FixedRate::RATE_1000_MS)`** - Updates every 1000ms
```rust
use pyth_lazer_protocol::api::Channel;
use pyth_lazer_protocol::time::FixedRate;
// Real-time updates
let channel = Channel::RealTime;
// Fixed rate updates
let channel = Channel::FixedRate(FixedRate::RATE_200_MS);
```
### Formats
Specify the signature format for the price data:
- **`Format::Evm`** - EVM-compatible format with secp256k1 signatures
- **`Format::Solana`** - Solana-compatible format with Ed25519 signatures
- **`Format::LeEcdsa`** - Little-endian ECDSA format
- **`Format::LeUnsigned`** - Little-endian unsigned format
```rust
use pyth_lazer_protocol::api::Format;
let formats = vec![Format::Evm, Format::Solana];
```
### Delivery Format
Choose how messages are delivered:
- **`DeliveryFormat::Json`** - Receive updates as JSON text messages (default)
- **`DeliveryFormat::Binary`** - Receive updates as binary messages (more efficient)
```rust
use pyth_lazer_protocol::api::DeliveryFormat;
let delivery_format = DeliveryFormat::Binary;
```
### Properties
Select which price feed properties to receive:
- `PriceFeedProperty::Price` - Current price
- `PriceFeedProperty::BestBidPrice` - Best bid price
- `PriceFeedProperty::BestAskPrice` - Best ask price
- `PriceFeedProperty::PublisherCount` - Number of contributing publishers
- `PriceFeedProperty::Exponent` - Price exponent
- `PriceFeedProperty::Confidence` - Confidence interval
- `PriceFeedProperty::FundingRate` - Funding rate (for perpetual markets)
- `PriceFeedProperty::FundingTimestamp` - Funding rate timestamp
- `PriceFeedProperty::FundingRateInterval` - Funding rate update interval
```rust
use pyth_lazer_protocol::PriceFeedProperty;
let properties = vec![
PriceFeedProperty::Price,
PriceFeedProperty::Exponent,
PriceFeedProperty::Confidence,
];
```
### Identifying Price Feeds
Subscribe to feeds using either price feed IDs or symbols:
```rust
// By price feed ID
let params = SubscriptionParamsRepr {
price_feed_ids: Some(vec![PriceFeedId(1), PriceFeedId(2)]),
symbols: None,
// ... other fields
};
// By symbol
let params = SubscriptionParamsRepr {
price_feed_ids: None,
symbols: Some(vec![
"Crypto.BTC/USD".to_string(),
"Crypto.ETH/USD".to_string(),
]),
// ... other fields
};
```
## Examples
### Comprehensive Streaming Example
See [`examples/subscribe_price_feeds.rs`](examples/subscribe_price_feeds.rs) for a complete example demonstrating:
- Client configuration with multiple connections
- Subscribing to price feeds with different formats
- Processing JSON and binary updates
- Verifying message signatures
- Unsubscribing from feeds
Run the example:
```bash
cargo run --example subscribe_price_feeds
```
### Symbol Metadata
Fetch symbol metadata using the history client:
```rust
use pyth_lazer_client::history_client::{PythLazerHistoryClient, PythLazerHistoryClientConfig};
let client = PythLazerHistoryClient::new(
PythLazerHistoryClientConfig::default()
);
// Get all symbol metadata
let symbols = client.all_symbols_metadata().await?;
// Or get an auto-updating handle
let handle = client.all_symbols_metadata_handle().await?;
let symbols = handle.symbols();
```
See [`examples/symbols.rs`](examples/symbols.rs) and [`examples/symbols_stream.rs`](examples/symbols_stream.rs) for complete examples.
## History Client
The `PythLazerHistoryClient` provides access to symbol metadata and historical price information:
```rust
use pyth_lazer_client::history_client::{PythLazerHistoryClient, PythLazerHistoryClientConfig};
use std::time::Duration;
let config = PythLazerHistoryClientConfig {
urls: vec!["https://history.pyth-lazer.dourolabs.app/".parse()?],
update_interval: Duration::from_secs(30),
request_timeout: Duration::from_secs(15),
cache_dir: Some("/tmp/pyth-lazer-cache".into()),
channel_capacity: 1000,
};
let client = PythLazerHistoryClient::new(config);
// Fetch symbol metadata once
let symbols = client.all_symbols_metadata().await?;
// Or get an auto-updating handle that refreshes in the background
let handle = client.all_symbols_metadata_handle().await?;
// Or get a stream of updates
let mut stream = client.all_symbols_metadata_stream().await?;
while let Some(symbols) = stream.recv().await {
println!("Updated symbols: {} feeds", symbols.len());
}
```
The history client supports:
- **One-time fetches** - Get current data with `all_symbols_metadata()`
- **Auto-updating handles** - Background updates with `all_symbols_metadata_handle()`
- **Streaming updates** - Receive updates via channels with `all_symbols_metadata_stream()`
- **Local caching** - Optional disk cache for offline access
- **Fault tolerance** - Graceful fallback to cached data on network failures
## API Reference
### Main Types
- **`PythLazerStreamClient`** - The main client for streaming price updates
- **`PythLazerStreamClientBuilder`** - Builder for configuring the stream client
- **`PythLazerHistoryClient`** - Client for fetching symbol metadata
- **`SubscribeRequest`** - Subscription configuration
- **`SubscriptionParams`** - Subscription parameters wrapper
- **`AnyResponse`** - Enum for JSON or binary responses
### Core Methods
#### PythLazerStreamClient
- `start() -> Result<Receiver<AnyResponse>>` - Start the client and return message receiver
- `subscribe(request: SubscribeRequest) -> Result<()>` - Subscribe to price feeds
- `unsubscribe(id: SubscriptionId) -> Result<()>` - Unsubscribe from a feed
#### PythLazerHistoryClient
- `all_symbols_metadata() -> Result<Vec<SymbolMetadata>>` - Fetch all symbols once
- `all_symbols_metadata_handle() -> Result<SymbolMetadataHandle>` - Get auto-updating handle
- `all_symbols_metadata_stream() -> Result<Receiver<Vec<SymbolMetadata>>>` - Get update stream
For complete API documentation, visit [docs.rs/pyth-lazer-client](https://docs.rs/pyth-lazer-client).
## License
This project is licensed under the Apache-2.0 License. See the [LICENSE](../../../../LICENSE) file for details.
## Resources
- [Pyth Network Documentation](https://docs.pyth.network/)
- [Pyth Lazer Overview](https://docs.pyth.network/price-feeds/lazer)
- [API Reference](https://docs.rs/pyth-lazer-client)
- [Examples](examples/)
- [GitHub Repository](https://github.com/pyth-network/pyth-crosschain)