Skip to content

Commit

Permalink
make tx response in json format
Browse files Browse the repository at this point in the history
Add cancelAndPlace endpoint
  • Loading branch information
jordy25519 committed Dec 15, 2023
1 parent 350a9e9 commit def91b7
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 11 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Options:

## API Examples

Please refer to https://drift-labs.github.io/v2-teacher/ for further examples and reference documentation on various types, fields, and operations available on drift.

### Get Market Info
```bash
$ curl localhost:8080/v2/markets
Expand Down Expand Up @@ -110,6 +112,7 @@ $ curl localhost:8080/v2/orders -X POST -H 'content-type: application/json' -d '
}]
}'
```
Returns solana tx signature on success

### Modify Orders
like place orders but caller must specify either `orderId` or `userOrderId` to indicate which order to modify.
Expand All @@ -130,6 +133,7 @@ $ curl localhost:8080/v2/orders -X PATCH -H 'content-type: application/json' -d
}]
}'
```
Returns solana tx signature on success

### Cancel Orders
```bash
Expand All @@ -142,6 +146,33 @@ $ curl localhost:8080/v2/orders -X DELETE -H 'content-type: application/json' -d
# cancel all orders
$ curl localhost:8080/v2/orders -X DELETE
```
Returns solana tx signature on success

### Cancel and Place Orders

Atomically cancel then place orders without possible downtime.
Request format is an embedded cancel and place request

```bash
$ curl localhost:8080/v2/orders/cancelAndPlace -X POST -H 'content-type: application/json' -d '{
"cancel": {
"marketIndex": 0,
"marketType": "perp"
},
"place": {
"orders": [
{
"marketIndex": 0,
"marketType": "perp",
"amount": -1.23,
"price": 80.0,
"postOnly": true,
"orderType": "limit",
"immediateOrCancel": false,
"reduce_only": false
}]
}
}'

### Errors
error responses have the following JSON structure:
Expand Down
57 changes: 48 additions & 9 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use log::error;
use thiserror::Error;

use crate::types::{
AllMarketsResponse, CancelOrdersRequest, GetOrderbookRequest, GetOrdersRequest,
GetOrdersResponse, GetPositionsRequest, GetPositionsResponse, ModifyOrdersRequest, Order,
PlaceOrdersRequest, SpotPosition,
AllMarketsResponse, CancelAndPlaceRequest, CancelOrdersRequest, GetOrderbookRequest,
GetOrdersRequest, GetOrdersResponse, GetPositionsRequest, GetPositionsResponse,
ModifyOrdersRequest, Order, PlaceOrdersRequest, SpotPosition, TxResponse,
};

