use crate::api_gateway::ApiGatewayRestClient;
use crate::marketdata::MarketdataWsClient;
use crate::order_gateway::*;
use anyhow::{anyhow, Result};
use arc_swap::ArcSwapOption;
use arcstr::ArcStr;
use chrono::{DateTime, Utc};
use log::warn;
use std::sync::Arc;
use url::Url;
pub const DEFAULT_BASE_URL: &str = "https://gateway.architect.exchange";
pub const SANDBOX_BASE_URL: &str = "https://gateway.sandbox.architect.exchange";
#[derive(Clone)]
pub struct ArchitectX {
base_url: Url,
api_gateway_base_url: Url,
order_gateway_base_url: Url,
api_key: Option<String>,
api_secret: Option<String>,
user_token: Arc<ArcSwapOption<(ArcStr, DateTime<Utc>)>>,
}
impl ArchitectX {
pub fn new(
base_url: Url,
api_key: Option<impl AsRef<str>>,
api_secret: Option<impl AsRef<str>>,
) -> Result<Self> {
Ok(Self {
base_url: base_url.clone(),
api_gateway_base_url: base_url.join("api/")?,
order_gateway_base_url: base_url.join("orders/")?,
api_key: api_key.map(|k| k.as_ref().to_string()),
api_secret: api_secret.map(|s| s.as_ref().to_string()),
user_token: Arc::new(ArcSwapOption::const_empty()),
})
}
pub fn with_credentials(api_key: impl AsRef<str>, api_secret: impl AsRef<str>) -> Result<Self> {
Self::new(
Url::parse(DEFAULT_BASE_URL)?,
Some(api_key),
Some(api_secret),
)
}
pub fn sandbox(api_key: impl AsRef<str>, api_secret: impl AsRef<str>) -> Result<Self> {
Self::new(
Url::parse(SANDBOX_BASE_URL)?,
Some(api_key),
Some(api_secret),
)
}
pub fn set_api_gateway_base_url(&mut self, base_url: Url) {
self.api_gateway_base_url = base_url;
}
pub fn set_order_gateway_base_url(&mut self, base_url: Url) {
self.order_gateway_base_url = base_url;
}
pub async fn authenticate(
&self,
api_key: impl Into<String>,
api_secret: impl Into<String>,
) -> Result<ArcStr> {
use crate::protocol::api_gateway::{AuthenticateRequest, AuthenticationMethod};
let auth = AuthenticationMethod::ApiKeySecret {
api_key: api_key.into(),
api_secret: api_secret.into(),
};
let client = ApiGatewayRestClient::new(self.api_gateway_base_url.clone())?;
let res = client
.authenticate(AuthenticateRequest {
auth,
expiration_seconds: 3600,
})
.await?;
let token: ArcStr = res.token.expose_secret().to_string().into();
let expires = Utc::now() + chrono::Duration::seconds(3300);
self.user_token
.store(Some(Arc::new((token.clone(), expires))));
Ok(token)
}
pub async fn login(
&self,
username: impl AsRef<str>,
password: impl AsRef<str>,
totp: Option<impl AsRef<str>>,
) -> Result<ArcStr> {
use crate::protocol::api_gateway::{AuthenticateRequest, AuthenticationMethod};
let auth = AuthenticationMethod::UsernamePassword {
username: username.as_ref().to_string(),
password: password.as_ref().to_string(),
totp: totp.map(|t| t.as_ref().to_string()),
};
let client = ApiGatewayRestClient::new(self.api_gateway_base_url.clone())?;
let res = client
.authenticate(AuthenticateRequest {
auth,
expiration_seconds: 3600,
})
.await?;
let token: ArcStr = res.token.expose_secret().to_string().into();
let expires = Utc::now() + chrono::Duration::seconds(3300);
self.user_token
.store(Some(Arc::new((token.clone(), expires))));
Ok(token)
}
pub async fn refresh_user_token(&self, force: bool) -> Result<ArcStr> {
let now = Utc::now();
let token = self.user_token.load();
if let Some(stored) = &*token {
let (token, expires_at) = &**stored;
if !force && *expires_at > now {
return Ok(token.clone());
}
}
let api_key = self
.api_key
.as_ref()
.ok_or_else(|| anyhow!("no api_key provided"))?;
let api_secret = self
.api_secret
.as_ref()
.ok_or_else(|| anyhow!("no secret provided"))?;
self.authenticate(api_key, api_secret).await
}
pub fn api_gateway(&self) -> Result<ApiGatewayRestClient> {
let mut client = ApiGatewayRestClient::new(self.api_gateway_base_url.clone())?;
let auth = self.user_token.load();
if let Some(token) = &*auth {
let (token, expires_at) = &**token;
if *expires_at > Utc::now() {
client.set_token(token.as_str().to_string(), *expires_at);
} else {
warn!("while creating api gateway client: token expired");
}
}
Ok(client)
}
pub fn order_gateway(&self) -> Result<OrderGatewayRestClient> {
let mut client = OrderGatewayRestClient::new(self.order_gateway_base_url.clone())?;
let auth = self.user_token.load();
if let Some(token) = &*auth {
let (token, expires_at) = &**token;
if *expires_at > Utc::now() {
client.set_token(token.as_str().to_string(), *expires_at);
} else {
warn!("while creating order gateway client: token expired");
}
}
Ok(client)
}
pub async fn order_gateway_ws(&self) -> Result<OrderGatewayWsClient> {
let token = self.refresh_user_token(false).await?;
OrderGatewayWsClient::connect(self.base_url.clone(), token).await
}
pub async fn order_gateway_ws_with_cancel_on_disconnect(&self) -> Result<OrderGatewayWsClient> {
let token = self.refresh_user_token(false).await?;
OrderGatewayWsClient::connect_with_cancel_on_disconnect(self.base_url.clone(), token).await
}
pub async fn marketdata_ws(&self) -> Result<MarketdataWsClient> {
let token = self.refresh_user_token(false).await?;
MarketdataWsClient::connect(self.base_url.clone(), token).await
}
}