Skip to content

Commit

Permalink
add some error handling
Browse files Browse the repository at this point in the history
allow query orders, positions, and cancellation with given market/ids
  • Loading branch information
jordy25519 committed Dec 8, 2023
1 parent 1f8870b commit 1885ecd
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 76 deletions.
30 changes: 16 additions & 14 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ argh = "0.1.12"
drift-sdk = { package = "drift-sdk", git = "https://github.com/circuit-research/protocol-v2", branch = "cargo-add-sdk" }
env_logger = "0.10.1"
log = "0.4.20"
serde = { version = "1.0.193", features = ["derive"] }
serde = { version = "1.0.193", features = ["derive"] }
thiserror = "1.0.38"
134 changes: 89 additions & 45 deletions src/controller.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
use drift_sdk::{types::Context, DriftClient, Pubkey, TransactionBuilder, Wallet};
use drift_sdk::{
types::{Context, MarketType, SdkError},
DriftClient, Pubkey, TransactionBuilder, Wallet,
};
use log::error;
use thiserror::Error;

use crate::types::{
AllMarketsResponse, GetOrdersResponse, GetPositionsResponse, PlaceOrdersRequest,
AllMarketsResponse, CancelOrdersRequest, GetOrdersRequest, GetOrdersResponse,
GetPositionsRequest, GetPositionsResponse, PlaceOrdersRequest,
};

