diff --git a/daemon/src/maker_cfd.rs b/daemon/src/maker_cfd.rs index fa4862b..fd3c5b3 100644 --- a/daemon/src/maker_cfd.rs +++ b/daemon/src/maker_cfd.rs @@ -5,7 +5,8 @@ use crate::db::{ }; use crate::maker_inc_connections::TakerCommand; use crate::model::cfd::{ - Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, SettlementProposal, + Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Role, + SettlementProposal, }; use crate::model::{TakerId, Usd}; use crate::monitor::MonitorParams; @@ -29,6 +30,10 @@ pub struct RejectOrder { pub order_id: OrderId, } +pub struct Commit { + pub order_id: OrderId, +} + pub struct NewOrder(pub Order); pub struct NewTakerOnline { @@ -93,6 +98,13 @@ impl Actor { tracing::info!("Refund transaction published on chain: {}", txid); } + for cfd in cfds.iter().filter(|cfd| Cfd::is_pending_commit(cfd)) { + let signed_commit_tx = cfd.commit_tx()?; + let txid = wallet.try_broadcast_transaction(signed_commit_tx).await?; + + tracing::info!("Commit transaction published on chain: {}", txid); + } + Ok(Self { db, wallet, @@ -347,7 +359,7 @@ impl Actor { (self.oracle_pk, nonce_pks), cfd, self.wallet.clone(), - setup_contract::Role::Maker, + Role::Maker, ); let this = ctx @@ -417,6 +429,25 @@ impl Actor { Ok(()) } + async fn handle_commit(&mut self, order_id: OrderId) -> Result<()> { + let mut conn = self.db.acquire().await?; + let cfd = load_cfd_by_order_id(order_id, &mut conn).await?; + + let signed_commit_tx = cfd.commit_tx()?; + + let txid = self + .wallet + .try_broadcast_transaction(signed_commit_tx) + .await?; + + tracing::info!("Commit transaction published on chain: {}", txid); + + let new_state = cfd.handle(CfdStateChangeEvent::CommitTxSent)?; + insert_new_cfd_state_by_order_id(cfd.order.id, new_state, &mut conn).await?; + + Ok(()) + } + async fn handle_monitoring_event(&mut self, event: monitor::Event) -> Result<()> { let order_id = event.order_id(); @@ -479,6 +510,13 @@ impl Handler for Actor { } } +#[async_trait] +impl Handler for Actor { + async fn handle(&mut self, msg: Commit, _ctx: &mut Context) { + log_error!(self.handle_commit(msg.order_id)) + } +} + #[async_trait] impl Handler for Actor { async fn handle(&mut self, msg: NewOrder, _ctx: &mut Context) { @@ -586,6 +624,10 @@ impl Message for RejectOrder { type Result = (); } +impl Message for Commit { + type Result = (); +} + // this signature is a bit different because we use `Address::attach_stream` impl Message for TakerStreamMessage { type Result = KeepRunning; diff --git a/daemon/src/model/cfd.rs b/daemon/src/model/cfd.rs index 67de33d..d665a72 100644 --- a/daemon/src/model/cfd.rs +++ b/daemon/src/model/cfd.rs @@ -39,12 +39,30 @@ impl<'v> FromParam<'v> for OrderId { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +// TODO: Could potentially remove this and use the Role in the Order instead +/// Origin of the order +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] pub enum Origin { Ours, Theirs, } +/// Role in the Cfd +#[derive(Debug, Copy, Clone)] +pub enum Role { + Maker, + Taker, +} + +impl From for Role { + fn from(origin: Origin) -> Self { + match origin { + Origin::Ours => Role::Maker, + Origin::Theirs => Role::Taker, + } + } +} + /// A concrete order created by a maker for a taker #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Order { @@ -198,6 +216,15 @@ pub enum CfdState { dlc: Dlc, }, + /// The commit transaction was published but it not final yet + /// + /// This state applies to taker and maker. + /// This state is needed, because otherwise the user does not get any feedback. + PendingCommit { + common: CfdStateCommon, + dlc: Dlc, + }, + // TODO: At the moment we are appending to this state. The way this is handled internally is // by inserting the same state with more information in the database. We could consider // changing this to insert different states or update the stae instead of inserting again. @@ -259,6 +286,7 @@ impl CfdState { CfdState::MustRefund { common, .. } => common, CfdState::Refunded { common, .. } => common, CfdState::SetupFailed { common, .. } => common, + CfdState::PendingCommit { common, .. } => common, }; *common @@ -293,6 +321,9 @@ impl Display for CfdState { CfdState::Open { .. } => { write!(f, "Open") } + CfdState::PendingCommit { .. } => { + write!(f, "Pending Committ") + } CfdState::OpenCommitted { .. } => { write!(f, "Open Committed") } @@ -428,41 +459,32 @@ impl Cfd { let new_state = match event { CfdStateChangeEvent::Monitor(event) => match event { - monitor::Event::LockFinality(_) => match self.state.clone() { - PendingOpen { dlc, .. } => CfdState::Open { - common: CfdStateCommon { - transition_timestamp: SystemTime::now(), - }, - dlc, - }, - OutgoingOrderRequest { .. } => unreachable!("taker-only state"), - IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => { - bail!("Did not expect lock finality yet: ignoring") - } - Open { .. } | OpenCommitted { .. } | MustRefund { .. } => { - bail!("State already assumes lock finality: ignoring") - } - Rejected { .. } | Refunded { .. } | SetupFailed { .. } => { - bail!("The cfd is already in final state {}", self.state) + monitor::Event::LockFinality(_) => { + if let PendingOpen { dlc, .. } = self.state.clone() { + CfdState::Open { + common: CfdStateCommon { + transition_timestamp: SystemTime::now(), + }, + dlc, + } + } else { + bail!( + "Cannot transition to Open because of unexpected state {}", + self.state + ) } - }, + } monitor::Event::CommitFinality(_) => { - let dlc = match self.state.clone() { - Open { dlc, .. } => dlc, - PendingOpen { dlc, .. } => { - tracing::debug!(%order_id, "Was waiting on lock finality, jumping ahead"); - dlc - } - OutgoingOrderRequest { .. } => unreachable!("taker-only state"), - IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => { - bail!("Did not expect commit finality yet: ignoring") - } - OpenCommitted { .. } | MustRefund { .. } => { - bail!("State already assumes commit finality: ignoring") - } - Rejected { .. } | Refunded { .. } | SetupFailed { .. } => { - bail!("The cfd is already in final state {}", self.state) - } + let dlc = if let Open { dlc, .. } = self.state.clone() { + dlc + } else if let PendingOpen { dlc, .. } = self.state.clone() { + tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to OpenCommitted", self.state); + dlc + } else { + bail!( + "Cannot transition to OpenCommitted because of unexpected state {}", + self.state + ) }; OpenCommitted { @@ -496,8 +518,8 @@ impl Cfd { dlc, cet_status: CetStatus::Ready(price), }, - PendingOpen { dlc, .. } => { - tracing::debug!(%order_id, "Was waiting on lock finality, jumping ahead"); + PendingOpen { dlc, .. } | Open { dlc, .. } => { + tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to MustRefund", self.state); CfdState::OpenCommitted { common: CfdStateCommon { transition_timestamp: SystemTime::now(), @@ -506,56 +528,22 @@ impl Cfd { cet_status: CetStatus::TimelockExpired, } } - Open { dlc, .. } => { - tracing::debug!(%order_id, "Was not aware of commit TX broadcast, jumping ahead"); - CfdState::OpenCommitted { - common: CfdStateCommon { - transition_timestamp: SystemTime::now(), - }, - dlc, - cet_status: CetStatus::TimelockExpired, - } - } - OutgoingOrderRequest { .. } => unreachable!("taker-only state"), - IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => { - bail!("Did not expect CET timelock expiry yet: ignoring") - } - OpenCommitted { - cet_status: CetStatus::TimelockExpired, - .. - } - | OpenCommitted { - cet_status: CetStatus::Ready(_), - .. - } => bail!("State already assumes CET timelock expiry: ignoring"), - MustRefund { .. } => { - bail!("Refund path does not care about CET timelock expiry: ignoring") - } - Rejected { .. } | Refunded { .. } | SetupFailed { .. } => { - bail!("The cfd is already in final state {}", self.state) - } + _ => bail!( + "Cannot transition to OpenCommitted because of unexpected state {}", + self.state + ), }, monitor::Event::RefundTimelockExpired(_) => { - let dlc = match self.state.clone() { - OpenCommitted { dlc, .. } => dlc, - MustRefund { .. } => { - bail!("State already assumes refund timelock expiry: ignoring") - } - OutgoingOrderRequest { .. } => unreachable!("taker-only state"), - IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => { - bail!("Did not expect refund timelock expiry yet: ignoring") - } - PendingOpen { dlc, .. } => { - tracing::debug!(%order_id, "Was waiting on lock finality, jumping ahead"); - dlc - } - Open { dlc, .. } => { - tracing::debug!(%order_id, "Was waiting on CET timelock expiry, jumping ahead"); - dlc - } - Rejected { .. } | Refunded { .. } | SetupFailed { .. } => { - bail!("The cfd is already in final state {}", self.state) - } + let dlc = if let OpenCommitted { dlc, .. } = self.state.clone() { + dlc + } else if let Open { dlc, .. } | PendingOpen { dlc, .. } = self.state.clone() { + tracing::debug!(%order_id, "Was in unexpected state {}, jumping ahead to MustRefund", self.state); + dlc + } else { + bail!( + "Cannot transition to OpenCommitted because of unexpected state {}", + self.state + ) }; MustRefund { @@ -566,24 +554,12 @@ impl Cfd { } } monitor::Event::RefundFinality(_) => { - match self.state { - MustRefund { .. } => (), - OutgoingOrderRequest { .. } => unreachable!("taker-only state"), - IncomingOrderRequest { .. } | Accepted { .. } | ContractSetup { .. } => { - bail!("Did not expect refund finality yet: ignoring") - } - PendingOpen { .. } => { - tracing::debug!(%order_id, "Was waiting on lock finality, jumping ahead"); - } - Open { .. } => { - tracing::debug!(%order_id, "Was waiting on CET timelock expiry, jumping ahead"); - } - OpenCommitted { .. } => { - tracing::debug!(%order_id, "Was waiting on refund timelock expiry, jumping ahead"); - } - Rejected { .. } | Refunded { .. } | SetupFailed { .. } => { - bail!("The cfd is already in final state {}", self.state) - } + if let MustRefund { .. } = self.state.clone() { + } else { + tracing::debug!( + "Was in unexpected state {}, jumping ahead to Refunded", + self.state + ); } Refunded { @@ -596,6 +572,23 @@ impl Cfd { todo!("Implement state transition") } }, + CfdStateChangeEvent::CommitTxSent => { + let dlc = if let Open { dlc, .. } | PendingOpen { dlc, .. } = self.state.clone() { + dlc + } else { + bail!( + "Cannot transition to PendingCommit because of unexpected state {}", + self.state + ) + }; + + PendingCommit { + common: CfdStateCommon { + transition_timestamp: SystemTime::now(), + }, + dlc, + } + } }; Ok(new_state) @@ -630,6 +623,43 @@ impl Cfd { Ok(signed_refund_tx) } + pub fn commit_tx(&self) -> Result { + let dlc = if let CfdState::Open { dlc, .. } | CfdState::PendingOpen { dlc, .. } = + self.state.clone() + { + dlc + } else { + bail!( + "Cannot publish commit transaction in state {}", + self.state.clone() + ) + }; + + let sig_hash = spending_tx_sighash( + &dlc.commit.0, + &dlc.lock.1, + Amount::from_sat(dlc.lock.0.output[0].value), + ); + let our_sig = SECP256K1.sign(&sig_hash, &dlc.identity); + let our_pubkey = PublicKey::new(bdk::bitcoin::secp256k1::PublicKey::from_secret_key( + SECP256K1, + &dlc.identity, + )); + + // TODO: verify that the dlc's `publish` sk corresponds to the decryption_sk we need here + let counterparty_sig = dlc.commit.1.decrypt(&dlc.publish)?; + let counterparty_pubkey = dlc.identity_counterparty; + + let signed_commit_tx = finalize_spend_transaction( + dlc.commit.0, + &dlc.commit.2, + (our_pubkey, our_sig), + (counterparty_pubkey, counterparty_sig), + )?; + + Ok(signed_commit_tx) + } + pub fn pending_open_dlc(&self) -> Option { if let CfdState::PendingOpen { dlc, .. } = self.state.clone() { Some(dlc) @@ -642,6 +672,10 @@ impl Cfd { matches!(self.state.clone(), CfdState::MustRefund { .. }) } + pub fn is_pending_commit(&self) -> bool { + matches!(self.state.clone(), CfdState::PendingCommit { .. }) + } + pub fn is_cleanup(&self) -> bool { matches!( self.state.clone(), @@ -651,13 +685,18 @@ impl Cfd { | CfdState::ContractSetup { .. } ) } + + pub fn role(&self) -> Role { + self.order.origin.into() + } } #[derive(Debug, Clone)] pub enum CfdStateChangeEvent { - // TODO: groupd other events by actors into enums and add them here so we can bundle all + // TODO: group other events by actors into enums and add them here so we can bundle all // transitions into cfd.transition_to(...) Monitor(monitor::Event), + CommitTxSent, } fn calculate_profit( diff --git a/daemon/src/monitor.rs b/daemon/src/monitor.rs index 9ab6913..6f3685c 100644 --- a/daemon/src/monitor.rs +++ b/daemon/src/monitor.rs @@ -68,7 +68,7 @@ where actor.cfds.insert(cfd.order.id, params.clone()); actor.monitor_all(¶ms, cfd.order.id).await; } - CfdState::Open { dlc, .. } => { + CfdState::Open { dlc, .. } | CfdState::PendingCommit { dlc, .. } => { let params = MonitorParams::from_dlc_and_timelocks(dlc.clone(), cfd.refund_timelock_in_blocks()); actor.cfds.insert(cfd.order.id, params.clone()); diff --git a/daemon/src/routes_maker.rs b/daemon/src/routes_maker.rs index b1b8e41..018f807 100644 --- a/daemon/src/routes_maker.rs +++ b/daemon/src/routes_maker.rs @@ -145,6 +145,17 @@ pub async fn post_cfd_action( .await .expect("actor to always be available"); } + CfdAction::Commit => { + cfd_actor_address + .do_send_async(maker_cfd::Commit { order_id: id }) + .await + .expect("actor to always be available"); + } + CfdAction::Settle => { + return Err(status::BadRequest(Some( + "Collaborative settlement can only be triggered by taker".to_string(), + ))); + } } Ok(status::Accepted(None)) diff --git a/daemon/src/routes_taker.rs b/daemon/src/routes_taker.rs index 5bd82a9..f731e6b 100644 --- a/daemon/src/routes_taker.rs +++ b/daemon/src/routes_taker.rs @@ -1,7 +1,7 @@ use crate::model::cfd::{calculate_buy_margin, Cfd, Order, OrderId}; use crate::model::{Leverage, Usd, WalletInfo}; use crate::routes::EmbeddedFileExt; -use crate::to_sse_event::{CfdsWithCurrentPrice, ToSseEvent}; +use crate::to_sse_event::{CfdAction, CfdsWithCurrentPrice, ToSseEvent}; use crate::{bitmex_price_feed, taker_cfd}; use bdk::bitcoin::Amount; use rocket::http::{ContentType, Status}; @@ -96,20 +96,37 @@ pub async fn post_order_request( .expect("actor to always be available"); } -#[rocket::post("/cfd//settle")] -pub async fn post_settlement_proposal( +#[rocket::post("/cfd//")] +pub async fn post_cfd_action( id: OrderId, - cfd_actor_inbox: &State>, + action: CfdAction, + cfd_actor_address: &State>, quote_updates: &State>, -) { - let current_price = quote_updates.borrow().for_taker(); - cfd_actor_inbox - .do_send_async(taker_cfd::ProposeSettlement { - order_id: id, - current_price, - }) - .await - .expect("actor to always be available"); +) -> Result, status::BadRequest> { + match action { + CfdAction::Accept | CfdAction::Reject => { + return Err(status::BadRequest(None)); + } + + CfdAction::Commit => { + cfd_actor_address + .do_send_async(taker_cfd::Commit { order_id: id }) + .await + .map_err(|e| status::BadRequest(Some(e.to_string())))?; + } + CfdAction::Settle => { + let current_price = quote_updates.borrow().for_taker(); + cfd_actor_address + .do_send_async(taker_cfd::ProposeSettlement { + order_id: id, + current_price, + }) + .await + .expect("actor to always be available"); + } + } + + Ok(status::Accepted(None)) } #[rocket::get("/alive")] diff --git a/daemon/src/setup_contract.rs b/daemon/src/setup_contract.rs index 9f5d7d4..cefb625 100644 --- a/daemon/src/setup_contract.rs +++ b/daemon/src/setup_contract.rs @@ -1,4 +1,4 @@ -use crate::model::cfd::{Cfd, Dlc}; +use crate::model::cfd::{Cfd, Dlc, Role}; use crate::wallet::Wallet; use crate::wire::{Msg0, Msg1, Msg2, SetupMsg}; use crate::{model, payout_curve}; @@ -190,13 +190,6 @@ pub async fn new( }) } -#[allow(dead_code)] -/// Role of the actor's owner in the upcoming contract -pub enum Role { - Maker, - Taker, -} - /// A convenience struct for storing PartyParams and PunishParams of both /// parties and the role of the caller. struct AllParams { diff --git a/daemon/src/taker.rs b/daemon/src/taker.rs index 2c93fdb..a13d4b9 100644 --- a/daemon/src/taker.rs +++ b/daemon/src/taker.rs @@ -213,9 +213,9 @@ async fn main() -> Result<()> { rocket::routes![ routes_taker::feed, routes_taker::post_order_request, - routes_taker::post_settlement_proposal, routes_taker::get_health_check, routes_taker::margin_calc, + routes_taker::post_cfd_action, ], ) .mount( diff --git a/daemon/src/taker_cfd.rs b/daemon/src/taker_cfd.rs index 107e78a..d990bc9 100644 --- a/daemon/src/taker_cfd.rs +++ b/daemon/src/taker_cfd.rs @@ -4,7 +4,7 @@ use crate::db::{ load_cfd_by_order_id, load_order_by_id, }; use crate::model::cfd::{ - Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, + Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Role, }; use crate::model::Usd; use crate::monitor::{self, MonitorParams}; @@ -40,6 +40,10 @@ pub struct CfdSetupCompleted { pub dlc: Result, } +pub struct Commit { + pub order_id: OrderId, +} + enum SetupState { Active { sender: mpsc::UnboundedSender, @@ -86,6 +90,13 @@ impl Actor { tracing::info!("Refund transaction published on chain: {}", txid); } + for cfd in cfds.iter().filter(|cfd| Cfd::is_pending_commit(cfd)) { + let signed_commit_tx = cfd.commit_tx()?; + let txid = wallet.try_broadcast_transaction(signed_commit_tx).await?; + + tracing::info!("Commit transaction published on chain: {}", txid); + } + Ok(Self { db, wallet, @@ -215,7 +226,7 @@ impl Actor { (self.oracle_pk, nonce_pks), cfd, self.wallet.clone(), - setup_contract::Role::Taker, + Role::Taker, ); let this = ctx @@ -342,6 +353,26 @@ impl Actor { Ok(()) } + // TODO: Duplicated with maker + async fn handle_commit(&mut self, order_id: OrderId) -> Result<()> { + let mut conn = self.db.acquire().await?; + let cfd = load_cfd_by_order_id(order_id, &mut conn).await?; + + let signed_commit_tx = cfd.commit_tx()?; + + let txid = self + .wallet + .try_broadcast_transaction(signed_commit_tx) + .await?; + + tracing::info!("Commit transaction published on chain: {}", txid); + + let new_state = cfd.handle(CfdStateChangeEvent::CommitTxSent)?; + insert_new_cfd_state_by_order_id(cfd.order.id, new_state, &mut conn).await?; + + Ok(()) + } + async fn handle_oracle_announcements( &mut self, announcements: oracle::Announcements, @@ -441,6 +472,13 @@ impl Handler for Actor { } } +#[async_trait] +impl Handler for Actor { + async fn handle(&mut self, msg: Commit, _ctx: &mut Context) { + log_error!(self.handle_commit(msg.order_id)) + } +} + impl Message for TakeOffer { type Result = (); } @@ -458,4 +496,8 @@ impl Message for CfdSetupCompleted { type Result = (); } +impl Message for Commit { + type Result = (); +} + impl xtra::Actor for Actor {} diff --git a/daemon/src/to_sse_event.rs b/daemon/src/to_sse_event.rs index a3da356..7a59b51 100644 --- a/daemon/src/to_sse_event.rs +++ b/daemon/src/to_sse_event.rs @@ -1,4 +1,4 @@ -use crate::model::cfd::OrderId; +use crate::model::cfd::{OrderId, Role}; use crate::model::{Leverage, Position, TradingPair, Usd}; use crate::{bitmex_price_feed, model}; use bdk::bitcoin::Amount; @@ -36,6 +36,8 @@ pub struct Cfd { pub enum CfdAction { Accept, Reject, + Commit, + Settle, } impl<'v> FromParam<'v> for CfdAction { @@ -56,6 +58,7 @@ pub enum CfdState { ContractSetup, PendingOpen, Open, + PendingCommit, OpenCommitted, MustRefund, Refunded, @@ -113,7 +116,7 @@ impl ToSseEvent for CfdsWithCurrentPrice { profit_btc, profit_usd, state: cfd.state.clone().into(), - actions: actions_for_state(cfd.state.clone()), + actions: actions_for_state(cfd.state.clone(), cfd.role()), state_transition_timestamp: cfd .state .get_transition_timestamp() @@ -189,6 +192,7 @@ impl From for CfdState { model::cfd::CfdState::MustRefund { .. } => CfdState::MustRefund, model::cfd::CfdState::Refunded { .. } => CfdState::Refunded, model::cfd::CfdState::SetupFailed { .. } => CfdState::SetupFailed, + model::cfd::CfdState::PendingCommit { .. } => CfdState::PendingCommit, } } } @@ -218,11 +222,16 @@ fn into_unix_secs(time: SystemTime) -> u64 { .as_secs() } -fn actions_for_state(state: model::cfd::CfdState) -> Vec { - if let model::cfd::CfdState::IncomingOrderRequest { .. } = state { - vec![CfdAction::Accept, CfdAction::Reject] - } else { - vec![] +fn actions_for_state(state: model::cfd::CfdState, role: Role) -> Vec { + match (state, role) { + (model::cfd::CfdState::IncomingOrderRequest { .. }, Role::Maker) => { + vec![CfdAction::Accept, CfdAction::Reject] + } + (model::cfd::CfdState::Open { .. }, Role::Taker) => { + vec![CfdAction::Commit, CfdAction::Settle] + } + (model::cfd::CfdState::Open { .. }, Role::Maker) => vec![CfdAction::Commit], + _ => vec![], } } diff --git a/frontend/src/components/Types.tsx b/frontend/src/components/Types.tsx index 3cb19e6..48e3324 100644 --- a/frontend/src/components/Types.tsx +++ b/frontend/src/components/Types.tsx @@ -69,6 +69,8 @@ export class State { return "Pending Open"; case StateKey.OPEN: return "Open"; + case StateKey.PENDING_COMMIT: + return "Pending Commit"; case StateKey.OPEN_COMMITTED: return "Open (commit-tx published)"; case StateKey.MUST_REFUND: @@ -94,6 +96,7 @@ export class State { case StateKey.REJECTED: return red; + case StateKey.PENDING_COMMIT: case StateKey.OPEN_COMMITTED: case StateKey.MUST_REFUND: return orange; @@ -120,6 +123,7 @@ export class State { case StateKey.PENDING_OPEN: case StateKey.OPEN: + case StateKey.PENDING_COMMIT: case StateKey.OPEN_COMMITTED: case StateKey.MUST_REFUND: return StateGroupKey.OPEN; @@ -135,6 +139,8 @@ export class State { export enum Action { ACCEPT = "accept", REJECT = "reject", + COMMIT = "commit", + SETTLE = "settle", } const enum StateKey { @@ -145,6 +151,7 @@ const enum StateKey { CONTRACT_SETUP = "ContractSetup", PENDING_OPEN = "PendingOpen", OPEN = "Open", + PENDING_COMMIT = "PendingCommit", OPEN_COMMITTED = "OpenCommitted", MUST_REFUND = "MustRefund", REFUNDED = "Refunded", diff --git a/frontend/src/components/cfdtables/CfdTable.tsx b/frontend/src/components/cfdtables/CfdTable.tsx index 40226bc..8290e58 100644 --- a/frontend/src/components/cfdtables/CfdTable.tsx +++ b/frontend/src/components/cfdtables/CfdTable.tsx @@ -1,10 +1,12 @@ import { + CheckCircleIcon, CheckIcon, ChevronRightIcon, ChevronUpIcon, CloseIcon, TriangleDownIcon, TriangleUpIcon, + WarningIcon, } from "@chakra-ui/icons"; import { Badge, @@ -186,6 +188,10 @@ function iconForAction(action: Action): any { return ; case Action.REJECT: return ; + case Action.COMMIT: + return ; + case Action.SETTLE: + return ; } } @@ -195,6 +201,10 @@ function colorSchemaForAction(action: Action): string { return "green"; case Action.REJECT: return "red"; + case Action.COMMIT: + return "red"; + case Action.SETTLE: + return "green"; } }