pyth-lazer-client 8.6.0

A Rust client for Pyth Lazer
Documentation
# 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