use crate::stream::{PolyEdgeStream, StreamOptions};
use crate::types::*;
use reqwest::Client as HttpClient;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ClientError {
#[error("PolyEdge API error [{status}]: {body}")]
Api { status: u16, body: String },
#[error("HTTP request error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("Serialization error: {0}")]
Json(#[from] serde_json::Error),
}
pub struct StreamsService<'a> {
client: &'a PolyEdgeClient,
}
impl<'a> StreamsService<'a> {
pub fn connect(&self, stream_id: impl Into<String>) -> PolyEdgeStream {
self.client.stream(stream_id)
}
pub async fn list(&self) -> Result<UserStreamsResponse, ClientError> {
let path = "/streams".to_string();
let url = format!("{}{}", self.client.stream_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn create(&self, req: &UserCreateStreamRequest) -> Result<UserStreamResponse, ClientError> {
let path = "/streams".to_string();
let url = format!("{}{}", self.client.stream_base_url, path);
let resp = self.client.http_client.post(&url).header("X-PolyEdge-Key", &self.client.api_key).json(req).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get(&self, id: &str) -> Result<UserStreamResponse, ClientError> {
let path = format!("/streams/{}/meta", id);
let url = format!("{}{}", self.client.stream_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn delete(&self, id: &str) -> Result<DeleteStreamResponse, ClientError> {
let path = format!("/streams/{}", id);
let url = format!("{}{}", self.client.stream_base_url, path);
let resp = self.client.http_client.delete(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn update_metadata(&self, id: &str, req: &UserUpdateStreamMetadataRequest) -> Result<UserStreamResponse, ClientError> {
let path = format!("/streams/{}/meta", id);
let url = format!("{}{}", self.client.stream_base_url, path);
let resp = self.client.http_client.put(&url).header("X-PolyEdge-Key", &self.client.api_key).json(req).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn update_subscription(&self, id: &str, req: &UpdateSubscriptionRequest) -> Result<UserStreamResponse, ClientError> {
let path = format!("/streams/{}/subscription", id);
let url = format!("{}{}", self.client.stream_base_url, path);
let resp = self.client.http_client.put(&url).header("X-PolyEdge-Key", &self.client.api_key).json(req).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_active_sessions(&self) -> Result<UserActiveSessionsResponse, ClientError> {
let path = "/sessions".to_string();
let url = format!("{}{}", self.client.stream_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn disconnect_session(&self, id: &str) -> Result<UserDisconnectSessionResponse, ClientError> {
let path = format!("/sessions/{}", id);
let url = format!("{}{}", self.client.stream_base_url, path);
let resp = self.client.http_client.delete(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_session_history(&self, limit: Option<i64>, offset: Option<i64>) -> Result<UserSessionHistoryResponse, ClientError> {
let path = "/sessions/history".to_string();
let mut url = format!("{}{}", self.client.stream_base_url, path);
let mut query_pairs = Vec::new();
if let Some(v) = limit { query_pairs.push(format!("limit={}", v)); }
if let Some(v) = offset { query_pairs.push(format!("offset={}", v)); }
if !query_pairs.is_empty() { url.push('?'); url.push_str(&query_pairs.join("&")); }
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
}
pub struct AnalyticsService<'a> {
client: &'a PolyEdgeClient,
}
impl<'a> AnalyticsService<'a> {
pub async fn get_deposits(&self, limit: Option<i64>, offset: Option<i64>) -> Result<DepositQueryResult, ClientError> {
let path = "/v2/analytics/deposits".to_string();
let mut url = format!("{}{}", self.client.api_base_url, path);
let mut query_pairs = Vec::new();
if let Some(v) = limit { query_pairs.push(format!("limit={}", v)); }
if let Some(v) = offset { query_pairs.push(format!("offset={}", v)); }
if !query_pairs.is_empty() { url.push('?'); url.push_str(&query_pairs.join("&")); }
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_leaderboard(&self, limit: Option<i64>, offset: Option<i64>) -> Result<LeaderboardResponse, ClientError> {
let path = "/v2/analytics/leaderboard".to_string();
let mut url = format!("{}{}", self.client.api_base_url, path);
let mut query_pairs = Vec::new();
if let Some(v) = limit { query_pairs.push(format!("limit={}", v)); }
if let Some(v) = offset { query_pairs.push(format!("offset={}", v)); }
if !query_pairs.is_empty() { url.push('?'); url.push_str(&query_pairs.join("&")); }
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_market(&self, id: &str) -> Result<MarketDetailResponse, ClientError> {
let path = format!("/v2/markets/{}", id);
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_trader(&self, address: &str) -> Result<TraderProfileResponse, ClientError> {
let path = format!("/v2/traders/{}", address);
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_trader_hourly_stats(&self, address: &str) -> Result<TraderHourlyStatsResponse, ClientError> {
let path = format!("/v2/traders/{}/hourly_stats", address);
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_trader_markets(&self, address: &str, limit: Option<i64>, offset: Option<i64>) -> Result<TraderHistoryResponse, ClientError> {
let path = format!("/v2/traders/{}/markets", address);
let mut url = format!("{}{}", self.client.api_base_url, path);
let mut query_pairs = Vec::new();
if let Some(v) = limit { query_pairs.push(format!("limit={}", v)); }
if let Some(v) = offset { query_pairs.push(format!("offset={}", v)); }
if !query_pairs.is_empty() { url.push('?'); url.push_str(&query_pairs.join("&")); }
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_trader_market_orders(&self, address: &str, id: &str, limit: Option<i64>, offset: Option<i64>) -> Result<UserOrdersResponse, ClientError> {
let path = format!("/v2/traders/{}/markets/{}/orders", address, id);
let mut url = format!("{}{}", self.client.api_base_url, path);
let mut query_pairs = Vec::new();
if let Some(v) = limit { query_pairs.push(format!("limit={}", v)); }
if let Some(v) = offset { query_pairs.push(format!("offset={}", v)); }
if !query_pairs.is_empty() { url.push('?'); url.push_str(&query_pairs.join("&")); }
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
}
pub struct AccountService<'a> {
client: &'a PolyEdgeClient,
}
impl<'a> AccountService<'a> {
pub async fn get_profile(&self) -> Result<UserProfile, ClientError> {
let path = "/v2/user/me".to_string();
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn list_keys(&self) -> Result<UserKeysResponse, ClientError> {
let path = "/v2/user/keys".to_string();
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn create_key(&self, req: &CreateAPIKeyRequest) -> Result<UserAPIKey, ClientError> {
let path = "/v2/user/keys".to_string();
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.post(&url).header("X-PolyEdge-Key", &self.client.api_key).json(req).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn update_key(&self, key: &str, req: &UserUpdateKeyRequest) -> Result<UserAPIKey, ClientError> {
let path = format!("/v2/user/keys/{}", key);
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.patch(&url).header("X-PolyEdge-Key", &self.client.api_key).json(req).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn delete_key(&self, key: &str) -> Result<DeleteKeyResponse, ClientError> {
let path = format!("/v2/user/keys/{}", key);
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.delete(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_ledger(&self, limit: Option<i64>, offset: Option<i64>) -> Result<UserLedgerResponse, ClientError> {
let path = "/v2/user/ledger".to_string();
let mut url = format!("{}{}", self.client.api_base_url, path);
let mut query_pairs = Vec::new();
if let Some(v) = limit { query_pairs.push(format!("limit={}", v)); }
if let Some(v) = offset { query_pairs.push(format!("offset={}", v)); }
if !query_pairs.is_empty() { url.push('?'); url.push_str(&query_pairs.join("&")); }
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_telemetry(&self) -> Result<UserTelemetryResponse, ClientError> {
let path = "/v2/user/telemetry".to_string();
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
}
pub struct SubscriptionService<'a> {
client: &'a PolyEdgeClient,
}
impl<'a> SubscriptionService<'a> {
pub async fn list_tiers(&self) -> Result<Vec<Tier>, ClientError> {
let path = "/v2/tiers".to_string();
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.get(&url).header("X-PolyEdge-Key", &self.client.api_key).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn get_quote(&self, req: &QuoteSubscriptionRequest) -> Result<QuoteSubscriptionResponse, ClientError> {
let path = "/v2/user/subscribe/quote".to_string();
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.post(&url).header("X-PolyEdge-Key", &self.client.api_key).json(req).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn subscribe(&self, req: &SubscribeRequest) -> Result<SubscribeResponse, ClientError> {
let path = "/v2/user/subscribe".to_string();
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.post(&url).header("X-PolyEdge-Key", &self.client.api_key).json(req).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
pub async fn validate_promo_code(&self, req: &ValidatePromoCodeRequest) -> Result<ValidatePromoCodeResponse, ClientError> {
let path = "/v2/user/promo-codes/validate".to_string();
let url = format!("{}{}", self.client.api_base_url, path);
let resp = self.client.http_client.post(&url).header("X-PolyEdge-Key", &self.client.api_key).json(req).send().await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(ClientError::Api { status, body });
}
Ok(resp.json().await?)
}
}
pub struct PolyEdgeClient {
api_key: String,
api_base_url: String,
stream_base_url: String,
http_client: HttpClient,
}
impl PolyEdgeClient {
pub fn new(api_key: impl Into<String>) -> Self {
let http_client = HttpClient::builder()
.no_proxy()
.build()
.unwrap_or_else(|_| HttpClient::new());
Self {
api_key: api_key.into(),
api_base_url: "https://api.polyedge.dev".to_string(),
stream_base_url: "https://stream.polyedge.dev".to_string(),
http_client,
}
}
pub fn with_urls(mut self, api_base_url: impl Into<String>, stream_base_url: impl Into<String>) -> Self {
self.api_base_url = api_base_url.into().trim_end_matches('/').to_string();
self.stream_base_url = stream_base_url.into().trim_end_matches('/').to_string();
self
}
pub fn streams(&self) -> StreamsService<'_> {
StreamsService { client: self }
}
pub fn analytics(&self) -> AnalyticsService<'_> {
AnalyticsService { client: self }
}
pub fn account(&self) -> AccountService<'_> {
AccountService { client: self }
}
pub fn subscription(&self) -> SubscriptionService<'_> {
SubscriptionService { client: self }
}
pub fn stream(&self, stream_id: impl Into<String>) -> PolyEdgeStream {
PolyEdgeStream::new(
stream_id,
self.api_key.clone(),
Some(StreamOptions {
stream_base_url: Some(self.stream_base_url.clone()),
heartbeat_timeout: None,
initial_backoff: None,
max_backoff: None,
}),
)
}
}