Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lazer/sdk/rust/client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pyth-lazer-client"
version = "8.2.1"
version = "8.2.2"
edition = "2021"
description = "A Rust client for Pyth Lazer"
license = "Apache-2.0"
Expand Down
367 changes: 367 additions & 0 deletions lazer/sdk/rust/client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
# 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)