pub type GatewayResult<T> = Result<T, ControllerError>;
Expand Down Expand Up @@ -82,7 +82,7 @@ impl AppState {
/// 2) "user ids" are set, cancel all orders by user assigned id
/// 3) ids are given, cancel all orders by id (global, exchange assigned id)
/// 4) catch all. cancel all orders
pub async fn cancel_orders(&self, req: CancelOrdersRequest) -> GatewayResult<String> {
pub async fn cancel_orders(&self, req: CancelOrdersRequest) -> GatewayResult<TxResponse> {
let user_data = self.client.get_user_account(self.user()).await?;
let builder = TransactionBuilder::new(&self.wallet, &user_data);

Expand All @@ -106,7 +106,7 @@ impl AppState {
self.client
.sign_and_send(&self.wallet, tx)
.await
.map(|s| s.to_string())
.map(|s| TxResponse::new(s.to_string()))
.map_err(handle_tx_err)
}

Expand Down Expand Up @@ -187,7 +187,46 @@ impl AppState {
}
}

pub async fn place_orders(&self, req: PlaceOrdersRequest) -> GatewayResult<String> {
pub async fn cancel_and_place_orders(
&self,
req: CancelAndPlaceRequest,
) -> GatewayResult<TxResponse> {
let orders = req
.place
.orders
.into_iter()
.map(|o| o.to_order_params(self.context()))
.collect();

let user_data = self.client.get_user_account(self.user()).await?;
let builder = TransactionBuilder::new(&self.wallet, &user_data);

let tx = if let Some(market) = req.cancel.market {
builder.cancel_orders((market.market_index, market.market_type), None)
} else if !req.cancel.user_ids.is_empty() {
let order_ids = user_data
.orders
.iter()
.filter(|o| o.slot > 0 && req.cancel.user_ids.contains(&o.user_order_id))
.map(|o| o.order_id)
.collect();
builder.cancel_orders_by_id(order_ids)
} else if !req.cancel.ids.is_empty() {
builder.cancel_orders_by_id(req.cancel.ids)
} else {
builder.cancel_all_orders()
}
.place_orders(orders)
.build();

self.client
.sign_and_send(&self.wallet, tx)
.await
.map(|s| TxResponse::new(s.to_string()))
.map_err(handle_tx_err)
}

pub async fn place_orders(&self, req: PlaceOrdersRequest) -> GatewayResult<TxResponse> {
let orders = req
.orders
.into_iter()
Expand All @@ -203,11 +242,11 @@ impl AppState {
self.client
.sign_and_send(&self.wallet, tx)
.await
.map(|s| s.to_string())
.map(|s| TxResponse::new(s.to_string()))
.map_err(handle_tx_err)
}

pub async fn modify_orders(&self, req: ModifyOrdersRequest) -> GatewayResult<String> {
pub async fn modify_orders(&self, req: ModifyOrdersRequest) -> GatewayResult<TxResponse> {
let user_data = &self.client.get_user_account(self.user()).await?;
// NB: its possible to let the drift program sort the modifications by userOrderId
// sorting it client side for simplicity
Expand Down Expand Up @@ -257,7 +296,7 @@ impl AppState {
self.client
.sign_and_send(&self.wallet, tx)
.await
.map(|s| s.to_string())
.map(|s| TxResponse::new(s.to_string()))
.map_err(handle_tx_err)
}

Expand Down
40 changes: 38 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use log::{error, info};

use controller::{AppState, ControllerError};
use serde_json::json;
use types::{CancelOrdersRequest, GetOrderbookRequest, ModifyOrdersRequest, PlaceOrdersRequest};
use types::{
CancelAndPlaceRequest, CancelOrdersRequest, GetOrderbookRequest, ModifyOrdersRequest,
PlaceOrdersRequest,
};

mod controller;
mod types;
Expand Down Expand Up @@ -61,6 +64,17 @@ async fn cancel_orders(controller: web::Data<AppState>, body: web::Bytes) -> imp
handle_result(controller.cancel_orders(req).await)
}

#[post("/orders/cancelAndPlace")]
async fn cancel_and_place_orders(
controller: web::Data<AppState>,
body: web::Bytes,
) -> impl Responder {
match serde_json::from_slice::<'_, CancelAndPlaceRequest>(body.as_ref()) {
Ok(req) => handle_result(controller.cancel_and_place_orders(req).await),
Err(err) => handle_deser_error(err),
}
}

#[get("/positions")]
async fn get_positions(controller: web::Data<AppState>, body: web::Bytes) -> impl Responder {
let mut req = None;
Expand Down Expand Up @@ -111,7 +125,8 @@ async fn main() -> std::io::Result<()> {
.service(create_orders)
.service(cancel_orders)
.service(modify_orders)
.service(get_orderbook),
.service(get_orderbook)
.service(cancel_and_place_orders),
)
})
.bind((config.host, config.port))?
Expand Down Expand Up @@ -175,3 +190,24 @@ fn handle_deser_error<T>(err: serde_json::Error) -> Either<HttpResponse, Json<T>
}
)))
}

#[cfg(test)]
mod tests {
use actix_web::{http::header::ContentType, test, App};

use super::*;

#[actix_web::test]
async fn get_orders_works() {
let controller = AppState::new("test", "example.com", true).await;
let app = test::init_service(
App::new()
.app_data(web::Data::new(controller))
.service(get_orders),
)
.await;
let req = test::TestRequest::default().to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
}
}
17 changes: 17 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,23 @@ pub struct GetOrderbookRequest {
pub market: Market,
}

#[derive(Serialize, Deserialize)]
pub struct TxResponse {
tx: String,
}

impl TxResponse {
pub fn new(tx_signature: String) -> Self {
Self { tx: tx_signature }
}
}

#[derive(Serialize, Deserialize)]
pub struct CancelAndPlaceRequest {
pub cancel: CancelOrdersRequest,
pub place: PlaceOrdersRequest,
}

#[cfg(test)]
mod tests {
use drift_sdk::types::{Context, MarketType, PositionDirection};
Expand Down

0 comments on commit def91b7

Please sign in to comment.