Browse Source

Merge pull request #119 from comit-network/accept-order

fix-bad-api-calls
Thomas Eizinger 3 years ago
committed by GitHub
parent
commit
b5509a0edd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      daemon/src/db.rs
  2. 3
      daemon/src/maker.rs
  3. 145
      daemon/src/maker_cfd_actor.rs
  4. 6
      daemon/src/maker_inc_connections_actor.rs
  5. 32
      daemon/src/model/cfd.rs
  6. 59
      daemon/src/routes_maker.rs
  7. 14
      daemon/src/routes_taker.rs
  8. 2
      daemon/src/taker.rs
  9. 28
      daemon/src/taker_cfd_actor.rs
  10. 7
      daemon/src/taker_inc_message_actor.rs
  11. 4
      daemon/src/wire.rs
  12. 14
      docs/asset/mvp_maker_taker_messaging.puml
  13. 23
      frontend/src/Maker.tsx
  14. 39
      frontend/src/MakerClient.tsx
  15. 18
      frontend/src/Taker.tsx
  16. 85
      frontend/src/components/CfdTile.tsx

6
daemon/src/db.rs

@ -432,7 +432,7 @@ mod tests {
let cfd = Cfd::new(
order.clone(),
Usd(dec!(1000)),
CfdState::PendingTakeRequest {
CfdState::OutgoingOrderRequest {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
@ -456,7 +456,7 @@ mod tests {
let cfd = Cfd::new(
order.clone(),
Usd(dec!(1000)),
CfdState::PendingTakeRequest {
CfdState::OutgoingOrderRequest {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
@ -481,7 +481,7 @@ mod tests {
let mut cfd = Cfd::new(
order.clone(),
Usd(dec!(1000)),
CfdState::PendingTakeRequest {
CfdState::OutgoingOrderRequest {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},

3
daemon/src/maker.rs

@ -238,7 +238,8 @@ async fn main() -> Result<()> {
rocket::routes![
routes_maker::maker_feed,
routes_maker::post_sell_order,
// routes_maker::post_confirm_order,
routes_maker::post_accept_order,
routes_maker::post_reject_order,
routes_maker::get_health_check
],
)

145
daemon/src/maker_cfd_actor.rs

@ -23,6 +23,14 @@ pub struct TakeOrder {
pub quantity: Usd,
}
pub struct AcceptOrder {
pub order_id: OrderId,
}
pub struct RejectOrder {
pub order_id: OrderId,
}
pub struct NewOrder(pub Order);
pub struct NewTakerOnline {
@ -178,7 +186,7 @@ impl MakerCfdActor {
Ok(())
}
async fn handle_take_order(&mut self, msg: TakeOrder, ctx: &mut Context<Self>) -> Result<()> {
async fn handle_take_order(&mut self, msg: TakeOrder) -> Result<()> {
let TakeOrder {
taker_id,
order_id,
@ -207,32 +215,80 @@ impl MakerCfdActor {
};
// 2. Insert CFD in DB
// TODO: Don't auto-accept, present to user in UI instead
let cfd = Cfd::new(
current_order.clone(),
quantity,
CfdState::Accepted {
msg.quantity,
CfdState::IncomingOrderRequest {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
taker_id,
},
);
insert_cfd(cfd, &mut conn).await?;
self.cfd_feed_actor_inbox
.send(load_all_cfds(&mut conn).await?)?;
// 3. Remove current order
self.current_order_id = None;
self.takers()?
.do_send_async(maker_inc_connections_actor::BroadcastOrder(None))
.await?;
self.order_feed_sender.send(None)?;
Ok(())
}
async fn handle_accept_order(
&mut self,
msg: AcceptOrder,
ctx: &mut Context<Self>,
) -> Result<()> {
tracing::debug!(%msg.order_id, "Maker accepts an order" );
let mut conn = self.db.acquire().await?;
// Validate if order is still valid
let cfd = load_cfd_by_order_id(msg.order_id, &mut conn).await?;
let taker_id = match cfd {
Cfd {
state: CfdState::IncomingOrderRequest { taker_id, .. },
..
} => taker_id,
_ => {
anyhow::bail!("Order is in invalid state. Ignoring trying to accept it.")
}
};
// Update order in db
let order_id = cfd.order.id;
insert_new_cfd_state_by_order_id(
order_id,
CfdState::Accepted {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
},
&mut conn,
)
.await
.unwrap();
self.takers()?
.do_send_async(maker_inc_connections_actor::TakerMessage {
taker_id,
command: TakerCommand::NotifyOrderAccepted { id: order_id },
command: TakerCommand::NotifyOrderAccepted { id: msg.order_id },
})
.await?;
self.cfd_feed_actor_inbox
.send(load_all_cfds(&mut conn).await?)?;
// 3. Remove current order
self.current_order_id = None;
self.takers()?
.do_send_async(maker_inc_connections_actor::BroadcastOrder(None))
.await?;
self.current_order_id = None;
self.order_feed_sender.send(None)?;
// 4. Start contract setup
@ -300,7 +356,56 @@ impl MakerCfdActor {
Ok(())
}
async fn handle_reject_order(&mut self, msg: RejectOrder) -> Result<()> {
tracing::debug!(%msg.order_id, "Maker rejects an order" );
let mut conn = self.db.acquire().await?;
let cfd = load_cfd_by_order_id(msg.order_id, &mut conn).await?;
let taker_id = match cfd {
Cfd {
state: CfdState::IncomingOrderRequest { taker_id, .. },
..
} => taker_id,
_ => {
anyhow::bail!("Order is in invalid state. Ignoring trying to accept it.")
}
};
// Update order in db
insert_new_cfd_state_by_order_id(
msg.order_id,
CfdState::Rejected {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
},
&mut conn,
)
.await
.unwrap();
self.takers()?
.do_send_async(maker_inc_connections_actor::TakerMessage {
taker_id,
command: TakerCommand::NotifyOrderRejected { id: msg.order_id },
})
.await?;
self.cfd_feed_actor_inbox
.send(load_all_cfds(&mut conn).await?)?;
// Remove order for all
self.current_order_id = None;
self.takers()?
.do_send_async(maker_inc_connections_actor::BroadcastOrder(None))
.await?;
self.order_feed_sender.send(None)?;
Ok(())
}
}
#[async_trait]
impl Handler<Initialized> for MakerCfdActor {
async fn handle(&mut self, msg: Initialized, _ctx: &mut Context<Self>) {
@ -318,8 +423,22 @@ macro_rules! log_error {
#[async_trait]
impl Handler<TakeOrder> for MakerCfdActor {
async fn handle(&mut self, msg: TakeOrder, ctx: &mut Context<Self>) {
log_error!(self.handle_take_order(msg, ctx))
async fn handle(&mut self, msg: TakeOrder, _ctx: &mut Context<Self>) {
log_error!(self.handle_take_order(msg))
}
}
#[async_trait]
impl Handler<AcceptOrder> for MakerCfdActor {
async fn handle(&mut self, msg: AcceptOrder, ctx: &mut Context<Self>) {
log_error!(self.handle_accept_order(msg, ctx))
}
}
#[async_trait]
impl Handler<RejectOrder> for MakerCfdActor {
async fn handle(&mut self, msg: RejectOrder, _ctx: &mut Context<Self>) {
log_error!(self.handle_reject_order(msg))
}
}
@ -386,4 +505,12 @@ impl Message for SyncWallet {
type Result = ();
}
impl Message for AcceptOrder {
type Result = ();
}
impl Message for RejectOrder {
type Result = ();
}
impl xtra::Actor for MakerCfdActor {}

6
daemon/src/maker_inc_connections_actor.rs

@ -26,6 +26,7 @@ pub enum TakerCommand {
SendOrder { order: Option<Order> },
NotifyInvalidOrderId { id: OrderId },
NotifyOrderAccepted { id: OrderId },
NotifyOrderRejected { id: OrderId },
OutProtocolMsg { setup_msg: SetupMsg },
}
@ -88,7 +89,10 @@ impl MakerIncConnectionsActor {
self.send_to_taker(msg.taker_id, wire::MakerToTaker::InvalidOrderId(id))?;
}
TakerCommand::NotifyOrderAccepted { id } => {
self.send_to_taker(msg.taker_id, wire::MakerToTaker::ConfirmTakeOrder(id))?;
self.send_to_taker(msg.taker_id, wire::MakerToTaker::ConfirmOrder(id))?;
}
TakerCommand::NotifyOrderRejected { id } => {
self.send_to_taker(msg.taker_id, wire::MakerToTaker::RejectOrder(id))?;
}
TakerCommand::OutProtocolMsg { setup_msg } => {
self.send_to_taker(msg.taker_id, wire::MakerToTaker::Protocol(setup_msg))?;

32
daemon/src/model/cfd.rs

@ -1,4 +1,4 @@
use crate::model::{Leverage, Position, TradingPair, Usd};
use crate::model::{Leverage, Position, TakerId, TradingPair, Usd};
use anyhow::Result;
use bdk::bitcoin::secp256k1::{SecretKey, Signature};
use bdk::bitcoin::{Amount, Transaction};
@ -134,19 +134,21 @@ pub struct CfdStateCommon {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", content = "payload")]
pub enum CfdState {
/// The taker has requested to take a CFD, but has not messaged the maker yet.
/// The taker sent an order to the maker to open the CFD but doesn't have a response yet.
///
/// This state only applies to the taker.
TakeRequested {
/// This state applies to taker only.
OutgoingOrderRequest {
common: CfdStateCommon,
},
/// The taker sent an open request to the maker to open the CFD but don't have a response yet.
/// The maker received an order from the taker to open the CFD but doesn't have a response yet.
///
/// This state applies to taker and maker.
/// Initial state for the maker.
PendingTakeRequest {
/// This state applies to the maker only.
IncomingOrderRequest {
common: CfdStateCommon,
taker_id: TakerId,
},
/// The maker has accepted the CFD take request, but the contract is not set up on chain yet.
///
/// This state applies to taker and maker.
@ -154,7 +156,7 @@ pub enum CfdState {
common: CfdStateCommon,
},
/// The maker rejected the CFD take request.
/// The maker rejected the CFD order.
///
/// This state applies to taker and maker.
Rejected {
@ -212,8 +214,8 @@ pub enum CfdState {
impl CfdState {
fn get_common(&self) -> CfdStateCommon {
let common = match self {
CfdState::TakeRequested { common } => common,
CfdState::PendingTakeRequest { common } => common,
CfdState::OutgoingOrderRequest { common } => common,
CfdState::IncomingOrderRequest { common, .. } => common,
CfdState::Accepted { common } => common,
CfdState::Rejected { common } => common,
CfdState::ContractSetup { common } => common,
@ -236,11 +238,11 @@ impl CfdState {
impl Display for CfdState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
CfdState::TakeRequested { .. } => {
write!(f, "Take Requested")
CfdState::OutgoingOrderRequest { .. } => {
write!(f, "Request sent")
}
CfdState::PendingTakeRequest { .. } => {
write!(f, "Pending Take Request")
CfdState::IncomingOrderRequest { .. } => {
write!(f, "Requested")
}
CfdState::Accepted { .. } => {
write!(f, "Accepted")

59
daemon/src/routes_maker.rs

@ -1,6 +1,6 @@
use crate::auth::Authenticated;
use crate::maker_cfd_actor::{self, MakerCfdActor};
use crate::model::cfd::{Cfd, Order, Origin};
use crate::model::cfd::{Cfd, Order, OrderId, Origin};
use crate::model::{Usd, WalletInfo};
use crate::routes::EmbeddedFileExt;
use crate::to_sse_event::ToSseEvent;
@ -122,27 +122,42 @@ pub struct PromptAuthentication {
www_authenticate: Header<'static>,
}
// // TODO: Shall we use a simpler struct for verification? AFAICT quantity is not
// // needed, no need to send the whole CFD either as the other fields can be generated from the
// order #[rocket::post("/order/confirm", data = "<cfd_confirm_order_request>")]
// pub async fn post_confirm_order(
// cfd_confirm_order_request: Json<CfdTakeRequest>,
// queue: &State<mpsc::Sender<CfdOrder>>,
// mut conn: Connection<Db>,
// ) -> Result<status::Accepted<()>, status::BadRequest<String>> {
// dbg!(&cfd_confirm_order_request);
// let order = db::load_order_by_id_from_conn(cfd_confirm_order_request.order_id, &mut conn)
// .await
// .map_err(|e| status::BadRequest(Some(e.to_string())))?;
// let _res = queue
// .send(order)
// .await
// .map_err(|_| status::BadRequest(Some("internal server error".to_string())))?;
// Ok(status::Accepted(None))
// }
/// The maker POSTs this to accept an order
#[derive(Debug, Clone, Deserialize)]
pub struct AcceptOrRejectOrderRequest {
pub order_id: OrderId,
}
#[rocket::post("/order/accept", data = "<cfd_accept_order_request>")]
pub async fn post_accept_order(
cfd_accept_order_request: Json<AcceptOrRejectOrderRequest>,
cfd_actor_address: &State<Address<MakerCfdActor>>,
_auth: Authenticated,
) -> status::Accepted<()> {
cfd_actor_address
.do_send_async(maker_cfd_actor::AcceptOrder {
order_id: cfd_accept_order_request.order_id,
})
.await
.expect("actor to always be available");
status::Accepted(None)
}
#[rocket::post("/order/reject", data = "<cfd_reject_order_request>")]
pub async fn post_reject_order(
cfd_reject_order_request: Json<AcceptOrRejectOrderRequest>,
cfd_actor_address: &State<Address<MakerCfdActor>>,
_auth: Authenticated,
) -> status::Accepted<()> {
cfd_actor_address
.do_send_async(maker_cfd_actor::RejectOrder {
order_id: cfd_reject_order_request.order_id,
})
.await
.expect("actor to always be available");
status::Accepted(None)
}
#[rocket::get("/alive")]
pub fn get_health_check() {}

14
daemon/src/routes_taker.rs

@ -56,20 +56,20 @@ pub async fn feed(
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CfdTakeRequest {
pub struct CfdOrderRequest {
pub order_id: OrderId,
pub quantity: Usd,
}
#[rocket::post("/cfd", data = "<cfd_take_request>")]
pub async fn post_cfd(
cfd_take_request: Json<CfdTakeRequest>,
#[rocket::post("/cfd", data = "<cfd_order_request>")]
pub async fn post_order_request(
cfd_order_request: Json<CfdOrderRequest>,
cfd_actor_inbox: &State<mpsc::UnboundedSender<taker_cfd_actor::Command>>,
) {
cfd_actor_inbox
.send(taker_cfd_actor::Command::TakeOrder {
order_id: cfd_take_request.order_id,
quantity: cfd_take_request.quantity,
.send(taker_cfd_actor::Command::TakeOffer {
order_id: cfd_order_request.order_id,
quantity: cfd_order_request.quantity,
})
.expect("actor to never disappear");
}

2
daemon/src/taker.rs

@ -191,7 +191,7 @@ async fn main() -> Result<()> {
"/api",
rocket::routes![
routes_taker::feed,
routes_taker::post_cfd,
routes_taker::post_order_request,
routes_taker::get_health_check,
routes_taker::margin_calc,
],

28
daemon/src/taker_cfd_actor.rs

@ -16,9 +16,10 @@ use tokio::sync::{mpsc, watch};
#[allow(clippy::large_enum_variant)]
pub enum Command {
SyncWallet,
TakeOrder { order_id: OrderId, quantity: Usd },
TakeOffer { order_id: OrderId, quantity: Usd },
NewOrder(Option<Order>),
OrderAccepted(OrderId),
OrderRejected(OrderId),
IncProtocolMsg(SetupMsg),
CfdSetupCompleted { order_id: OrderId, dlc: Dlc },
}
@ -52,17 +53,17 @@ pub fn new(
let wallet_info = wallet.sync().await.unwrap();
wallet_feed_sender.send(wallet_info).unwrap();
}
Command::TakeOrder { order_id, quantity } => {
Command::TakeOffer { order_id, quantity } => {
let mut conn = db.acquire().await.unwrap();
let current_order = load_order_by_id(order_id, &mut conn).await.unwrap();
tracing::info!("Accepting current order: {:?}", &current_order);
tracing::info!("Taking current order: {:?}", &current_order);
let cfd = Cfd::new(
current_order.clone(),
quantity,
CfdState::PendingTakeRequest {
CfdState::OutgoingOrderRequest {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
@ -144,6 +145,25 @@ pub fn new(
});
current_contract_setup = Some(inbox);
}
Command::OrderRejected(order_id) => {
tracing::debug!(%order_id, "Order rejected");
let mut conn = db.acquire().await.unwrap();
insert_new_cfd_state_by_order_id(
order_id,
CfdState::Rejected {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
},
&mut conn,
)
.await
.unwrap();
cfd_feed_actor_inbox
.send(load_all_cfds(&mut conn).await.unwrap())
.unwrap();
}
Command::IncProtocolMsg(msg) => {
let inbox = match &current_contract_setup {
None => {

7
daemon/src/taker_inc_message_actor.rs

@ -28,12 +28,17 @@ pub fn new(
.send(taker_cfd_actor::Command::NewOrder(order))
.unwrap();
}
Ok(wire::MakerToTaker::ConfirmTakeOrder(order_id)) => {
Ok(wire::MakerToTaker::ConfirmOrder(order_id)) => {
// TODO: This naming is not well aligned.
cfd_actor
.send(taker_cfd_actor::Command::OrderAccepted(order_id))
.unwrap();
}
Ok(wire::MakerToTaker::RejectOrder(order_id)) => {
cfd_actor
.send(taker_cfd_actor::Command::OrderRejected(order_id))
.unwrap();
}
Ok(wire::MakerToTaker::InvalidOrderId(_)) => {
todo!()
}

4
daemon/src/wire.rs

@ -22,8 +22,8 @@ pub enum TakerToMaker {
#[allow(clippy::large_enum_variant)]
pub enum MakerToTaker {
CurrentOrder(Option<Order>),
// TODO: Needs RejectOrder as well
ConfirmTakeOrder(OrderId), // TODO: Include payout curve in "accept" message from maker
ConfirmOrder(OrderId), // TODO: Include payout curve in "accept" message from maker
RejectOrder(OrderId),
InvalidOrderId(OrderId),
Protocol(SetupMsg),
}

14
docs/asset/mvp_maker_taker_messaging.puml

@ -36,17 +36,17 @@ BuyerApp -> BuyerOrderFeed: push order
BuyerOrderFeed --> Buyer: order
Buyer -> Buyer: Click BUY
Buyer -> BuyerApp: POST cfd_take_request
BuyerApp -> BuyerApp: Create cfd [TakeRequested]
Buyer -> BuyerApp: POST cfd_order_request
BuyerApp -> BuyerApp: Create cfd [OrderRequest]
note over BuyerApp: Must include order_id
BuyerApp -> BuyerCfdFeed: Push cfd
BuyerCfdFeed --> Buyer: cfd [TakeRequested]
BuyerCfdFeed --> Buyer: cfd [OrderRequest]
BuyerApp -> SellerApp: {TCP} cfd_take_request (order_id, quantity)
SellerApp -> SellerApp: Create cfd [TakeRequested]
SellerApp -> SellerCfdFeed: cfd [TakeRequested]
SellerCfdFeed --> Seller: cfd [TakeRequested]
BuyerApp -> SellerApp: {TCP} cfd_order_request (order_id, quantity)
SellerApp -> SellerApp: Create cfd [OrderRequest]
SellerApp -> SellerCfdFeed: cfd [OrderRequest]
SellerCfdFeed --> Seller: cfd [OrderRequest]
Seller -> Seller: Accept cfd
Seller -> SellerApp: POST cfd [Accepted]
SellerApp -> BuyerApp: {TCP} cfd [Accepted]

23
frontend/src/Maker.tsx

@ -23,28 +23,7 @@ import NavLink from "./components/NavLink";
import OrderTile from "./components/OrderTile";
import { Cfd, Order, WalletInfo } from "./components/Types";
import Wallet from "./components/Wallet";
interface CfdSellOrderPayload {
price: number;
min_quantity: number;
max_quantity: number;
}
async function postCfdSellOrderRequest(payload: CfdSellOrderPayload) {
let res = await fetch(`/api/order/sell`, {
method: "POST",
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
},
credentials: "include",
});
if (!res.status.toString().startsWith("2")) {
console.log("Status: " + res.status + ", " + res.statusText);
throw new Error("failed to publish new order");
}
}
import { CfdSellOrderPayload, postCfdSellOrderRequest } from "./MakerClient";
export default function App() {
let source = useEventSource({ source: "/api/feed", options: { withCredentials: true } });

39
frontend/src/MakerClient.tsx

@ -0,0 +1,39 @@
export interface CfdSellOrderPayload {
price: number;
min_quantity: number;
max_quantity: number;
}
export interface AcceptOrderRequestPayload {
order_id: string;
}
export async function postCfdSellOrderRequest(payload: CfdSellOrderPayload) {
let res = await fetch(`/api/order/sell`, {
method: "POST",
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
},
credentials: "include",
});
if (!res.status.toString().startsWith("2")) {
console.log("Status: " + res.status + ", " + res.statusText);
throw new Error("failed to publish new order");
}
}
export async function postAcceptOrder(payload: AcceptOrderRequestPayload) {
let res = await fetch(
`/api/order/accept`,
{ method: "POST", body: JSON.stringify(payload), credentials: "include" },
);
}
export async function postRejectOrder(payload: AcceptOrderRequestPayload) {
let res = await fetch(
`/api/order/reject`,
{ method: "POST", body: JSON.stringify(payload), credentials: "include" },
);
}

18
frontend/src/Taker.tsx

@ -11,7 +11,7 @@ import NavLink from "./components/NavLink";
import { Cfd, Order, WalletInfo } from "./components/Types";
import Wallet from "./components/Wallet";
interface CfdTakeRequestPayload {
interface CfdOrderRequestPayload {
order_id: string;
quantity: number;
}
@ -26,11 +26,11 @@ interface MarginResponse {
margin: number;
}
async function postCfdTakeRequest(payload: CfdTakeRequestPayload) {
async function postCfdOrderRequest(payload: CfdOrderRequestPayload) {
let res = await fetch(`/api/cfd`, { method: "POST", body: JSON.stringify(payload) });
if (!res.status.toString().startsWith("2")) {
throw new Error("failed to create new CFD take request: " + res.status + ", " + res.statusText);
throw new Error("failed to create new CFD order request: " + res.status + ", " + res.statusText);
}
}
@ -38,7 +38,7 @@ async function getMargin(payload: MarginRequestPayload): Promise<MarginResponse>
let res = await fetch(`/api/calculate/margin`, { method: "POST", body: JSON.stringify(payload) });
if (!res.status.toString().startsWith("2")) {
throw new Error("failed to create new CFD take request: " + res.status + ", " + res.statusText);
throw new Error("failed to create new CFD order request: " + res.status + ", " + res.statusText);
}
return res.json();
@ -77,10 +77,10 @@ export default function App() {
const format = (val: any) => `$` + val;
const parse = (val: any) => val.replace(/^\$/, "");
let { run: makeNewTakeRequest, isLoading: isCreatingNewTakeRequest } = useAsync({
let { run: makeNewOrderRequest, isLoading: isCreatingNewOrderRequest } = useAsync({
deferFn: async ([payload]: any[]) => {
try {
await postCfdTakeRequest(payload as CfdTakeRequestPayload);
await postCfdOrderRequest(payload as CfdOrderRequestPayload);
} catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e);
@ -175,15 +175,15 @@ export default function App() {
<Button colorScheme="blue" variant="solid">x{order?.leverage}</Button>
</Flex>
{<Button
disabled={isCreatingNewTakeRequest || !order}
disabled={isCreatingNewOrderRequest || !order}
variant={"solid"}
colorScheme={"blue"}
onClick={() => {
let payload: CfdTakeRequestPayload = {
let payload: CfdOrderRequestPayload = {
order_id: order!.id,
quantity: Number.parseFloat(quantity),
};
makeNewTakeRequest(payload);
makeNewOrderRequest(payload);
}}
>
BUY

85
frontend/src/components/CfdTile.tsx

@ -1,5 +1,7 @@
import { Box, Button, SimpleGrid, Text, VStack } from "@chakra-ui/react";
import { Box, Button, HStack, SimpleGrid, Text, useToast, VStack } from "@chakra-ui/react";
import React from "react";
import { useAsync } from "react-async";
import { postAcceptOrder, postRejectOrder } from "../MakerClient";
import { Cfd, unixTimestampToDate } from "./Types";
interface CfdTileProps {
@ -13,6 +15,84 @@ export default function CfdTile(
cfd,
}: CfdTileProps,
) {
const toast = useToast();
let { run: acceptOrder, isLoading: isAccepting } = useAsync({
deferFn: async ([args]: any[]) => {
try {
let payload = {
order_id: args.order_id,
};
await postAcceptOrder(payload);
} catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 9000,
isClosable: true,
});
}
},
});
let { run: rejectOrder, isLoading: isRejecting } = useAsync({
deferFn: async ([args]: any[]) => {
try {
let payload = {
order_id: args.order_id,
};
await postRejectOrder(payload);
} catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 9000,
isClosable: true,
});
}
},
});
let actionButtons;
if (cfd.state === "Open") {
actionButtons = <Box paddingBottom={5}>
<Button colorScheme="blue" variant="solid">
Close
</Button>
</Box>;
} else if (cfd.state == "Requested") {
actionButtons = (
<HStack>
<Box paddingBottom={5}>
<Button
colorScheme="blue"
variant="solid"
onClick={async () => acceptOrder(cfd)}
isLoading={isAccepting}
>
Accept
</Button>
</Box>
<Box paddingBottom={5}>
<Button
colorScheme="blue"
variant="solid"
onClick={async () => rejectOrder(cfd)}
isLoading={isRejecting}
>
Reject
</Button>
</Box>
</HStack>
);
}
return (
<Box borderRadius={"md"} borderColor={"blue.800"} borderWidth={2} bg={"gray.50"}>
<VStack>
@ -51,8 +131,7 @@ export default function CfdTile(
<Text>Status</Text>
<Text>{cfd.state}</Text>
</SimpleGrid>
{cfd.state === "Open"
&& <Box paddingBottom={5}><Button colorScheme="blue" variant="solid">Close</Button></Box>}
{actionButtons}
</VStack>
</Box>
);

Loading…
Cancel
Save