#[derive(Error, Debug)]
pub enum ControllerError {
#[error("internal server error")]
Sdk(#[from] SdkError),
// #[error("the data for key `{0}` is not available")]
// Redaction(String),
}

#[derive(Clone)]
pub struct AppState {
wallet: Wallet,
Expand All @@ -29,47 +42,87 @@ impl AppState {
)
.expect("valid key");
let client = DriftClient::new(endpoint).await.expect("connects");
client.subscribe_account(&wallet).await.expect("cache on");
client
.subscribe_account(wallet.user())
.await
.expect("cache on");
Self { wallet, client }
}

pub async fn cancel_orders(&self) -> Result<String, ()> {
let tx = TransactionBuilder::new(
&self.wallet,
&self
.client
.get_account_data(&self.wallet)
.await
.map_err(|_| ())?,
)
.cancel_all_orders()
/// Cancel orders
///
/// There are 3 intended scenarios for cancellation, in order of priority:
/// 1) "market" is set cancel all orders in the market
/// 2) ids are given cancel all orders by id
/// 3) catch all cancel all orders
pub async fn cancel_orders(&self, req: CancelOrdersRequest) -> Result<String, ControllerError> {
let user_data = self.client.get_account_data(self.user()).await?;
let builder = TransactionBuilder::new(&self.wallet, &user_data);

let tx = if let Some(market) = req.market {
builder.cancel_orders((market.id, market.market_type).into(), None)
} else if !req.ids.is_empty() {
builder.cancel_orders_by_id(req.ids)
} else {
builder.cancel_all_orders()
}
.build();

self.client
.sign_and_send(&self.wallet, tx)
.await
.map_err(|_| ())
.map(|s| s.to_string())
let signature = self.client.sign_and_send(&self.wallet, tx).await?;

Ok(signature.to_string())
}

pub async fn get_positions(&self) -> Result<GetPositionsResponse, ()> {
// TODO: log/surface sdk error
let (spot, perp) = self
.client
.all_positions(&self.wallet)
.await
.map_err(|_| ())?;
/// Return orders by position if given, otherwise return all positions
pub async fn get_positions(
&self,
req: GetPositionsRequest,
) -> Result<GetPositionsResponse, ControllerError> {
let (spot, perp) = self.client.all_positions(self.user()).await?;
Ok(GetPositionsResponse {
spot: spot.iter().map(|x| (*x).into()).collect(),
perp: perp.iter().map(|x| (*x).into()).collect(),
spot: spot
.iter()
.filter(|p| {
if let Some(ref market) = req.market {
p.market_index == market.id && MarketType::Spot == market.market_type
} else {
true
}
})
.map(|x| (*x).into())
.collect(),
perp: perp
.iter()
.filter(|p| {
if let Some(ref market) = req.market {
p.market_index == market.id && MarketType::Perp == market.market_type
} else {
true
}
})
.map(|x| (*x).into())
.collect(),
})
}

pub async fn get_orders(&self) -> Result<GetOrdersResponse, ()> {
// TODO: log/surface sdk error
let orders = self.client.all_orders(&self.wallet).await.map_err(|_| ())?;
/// Return orders by market if given, otherwise return all orders
pub async fn get_orders(
&self,
req: GetOrdersRequest,
) -> Result<GetOrdersResponse, ControllerError> {
let orders = self.client.all_orders(self.user()).await?;
Ok(GetOrdersResponse {
orders: orders.into_iter().map(Into::into).collect(),
orders: orders
.into_iter()
.filter(|o| {
if let Some(ref market) = req.market {
o.market_index == market.id && o.market_type == market.market_type
} else {
true
}
})
.map(Into::into)
.collect(),
})
}

Expand All @@ -83,26 +136,17 @@ impl AppState {
}
}

pub async fn place_orders(&self, req: PlaceOrdersRequest) -> Result<String, ()> {
pub async fn place_orders(&self, req: PlaceOrdersRequest) -> Result<String, ControllerError> {
let orders = req.orders.into_iter().map(Into::into).collect();
let tx = TransactionBuilder::new(
&self.wallet,
&self
.client
.get_account_data(&self.wallet)
.await
.map_err(|_| ())?,
&self.client.get_account_data(self.user()).await?,
)
.place_orders(orders)
.build();

self.client
.sign_and_send(&self.wallet, tx)
.await
.map_err(|err| {
error!("{err:?}");
()
})
.map(|s| s.to_string())
let signature = self.client.sign_and_send(&self.wallet, tx).await?;

Ok(signature.to_string())
}
}
59 changes: 43 additions & 16 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use actix_web::{
delete, get, post,
web::{self, Json},
App, HttpRequest, HttpServer, Responder,
App, Either, HttpResponse, HttpServer, Responder,
};
use argh::FromArgs;
use log::info;
use log::{error, info};

use controller::AppState;
use types::PlaceOrdersRequest;
use types::{CancelOrdersRequest, GetOrdersRequest, GetPositionsRequest, PlaceOrdersRequest};

mod controller;
mod types;
Expand All @@ -19,32 +19,59 @@ async fn get_markets(controller: web::Data<AppState>) -> impl Responder {
}

#[get("/orders")]
async fn get_orders(controller: web::Data<AppState>, _req: HttpRequest) -> impl Responder {
// TODO: add some RequestType to filter
let orders = controller.get_orders().await;
Json(orders)
async fn get_orders(
controller: web::Data<AppState>,
req: Json<GetOrdersRequest>,
) -> impl Responder {
match controller.get_orders(req.0).await {
Err(err) => {
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError())
}
Ok(payload) => Either::Right(Json(payload)),
}
}

#[post("/orders")]
async fn create_orders(
controller: web::Data<AppState>,
req: Json<PlaceOrdersRequest>,
) -> impl Responder {
let signature = controller.place_orders(req.0).await;
Json(signature)
match controller.place_orders(req.0).await {
Err(err) => {
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError())
}
Ok(payload) => Either::Right(Json(payload)),
}
}

#[delete("/orders")]
async fn cancel_orders(controller: web::Data<AppState>, _req: HttpRequest) -> impl Responder {
let signature = controller.cancel_orders().await;
Json(signature)
async fn cancel_orders(
controller: web::Data<AppState>,
req: Json<CancelOrdersRequest>,
) -> impl Responder {
match controller.cancel_orders(req.0).await {
Err(err) => {
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError())
}
Ok(payload) => Either::Right(Json(payload)),
}
}

#[get("/positions")]
async fn get_positions(controller: web::Data<AppState>, _req: HttpRequest) -> impl Responder {
// TODO: add some RequestType to filter
let positions = controller.get_positions().await;
Json(positions)
async fn get_positions(
controller: web::Data<AppState>,
req: Json<GetPositionsRequest>,
) -> impl Responder {
match controller.get_positions(req.0).await {
Err(err) => {
error!("{err:?}");
Either::Left(HttpResponse::InternalServerError())
}
Ok(payload) => Either::Right(Json(payload)),
}
}

#[actix_web::main]
Expand Down
Loading

0 comments on commit 1885ecd

Please sign in to comment.