Browse Source

Rename offer to order. Everywhere!!!

no-contract-setup-message
Daniel Karzel 4 years ago
parent
commit
a8a8076887
No known key found for this signature in database GPG Key ID: 30C3FC2E438ADB6E
  1. 2
      README.md
  2. 2
      cfd_protocol/src/lib.rs
  3. 16
      daemon/migrations/20210903050345_create_cfd_and_order_tables.sql
  4. 70
      daemon/sqlx-data.json
  5. 122
      daemon/src/db.rs
  6. 12
      daemon/src/maker.rs
  7. 86
      daemon/src/maker_cfd_actor.rs
  8. 42
      daemon/src/maker_inc_connections_actor.rs
  9. 36
      daemon/src/model/cfd.rs
  10. 50
      daemon/src/routes_maker.rs
  11. 22
      daemon/src/routes_taker.rs
  12. 8
      daemon/src/taker.rs
  13. 58
      daemon/src/taker_cfd_actor.rs
  14. 10
      daemon/src/taker_inc_message_actor.rs
  15. 36
      daemon/src/to_sse_event.rs
  16. 16
      daemon/src/wire.rs
  17. 6
      docs/asset/mvp_maker_taker_db.puml
  18. 34
      docs/asset/mvp_maker_taker_messaging.puml
  19. 42
      frontend/src/Maker.tsx
  20. 24
      frontend/src/Taker.tsx
  21. 30
      frontend/src/components/OrderTile.tsx
  22. 4
      frontend/src/components/Types.tsx

2
README.md

@ -20,7 +20,7 @@ Once the maker is started you can start the taker:
cargo run --bin taker cargo run --bin taker
``` ```
Upon startup the taker daemon will connect to the (hardcoded) maker and retrieve the current offer. Upon startup the taker daemon will connect to the (hardcoded) maker and retrieve the current order.
Note: The sqlite databases for maker and taker are currently created in the project root. Note: The sqlite databases for maker and taker are currently created in the project root.

2
cfd_protocol/src/lib.rs

@ -409,7 +409,7 @@ pub fn punish_transaction(
Ok(punish_tx) Ok(punish_tx)
} }
// NOTE: We have decided to not offer any verification utility because // NOTE: We have decided to not order any verification utility because
// the APIs would be incredibly thin // the APIs would be incredibly thin
#[derive(Clone)] #[derive(Clone)]

16
daemon/migrations/20210903050345_create_cfd_and_offer_tables.sql → daemon/migrations/20210903050345_create_cfd_and_order_tables.sql

@ -1,5 +1,5 @@
-- todo: Decimal is had to deserialize as number so we use text -- todo: Decimal is had to deserialize as number so we use text
create table if not exists offers create table if not exists orders
( (
id integer primary key autoincrement, id integer primary key autoincrement,
uuid text unique not null, uuid text unique not null,
@ -15,21 +15,21 @@ create table if not exists offers
origin text not null origin text not null
); );
create unique index if not exists offers_uuid create unique index if not exists orders_uuid
on offers (uuid); on orders (uuid);
create table if not exists cfds create table if not exists cfds
( (
id integer primary key autoincrement, id integer primary key autoincrement,
offer_id integer unique not null, order_id integer unique not null,
offer_uuid text unique not null, order_uuid text unique not null,
quantity_usd text not null, quantity_usd text not null,
foreign key (offer_id) references offers (id) foreign key (order_id) references orders (id)
); );
create unique index if not exists cfd_offer_uuid create unique index if not exists cfd_order_uuid
on cfds (offer_uuid); on cfds (order_uuid);
create table if not exists cfd_states create table if not exists cfd_states
( (

70
daemon/sqlx-data.json

@ -1,25 +1,35 @@
{ {
"db": "SQLite", "db": "SQLite",
"1bd5f2355d2e9351a443ec10b2533ca9326bb2a27b9f049d60759ac5a9eba758": { "3ec696a1077ae52f21230ac33b9083ae3420698ff4d6bb1bba2d71ad518daf85": {
"query": "\n select\n id\n from cfds\n where offer_uuid = ?;\n ", "query": "\n insert into orders (\n uuid,\n trading_pair,\n position,\n initial_price,\n min_quantity,\n max_quantity,\n leverage,\n liquidation_price,\n creation_timestamp,\n term,\n origin\n ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 11
},
"nullable": []
}
},
"50abbb297394739ec9d85917f8c32aa8bcfa0bfe140b24e9eeda4ce8d30d4f8d": {
"query": "\n select\n state\n from cfd_states\n where cfd_id = ?\n order by id desc\n limit 1;\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
"name": "id", "name": "state",
"ordinal": 0, "ordinal": 0,
"type_info": "Int64" "type_info": "Text"
} }
], ],
"parameters": { "parameters": {
"Right": 1 "Right": 1
}, },
"nullable": [ "nullable": [
true false
] ]
} }
}, },
"2bfe23378b852e9c98f1db3e7c7694e1a7037183eb6fa8d81916cdb46c760547": { "7941da81b43abcaa82d51bbfecaed51f13fc637dc611cebb87eea638ba055a42": {
"query": "\n select\n cfds.id as cfd_id,\n offers.uuid as offer_id,\n offers.initial_price as initial_price,\n offers.leverage as leverage,\n offers.trading_pair as trading_pair,\n offers.position as position,\n offers.origin as origin,\n offers.liquidation_price as liquidation_price,\n cfds.quantity_usd as quantity_usd,\n cfd_states.state as state\n from cfds as cfds\n inner join offers as offers on cfds.offer_id = offers.id\n inner join cfd_states as cfd_states on cfd_states.cfd_id = cfds.id\n where cfd_states.state in (\n select\n state\n from cfd_states\n where cfd_id = cfds.id\n order by id desc\n limit 1\n )\n ", "query": "\n select\n cfds.id as cfd_id,\n orders.uuid as order_id,\n orders.initial_price as initial_price,\n orders.leverage as leverage,\n orders.trading_pair as trading_pair,\n orders.position as position,\n orders.origin as origin,\n orders.liquidation_price as liquidation_price,\n cfds.quantity_usd as quantity_usd,\n cfd_states.state as state\n from cfds as cfds\n inner join orders as orders on cfds.order_id = orders.id\n inner join cfd_states as cfd_states on cfd_states.cfd_id = cfds.id\n where cfd_states.state in (\n select\n state\n from cfd_states\n where cfd_id = cfds.id\n order by id desc\n limit 1\n )\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -28,7 +38,7 @@
"type_info": "Int64" "type_info": "Int64"
}, },
{ {
"name": "offer_id", "name": "order_id",
"ordinal": 1, "ordinal": 1,
"type_info": "Text" "type_info": "Text"
}, },
@ -90,44 +100,24 @@
] ]
} }
}, },
"4a8db91aaef56a804d0151460297175355c9adee100b87fa9052245cdd18d7e9": { "8e7571250da58b12f5884f17656e5966957c7798ea029c701a4fc43fd613f015": {
"query": "\n insert into offers (\n uuid,\n trading_pair,\n position,\n initial_price,\n min_quantity,\n max_quantity,\n leverage,\n liquidation_price,\n creation_timestamp,\n term,\n origin\n ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n ", "query": "\n select\n id\n from cfds\n where order_uuid = ?;\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 11
},
"nullable": []
}
},
"50abbb297394739ec9d85917f8c32aa8bcfa0bfe140b24e9eeda4ce8d30d4f8d": {
"query": "\n select\n state\n from cfd_states\n where cfd_id = ?\n order by id desc\n limit 1;\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
"name": "state", "name": "id",
"ordinal": 0, "ordinal": 0,
"type_info": "Text" "type_info": "Int64"
} }
], ],
"parameters": { "parameters": {
"Right": 1 "Right": 1
}, },
"nullable": [ "nullable": [
false true
] ]
} }
}, },
"79162c94809f9fac4850236d06a76206d0914285d462c04e30ad7af222092675": {
"query": "\n insert into cfds (\n offer_id,\n offer_uuid,\n quantity_usd\n ) values (?, ?, ?);\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
}
},
"a464a1feb12abadff8bfd5b2b3b7362f3846869c0702944b21737eff8f420be5": { "a464a1feb12abadff8bfd5b2b3b7362f3846869c0702944b21737eff8f420be5": {
"query": "\n insert into cfd_states (\n cfd_id,\n state\n ) values (?, ?);\n ", "query": "\n insert into cfd_states (\n cfd_id,\n state\n ) values (?, ?);\n ",
"describe": { "describe": {
@ -138,8 +128,8 @@
"nullable": [] "nullable": []
} }
}, },
"d380cac37744c2169d4e0a8f13d223cbd37ca5034e461a583b18cf7566a9c5c6": { "f3dce76f316212c91cb3402b0cef00f1c9adbef8519c54e9bdbd373aab946209": {
"query": "\n select * from offers where uuid = ?;\n ", "query": "\n select * from orders where uuid = ?;\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -231,5 +221,15 @@
}, },
"nullable": [] "nullable": []
} }
},
"fad32c267100e26f4fba80e4feb5ff45ee29c3a67bd378f6627b1f13ee45c573": {
"query": "\n insert into cfds (\n order_id,\n order_uuid,\n quantity_usd\n ) values (?, ?, ?);\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
}
} }
} }

122
daemon/src/db.rs

@ -1,4 +1,4 @@
use crate::model::cfd::{Cfd, CfdOffer, CfdOfferId, CfdState}; use crate::model::cfd::{Cfd, CfdState, Order, OrderId};
use crate::model::{Leverage, Position}; use crate::model::{Leverage, Position};
use anyhow::Context; use anyhow::Context;
use rocket_db_pools::sqlx; use rocket_db_pools::sqlx;
@ -19,26 +19,26 @@ pub async fn run_migrations(pool: &SqlitePool) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
pub async fn insert_cfd_offer( pub async fn insert_order(
cfd_offer: &CfdOffer, order: &Order,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
origin: Origin, origin: Origin,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let uuid = serde_json::to_string(&cfd_offer.id).unwrap(); let uuid = serde_json::to_string(&order.id).unwrap();
let trading_pair = serde_json::to_string(&cfd_offer.trading_pair).unwrap(); let trading_pair = serde_json::to_string(&order.trading_pair).unwrap();
let position = serde_json::to_string(&cfd_offer.position).unwrap(); let position = serde_json::to_string(&order.position).unwrap();
let initial_price = serde_json::to_string(&cfd_offer.price).unwrap(); let initial_price = serde_json::to_string(&order.price).unwrap();
let min_quantity = serde_json::to_string(&cfd_offer.min_quantity).unwrap(); let min_quantity = serde_json::to_string(&order.min_quantity).unwrap();
let max_quantity = serde_json::to_string(&cfd_offer.max_quantity).unwrap(); let max_quantity = serde_json::to_string(&order.max_quantity).unwrap();
let leverage = cfd_offer.leverage.0; let leverage = order.leverage.0;
let liquidation_price = serde_json::to_string(&cfd_offer.liquidation_price).unwrap(); let liquidation_price = serde_json::to_string(&order.liquidation_price).unwrap();
let creation_timestamp = serde_json::to_string(&cfd_offer.creation_timestamp).unwrap(); let creation_timestamp = serde_json::to_string(&order.creation_timestamp).unwrap();
let term = serde_json::to_string(&cfd_offer.term).unwrap(); let term = serde_json::to_string(&order.term).unwrap();
let origin = serde_json::to_string(&origin).unwrap(); let origin = serde_json::to_string(&origin).unwrap();
sqlx::query!( sqlx::query!(
r#" r#"
insert into offers ( insert into orders (
uuid, uuid,
trading_pair, trading_pair,
position, position,
@ -70,15 +70,15 @@ pub async fn insert_cfd_offer(
Ok(()) Ok(())
} }
pub async fn load_offer_by_id( pub async fn load_order_by_id(
id: CfdOfferId, id: OrderId,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
) -> anyhow::Result<CfdOffer> { ) -> anyhow::Result<Order> {
let uuid = serde_json::to_string(&id).unwrap(); let uuid = serde_json::to_string(&id).unwrap();
let row = sqlx::query!( let row = sqlx::query!(
r#" r#"
select * from offers where uuid = ?; select * from orders where uuid = ?;
"#, "#,
uuid uuid
) )
@ -96,7 +96,7 @@ pub async fn load_offer_by_id(
let creation_timestamp = serde_json::from_str(row.creation_timestamp.as_str()).unwrap(); let creation_timestamp = serde_json::from_str(row.creation_timestamp.as_str()).unwrap();
let term = serde_json::from_str(row.term.as_str()).unwrap(); let term = serde_json::from_str(row.term.as_str()).unwrap();
Ok(CfdOffer { Ok(Order {
id: uuid, id: uuid,
trading_pair, trading_pair,
position, position,
@ -113,17 +113,17 @@ pub async fn load_offer_by_id(
pub async fn insert_cfd(cfd: Cfd, conn: &mut PoolConnection<Sqlite>) -> anyhow::Result<()> { pub async fn insert_cfd(cfd: Cfd, conn: &mut PoolConnection<Sqlite>) -> anyhow::Result<()> {
let mut tx = conn.begin().await?; let mut tx = conn.begin().await?;
let offer_uuid = serde_json::to_string(&cfd.offer_id)?; let order_uuid = serde_json::to_string(&cfd.order_id)?;
let offer_row = sqlx::query!( let order_row = sqlx::query!(
r#" r#"
select * from offers where uuid = ?; select * from orders where uuid = ?;
"#, "#,
offer_uuid order_uuid
) )
.fetch_one(&mut tx) .fetch_one(&mut tx)
.await?; .await?;
let offer_id = offer_row.id; let order_id = order_row.id;
let quantity_usd = serde_json::to_string(&cfd.quantity_usd)?; let quantity_usd = serde_json::to_string(&cfd.quantity_usd)?;
let cfd_state = serde_json::to_string(&cfd.state)?; let cfd_state = serde_json::to_string(&cfd.state)?;
@ -134,13 +134,13 @@ pub async fn insert_cfd(cfd: Cfd, conn: &mut PoolConnection<Sqlite>) -> anyhow::
let cfd_id = sqlx::query!( let cfd_id = sqlx::query!(
r#" r#"
insert into cfds ( insert into cfds (
offer_id, order_id,
offer_uuid, order_uuid,
quantity_usd quantity_usd
) values (?, ?, ?); ) values (?, ?, ?);
"#, "#,
offer_id, order_id,
offer_uuid, order_uuid,
quantity_usd, quantity_usd,
) )
.execute(&mut tx) .execute(&mut tx)
@ -166,12 +166,12 @@ pub async fn insert_cfd(cfd: Cfd, conn: &mut PoolConnection<Sqlite>) -> anyhow::
} }
#[allow(dead_code)] #[allow(dead_code)]
pub async fn insert_new_cfd_state_by_offer_id( pub async fn insert_new_cfd_state_by_order_id(
offer_id: CfdOfferId, order_id: OrderId,
new_state: CfdState, new_state: CfdState,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let cfd_id = load_cfd_id_by_offer_uuid(offer_id, conn).await?; let cfd_id = load_cfd_id_by_order_uuid(order_id, conn).await?;
let latest_cfd_state_in_db = load_latest_cfd_state(cfd_id, conn) let latest_cfd_state_in_db = load_latest_cfd_state(cfd_id, conn)
.await .await
.context("loading latest state failed")?; .context("loading latest state failed")?;
@ -179,7 +179,7 @@ pub async fn insert_new_cfd_state_by_offer_id(
// make sure that the new state is different than the current one to avoid that we save the same // make sure that the new state is different than the current one to avoid that we save the same
// state twice // state twice
if mem::discriminant(&latest_cfd_state_in_db) == mem::discriminant(&new_state) { if mem::discriminant(&latest_cfd_state_in_db) == mem::discriminant(&new_state) {
anyhow::bail!("Cannot insert new state {} for cfd with order_id {} because it currently already is in state {}", new_state, offer_id, latest_cfd_state_in_db); anyhow::bail!("Cannot insert new state {} for cfd with order_id {} because it currently already is in state {}", new_state, order_id, latest_cfd_state_in_db);
} }
let cfd_state = serde_json::to_string(&new_state)?; let cfd_state = serde_json::to_string(&new_state)?;
@ -201,20 +201,20 @@ pub async fn insert_new_cfd_state_by_offer_id(
} }
#[allow(dead_code)] #[allow(dead_code)]
async fn load_cfd_id_by_offer_uuid( async fn load_cfd_id_by_order_uuid(
offer_uuid: CfdOfferId, order_uuid: OrderId,
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
) -> anyhow::Result<i64> { ) -> anyhow::Result<i64> {
let offer_uuid = serde_json::to_string(&offer_uuid)?; let order_uuid = serde_json::to_string(&order_uuid)?;
let cfd_id = sqlx::query!( let cfd_id = sqlx::query!(
r#" r#"
select select
id id
from cfds from cfds
where offer_uuid = ?; where order_uuid = ?;
"#, "#,
offer_uuid order_uuid
) )
.fetch_one(conn) .fetch_one(conn)
.await?; .await?;
@ -257,17 +257,17 @@ pub async fn load_all_cfds(conn: &mut PoolConnection<Sqlite>) -> anyhow::Result<
r#" r#"
select select
cfds.id as cfd_id, cfds.id as cfd_id,
offers.uuid as offer_id, orders.uuid as order_id,
offers.initial_price as initial_price, orders.initial_price as initial_price,
offers.leverage as leverage, orders.leverage as leverage,
offers.trading_pair as trading_pair, orders.trading_pair as trading_pair,
offers.position as position, orders.position as position,
offers.origin as origin, orders.origin as origin,
offers.liquidation_price as liquidation_price, orders.liquidation_price as liquidation_price,
cfds.quantity_usd as quantity_usd, cfds.quantity_usd as quantity_usd,
cfd_states.state as state cfd_states.state as state
from cfds as cfds from cfds as cfds
inner join offers as offers on cfds.offer_id = offers.id inner join orders as orders on cfds.order_id = orders.id
inner join cfd_states as cfd_states on cfd_states.cfd_id = cfds.id inner join cfd_states as cfd_states on cfd_states.cfd_id = cfds.id
where cfd_states.state in ( where cfd_states.state in (
select select
@ -285,7 +285,7 @@ pub async fn load_all_cfds(conn: &mut PoolConnection<Sqlite>) -> anyhow::Result<
let cfds = rows let cfds = rows
.iter() .iter()
.map(|row| { .map(|row| {
let offer_id = serde_json::from_str(row.offer_id.as_str()).unwrap(); let order_id = serde_json::from_str(row.order_id.as_str()).unwrap();
let initial_price = serde_json::from_str(row.initial_price.as_str()).unwrap(); let initial_price = serde_json::from_str(row.initial_price.as_str()).unwrap();
let leverage = Leverage(row.leverage.try_into().unwrap()); let leverage = Leverage(row.leverage.try_into().unwrap());
let trading_pair = serde_json::from_str(row.trading_pair.as_str()).unwrap(); let trading_pair = serde_json::from_str(row.trading_pair.as_str()).unwrap();
@ -302,7 +302,7 @@ pub async fn load_all_cfds(conn: &mut PoolConnection<Sqlite>) -> anyhow::Result<
}; };
Cfd { Cfd {
offer_id, order_id,
initial_price, initial_price,
leverage, leverage,
trading_pair, trading_pair,
@ -326,25 +326,25 @@ mod tests {
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tempfile::tempdir; use tempfile::tempdir;
use crate::db::insert_cfd_offer; use crate::db::insert_order;
use crate::model::cfd::{Cfd, CfdOffer, CfdState, CfdStateCommon}; use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, Order};
use crate::model::Usd; use crate::model::Usd;
use super::*; use super::*;
#[tokio::test] #[tokio::test]
async fn test_insert_and_load_offer() { async fn test_insert_and_load_order() {
let pool = setup_test_db().await; let pool = setup_test_db().await;
let mut conn = pool.acquire().await.unwrap(); let mut conn = pool.acquire().await.unwrap();
let cfd_offer = CfdOffer::from_default_with_price(Usd(dec!(10000))).unwrap(); let order = Order::from_default_with_price(Usd(dec!(10000))).unwrap();
insert_cfd_offer(&cfd_offer, &mut conn, Origin::Theirs) insert_order(&order, &mut conn, Origin::Theirs)
.await .await
.unwrap(); .unwrap();
let cfd_offer_loaded = load_offer_by_id(cfd_offer.id, &mut conn).await.unwrap(); let order_loaded = load_order_by_id(order.id, &mut conn).await.unwrap();
assert_eq!(cfd_offer, cfd_offer_loaded); assert_eq!(order, order_loaded);
} }
#[tokio::test] #[tokio::test]
@ -352,9 +352,9 @@ mod tests {
let pool = setup_test_db().await; let pool = setup_test_db().await;
let mut conn = pool.acquire().await.unwrap(); let mut conn = pool.acquire().await.unwrap();
let cfd_offer = CfdOffer::from_default_with_price(Usd(dec!(10000))).unwrap(); let order = Order::from_default_with_price(Usd(dec!(10000))).unwrap();
let cfd = Cfd::new( let cfd = Cfd::new(
cfd_offer.clone(), order.clone(),
Usd(dec!(1000)), Usd(dec!(1000)),
CfdState::PendingTakeRequest { CfdState::PendingTakeRequest {
common: CfdStateCommon { common: CfdStateCommon {
@ -365,7 +365,7 @@ mod tests {
); );
// the order ahs to exist in the db in order to be able to insert the cfd // the order ahs to exist in the db in order to be able to insert the cfd
insert_cfd_offer(&cfd_offer, &mut conn, Origin::Theirs) insert_order(&order, &mut conn, Origin::Theirs)
.await .await
.unwrap(); .unwrap();
insert_cfd(cfd.clone(), &mut conn).await.unwrap(); insert_cfd(cfd.clone(), &mut conn).await.unwrap();
@ -380,9 +380,9 @@ mod tests {
let pool = setup_test_db().await; let pool = setup_test_db().await;
let mut conn = pool.acquire().await.unwrap(); let mut conn = pool.acquire().await.unwrap();
let cfd_offer = CfdOffer::from_default_with_price(Usd(dec!(10000))).unwrap(); let order = Order::from_default_with_price(Usd(dec!(10000))).unwrap();
let mut cfd = Cfd::new( let mut cfd = Cfd::new(
cfd_offer.clone(), order.clone(),
Usd(dec!(1000)), Usd(dec!(1000)),
CfdState::PendingTakeRequest { CfdState::PendingTakeRequest {
common: CfdStateCommon { common: CfdStateCommon {
@ -393,7 +393,7 @@ mod tests {
); );
// the order ahs to exist in the db in order to be able to insert the cfd // the order ahs to exist in the db in order to be able to insert the cfd
insert_cfd_offer(&cfd_offer, &mut conn, Origin::Theirs) insert_order(&order, &mut conn, Origin::Theirs)
.await .await
.unwrap(); .unwrap();
insert_cfd(cfd.clone(), &mut conn).await.unwrap(); insert_cfd(cfd.clone(), &mut conn).await.unwrap();
@ -403,7 +403,7 @@ mod tests {
transition_timestamp: SystemTime::now(), transition_timestamp: SystemTime::now(),
}, },
}; };
insert_new_cfd_state_by_offer_id(cfd.offer_id, cfd.state, &mut conn) insert_new_cfd_state_by_order_id(cfd.order_id, cfd.state, &mut conn)
.await .await
.unwrap(); .unwrap();

12
daemon/src/maker.rs

@ -2,7 +2,7 @@ use anyhow::Result;
use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1}; use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1};
use bdk::bitcoin::{self, Amount}; use bdk::bitcoin::{self, Amount};
use bdk::blockchain::{ElectrumBlockchain, NoopProgress}; use bdk::blockchain::{ElectrumBlockchain, NoopProgress};
use model::cfd::{Cfd, CfdOffer}; use model::cfd::{Cfd, Order};
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
use rocket::figment::util::map; use rocket::figment::util::map;
use rocket::figment::value::{Map, Value}; use rocket::figment::value::{Map, Value};
@ -45,7 +45,7 @@ async fn main() -> Result<()> {
let oracle = schnorrsig::KeyPair::new(SECP256K1, &mut rand::thread_rng()); // TODO: Fetch oracle public key from oracle. let oracle = schnorrsig::KeyPair::new(SECP256K1, &mut rand::thread_rng()); // TODO: Fetch oracle public key from oracle.
let (cfd_feed_sender, cfd_feed_receiver) = watch::channel::<Vec<Cfd>>(vec![]); let (cfd_feed_sender, cfd_feed_receiver) = watch::channel::<Vec<Cfd>>(vec![]);
let (offer_feed_sender, offer_feed_receiver) = watch::channel::<Option<CfdOffer>>(None); let (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(None);
let (_balance_feed_sender, balance_feed_receiver) = watch::channel::<Amount>(Amount::ZERO); let (_balance_feed_sender, balance_feed_receiver) = watch::channel::<Amount>(Amount::ZERO);
let db: Map<_, Value> = map! { let db: Map<_, Value> = map! {
@ -63,7 +63,7 @@ async fn main() -> Result<()> {
rocket::custom(figment) rocket::custom(figment)
.manage(cfd_feed_receiver) .manage(cfd_feed_receiver)
.manage(offer_feed_receiver) .manage(order_feed_receiver)
.manage(balance_feed_receiver) .manage(balance_feed_receiver)
.attach(Db::init()) .attach(Db::init())
.attach(AdHoc::try_on_ignite( .attach(AdHoc::try_on_ignite(
@ -95,7 +95,7 @@ async fn main() -> Result<()> {
schnorrsig::PublicKey::from_keypair(SECP256K1, &oracle), schnorrsig::PublicKey::from_keypair(SECP256K1, &oracle),
connections_actor_inbox_sender, connections_actor_inbox_sender,
cfd_feed_sender, cfd_feed_sender,
offer_feed_sender, order_feed_sender,
); );
let connections_actor = maker_inc_connections_actor::new( let connections_actor = maker_inc_connections_actor::new(
listener, listener,
@ -113,8 +113,8 @@ async fn main() -> Result<()> {
"/", "/",
rocket::routes![ rocket::routes![
routes_maker::maker_feed, routes_maker::maker_feed,
routes_maker::post_sell_offer, routes_maker::post_sell_order,
// routes_maker::post_confirm_offer, // routes_maker::post_confirm_order,
routes_maker::get_health_check routes_maker::get_health_check
], ],
) )

86
daemon/src/maker_cfd_actor.rs

@ -1,8 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::time::SystemTime; use std::time::SystemTime;
use crate::db::{insert_cfd, insert_cfd_offer, load_all_cfds, load_offer_by_id, Origin}; use crate::db::{insert_cfd, insert_order, load_all_cfds, load_order_by_id, Origin};
use crate::model::cfd::{Cfd, CfdOffer, CfdOfferId, CfdState, CfdStateCommon, FinalizedCfd}; use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId};
use crate::model::{TakerId, Usd}; use crate::model::{TakerId, Usd};
use crate::wire::{Msg0, Msg1, SetupMsg}; use crate::wire::{Msg0, Msg1, SetupMsg};
use crate::{maker_cfd_actor, maker_inc_connections_actor}; use crate::{maker_cfd_actor, maker_inc_connections_actor};
@ -19,15 +19,15 @@ use tokio::sync::{mpsc, watch};
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[derive(Debug)] #[derive(Debug)]
pub enum Command { pub enum Command {
TakeOffer { TakeOrder {
taker_id: TakerId, taker_id: TakerId,
offer_id: CfdOfferId, order_id: OrderId,
quantity: Usd, quantity: Usd,
}, },
NewOffer(CfdOffer), NewOrder(Order),
StartContractSetup { StartContractSetup {
taker_id: TakerId, taker_id: TakerId,
offer_id: CfdOfferId, order_id: OrderId,
}, },
NewTakerOnline { NewTakerOnline {
id: TakerId, id: TakerId,
@ -42,7 +42,7 @@ pub fn new<B, D>(
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
takers: mpsc::UnboundedSender<maker_inc_connections_actor::Command>, takers: mpsc::UnboundedSender<maker_inc_connections_actor::Command>,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>,
offer_feed_sender: watch::Sender<Option<CfdOffer>>, order_feed_sender: watch::Sender<Option<Order>>,
) -> ( ) -> (
impl Future<Output = ()>, impl Future<Output = ()>,
mpsc::UnboundedSender<maker_cfd_actor::Command>, mpsc::UnboundedSender<maker_cfd_actor::Command>,
@ -53,7 +53,7 @@ where
let (sender, mut receiver) = mpsc::unbounded_channel(); let (sender, mut receiver) = mpsc::unbounded_channel();
let mut current_contract_setup = None; let mut current_contract_setup = None;
let mut current_offer_id = None; let mut current_order_id = None;
let actor = { let actor = {
let sender = sender.clone(); let sender = sender.clone();
@ -67,27 +67,27 @@ where
while let Some(message) = receiver.recv().await { while let Some(message) = receiver.recv().await {
match message { match message {
maker_cfd_actor::Command::TakeOffer { maker_cfd_actor::Command::TakeOrder {
taker_id, taker_id,
offer_id, order_id,
quantity, quantity,
} => { } => {
println!( println!(
"Taker {} wants to take {} of offer {}", "Taker {} wants to take {} of order {}",
taker_id, quantity, offer_id taker_id, quantity, order_id
); );
let mut conn = db.acquire().await.unwrap(); let mut conn = db.acquire().await.unwrap();
// 1. Validate if offer is still valid // 1. Validate if order is still valid
let current_offer = match current_offer_id { let current_order = match current_order_id {
Some(current_offer_id) if current_offer_id == offer_id => { Some(current_order_id) if current_order_id == order_id => {
load_offer_by_id(current_offer_id, &mut conn).await.unwrap() load_order_by_id(current_order_id, &mut conn).await.unwrap()
} }
_ => { _ => {
takers takers
.send(maker_inc_connections_actor::Command::NotifyInvalidOfferId { .send(maker_inc_connections_actor::Command::NotifyInvalidOrderId {
id: offer_id, id: order_id,
taker_id, taker_id,
}) })
.unwrap(); .unwrap();
@ -98,20 +98,20 @@ where
// 2. Insert CFD in DB // 2. Insert CFD in DB
// TODO: Don't auto-accept, present to user in UI instead // TODO: Don't auto-accept, present to user in UI instead
let cfd = Cfd::new( let cfd = Cfd::new(
current_offer.clone(), current_order.clone(),
quantity, quantity,
CfdState::Accepted { CfdState::Accepted {
common: CfdStateCommon { common: CfdStateCommon {
transition_timestamp: SystemTime::now(), transition_timestamp: SystemTime::now(),
}, },
}, },
current_offer.position, current_order.position,
); );
insert_cfd(cfd, &mut conn).await.unwrap(); insert_cfd(cfd, &mut conn).await.unwrap();
takers takers
.send(maker_inc_connections_actor::Command::NotifyOfferAccepted { .send(maker_inc_connections_actor::Command::NotifyOrderAccepted {
id: offer_id, id: order_id,
taker_id, taker_id,
}) })
.unwrap(); .unwrap();
@ -119,60 +119,56 @@ where
.send(load_all_cfds(&mut conn).await.unwrap()) .send(load_all_cfds(&mut conn).await.unwrap())
.unwrap(); .unwrap();
// 3. Remove current offer // 3. Remove current order
current_offer_id = None; current_order_id = None;
takers takers
.send(maker_inc_connections_actor::Command::BroadcastCurrentOffer( .send(maker_inc_connections_actor::Command::BroadcastOrder(None))
None,
))
.unwrap(); .unwrap();
offer_feed_sender.send(None).unwrap(); order_feed_sender.send(None).unwrap();
} }
maker_cfd_actor::Command::NewOffer(offer) => { maker_cfd_actor::Command::NewOrder(order) => {
// 1. Save to DB // 1. Save to DB
let mut conn = db.acquire().await.unwrap(); let mut conn = db.acquire().await.unwrap();
insert_cfd_offer(&offer, &mut conn, Origin::Ours) insert_order(&order, &mut conn, Origin::Ours).await.unwrap();
.await
.unwrap();
// 2. Update actor state to current offer // 2. Update actor state to current order
current_offer_id.replace(offer.id); current_order_id.replace(order.id);
// 3. Notify UI via feed // 3. Notify UI via feed
offer_feed_sender.send(Some(offer.clone())).unwrap(); order_feed_sender.send(Some(order.clone())).unwrap();
// 4. Inform connected takers // 4. Inform connected takers
takers takers
.send(maker_inc_connections_actor::Command::BroadcastCurrentOffer( .send(maker_inc_connections_actor::Command::BroadcastOrder(Some(
Some(offer), order,
)) )))
.unwrap(); .unwrap();
} }
maker_cfd_actor::Command::NewTakerOnline { id: taker_id } => { maker_cfd_actor::Command::NewTakerOnline { id: taker_id } => {
let mut conn = db.acquire().await.unwrap(); let mut conn = db.acquire().await.unwrap();
let current_offer = match current_offer_id { let current_order = match current_order_id {
Some(current_offer_id) => { Some(current_order_id) => {
Some(load_offer_by_id(current_offer_id, &mut conn).await.unwrap()) Some(load_order_by_id(current_order_id, &mut conn).await.unwrap())
} }
None => None, None => None,
}; };
takers takers
.send(maker_inc_connections_actor::Command::SendCurrentOffer { .send(maker_inc_connections_actor::Command::SendOrder {
offer: current_offer, order: current_order,
taker_id, taker_id,
}) })
.unwrap(); .unwrap();
} }
maker_cfd_actor::Command::StartContractSetup { maker_cfd_actor::Command::StartContractSetup {
taker_id, taker_id,
offer_id: _offer_id, order_id: _order_id,
} => { } => {
// Kick-off the CFD protocol // Kick-off the CFD protocol
let (sk, pk) = crate::keypair::new(&mut rand::thread_rng()); let (sk, pk) = crate::keypair::new(&mut rand::thread_rng());
// TODO: Load correct quantity from DB with offer_id // TODO: Load correct quantity from DB with order_id
let maker_params = wallet let maker_params = wallet
.build_party_params(bitcoin::Amount::ZERO, pk) .build_party_params(bitcoin::Amount::ZERO, pk)
.unwrap(); .unwrap();

42
daemon/src/maker_inc_connections_actor.rs

@ -1,4 +1,4 @@
use crate::model::cfd::{CfdOffer, CfdOfferId}; use crate::model::cfd::{Order, OrderId};
use crate::model::TakerId; use crate::model::TakerId;
use crate::wire::SetupMsg; use crate::wire::SetupMsg;
use crate::{maker_cfd_actor, maker_inc_connections_actor, send_wire_message_actor, wire}; use crate::{maker_cfd_actor, maker_inc_connections_actor, send_wire_message_actor, wire};
@ -12,17 +12,17 @@ use tokio_util::codec::{FramedRead, LengthDelimitedCodec};
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[derive(Debug)] #[derive(Debug)]
pub enum Command { pub enum Command {
BroadcastCurrentOffer(Option<CfdOffer>), BroadcastOrder(Option<Order>),
SendCurrentOffer { SendOrder {
offer: Option<CfdOffer>, order: Option<Order>,
taker_id: TakerId, taker_id: TakerId,
}, },
NotifyInvalidOfferId { NotifyInvalidOrderId {
id: CfdOfferId, id: OrderId,
taker_id: TakerId, taker_id: TakerId,
}, },
NotifyOfferAccepted { NotifyOrderAccepted {
id: CfdOfferId, id: OrderId,
taker_id: TakerId, taker_id: TakerId,
}, },
OutProtocolMsg { OutProtocolMsg {
@ -59,22 +59,22 @@ pub fn new(
}, },
Some(message) = our_inbox.recv() => { Some(message) = our_inbox.recv() => {
match message { match message {
maker_inc_connections_actor::Command::NotifyInvalidOfferId { id, taker_id } => { maker_inc_connections_actor::Command::NotifyInvalidOrderId { id, taker_id } => {
let conn = write_connections.get(&taker_id).expect("no connection to taker_id"); let conn = write_connections.get(&taker_id).expect("no connection to taker_id");
conn.send(wire::MakerToTaker::InvalidOfferId(id)).unwrap(); conn.send(wire::MakerToTaker::InvalidOrderId(id)).unwrap();
}, },
maker_inc_connections_actor::Command::BroadcastCurrentOffer(offer) => { maker_inc_connections_actor::Command::BroadcastOrder(order) => {
for conn in write_connections.values() { for conn in write_connections.values() {
conn.send(wire::MakerToTaker::CurrentOffer(offer.clone())).unwrap(); conn.send(wire::MakerToTaker::CurrentOrder(order.clone())).unwrap();
} }
}, },
maker_inc_connections_actor::Command::SendCurrentOffer {offer, taker_id} => { maker_inc_connections_actor::Command::SendOrder {order, taker_id} => {
let conn = write_connections.get(&taker_id).expect("no connection to taker_id"); let conn = write_connections.get(&taker_id).expect("no connection to taker_id");
conn.send(wire::MakerToTaker::CurrentOffer(offer)).unwrap(); conn.send(wire::MakerToTaker::CurrentOrder(order)).unwrap();
}, },
maker_inc_connections_actor::Command::NotifyOfferAccepted { id, taker_id } => { maker_inc_connections_actor::Command::NotifyOrderAccepted { id, taker_id } => {
let conn = write_connections.get(&taker_id).expect("no connection to taker_id"); let conn = write_connections.get(&taker_id).expect("no connection to taker_id");
conn.send(wire::MakerToTaker::ConfirmTakeOffer(id)).unwrap(); conn.send(wire::MakerToTaker::ConfirmTakeOrder(id)).unwrap();
}, },
maker_inc_connections_actor::Command::OutProtocolMsg { taker_id, msg } => { maker_inc_connections_actor::Command::OutProtocolMsg { taker_id, msg } => {
let conn = write_connections.get(&taker_id).expect("no connection to taker_id"); let conn = write_connections.get(&taker_id).expect("no connection to taker_id");
@ -100,15 +100,15 @@ fn in_taker_messages(
async move { async move {
while let Some(message) = messages.next().await { while let Some(message) = messages.next().await {
match message { match message {
Ok(wire::TakerToMaker::TakeOffer { offer_id, quantity }) => cfd_actor_inbox Ok(wire::TakerToMaker::TakeOrder { order_id, quantity }) => cfd_actor_inbox
.send(maker_cfd_actor::Command::TakeOffer { .send(maker_cfd_actor::Command::TakeOrder {
taker_id, taker_id,
offer_id, order_id,
quantity, quantity,
}) })
.unwrap(), .unwrap(),
Ok(wire::TakerToMaker::StartContractSetup(offer_id)) => cfd_actor_inbox Ok(wire::TakerToMaker::StartContractSetup(order_id)) => cfd_actor_inbox
.send(maker_cfd_actor::Command::StartContractSetup { taker_id, offer_id }) .send(maker_cfd_actor::Command::StartContractSetup { taker_id, order_id })
.unwrap(), .unwrap(),
Ok(wire::TakerToMaker::Protocol(msg)) => cfd_actor_inbox Ok(wire::TakerToMaker::Protocol(msg)) => cfd_actor_inbox
.send(maker_cfd_actor::Command::IncProtocolMsg(msg)) .send(maker_cfd_actor::Command::IncProtocolMsg(msg))

36
daemon/src/model/cfd.rs

@ -12,24 +12,24 @@ use std::time::{Duration, SystemTime};
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
pub struct CfdOfferId(Uuid); pub struct OrderId(Uuid);
impl Default for CfdOfferId { impl Default for OrderId {
fn default() -> Self { fn default() -> Self {
Self(Uuid::new_v4()) Self(Uuid::new_v4())
} }
} }
impl Display for CfdOfferId { impl Display for OrderId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f) self.0.fmt(f)
} }
} }
/// A concrete offer created by a maker for a taker /// A concrete order created by a maker for a taker
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CfdOffer { pub struct Order {
pub id: CfdOfferId, pub id: OrderId,
pub trading_pair: TradingPair, pub trading_pair: TradingPair,
pub position: Position, pub position: Position,
@ -52,15 +52,15 @@ pub struct CfdOffer {
} }
#[allow(dead_code)] // Only one binary and the tests use this. #[allow(dead_code)] // Only one binary and the tests use this.
impl CfdOffer { impl Order {
pub fn from_default_with_price(price: Usd) -> Result<Self> { pub fn from_default_with_price(price: Usd) -> Result<Self> {
let leverage = Leverage(5); let leverage = Leverage(5);
let maintenance_margin_rate = dec!(0.005); let maintenance_margin_rate = dec!(0.005);
let liquidation_price = let liquidation_price =
calculate_liquidation_price(&leverage, &price, &maintenance_margin_rate)?; calculate_liquidation_price(&leverage, &price, &maintenance_margin_rate)?;
Ok(CfdOffer { Ok(Order {
id: CfdOfferId::default(), id: OrderId::default(),
price, price,
min_quantity: Usd(dec!(1000)), min_quantity: Usd(dec!(1000)),
max_quantity: Usd(dec!(10000)), max_quantity: Usd(dec!(10000)),
@ -72,12 +72,12 @@ impl CfdOffer {
term: Duration::from_secs(60 * 60 * 8), // 8 hours term: Duration::from_secs(60 * 60 * 8), // 8 hours
}) })
} }
pub fn with_min_quantity(mut self, min_quantity: Usd) -> CfdOffer { pub fn with_min_quantity(mut self, min_quantity: Usd) -> Order {
self.min_quantity = min_quantity; self.min_quantity = min_quantity;
self self
} }
pub fn with_max_quantity(mut self, max_quantity: Usd) -> CfdOffer { pub fn with_max_quantity(mut self, max_quantity: Usd) -> Order {
self.max_quantity = max_quantity; self.max_quantity = max_quantity;
self self
} }
@ -240,7 +240,7 @@ impl Display for CfdState {
/// Represents a cfd (including state) /// Represents a cfd (including state)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Cfd { pub struct Cfd {
pub offer_id: CfdOfferId, pub order_id: OrderId,
pub initial_price: Usd, pub initial_price: Usd,
pub leverage: Leverage, pub leverage: Leverage,
@ -254,14 +254,14 @@ pub struct Cfd {
} }
impl Cfd { impl Cfd {
pub fn new(cfd_offer: CfdOffer, quantity: Usd, state: CfdState, position: Position) -> Self { pub fn new(cfd_order: Order, quantity: Usd, state: CfdState, position: Position) -> Self {
Cfd { Cfd {
offer_id: cfd_offer.id, order_id: cfd_order.id,
initial_price: cfd_offer.price, initial_price: cfd_order.price,
leverage: cfd_offer.leverage, leverage: cfd_order.leverage,
trading_pair: cfd_offer.trading_pair, trading_pair: cfd_order.trading_pair,
position, position,
liquidation_price: cfd_offer.liquidation_price, liquidation_price: cfd_order.liquidation_price,
quantity_usd: quantity, quantity_usd: quantity,
state, state,
} }

50
daemon/src/routes_maker.rs

@ -1,5 +1,5 @@
use crate::maker_cfd_actor; use crate::maker_cfd_actor;
use crate::model::cfd::{Cfd, CfdOffer}; use crate::model::cfd::{Cfd, Order};
use crate::model::Usd; use crate::model::Usd;
use crate::to_sse_event::ToSseEvent; use crate::to_sse_event::ToSseEvent;
use anyhow::Result; use anyhow::Result;
@ -15,19 +15,19 @@ use tokio::sync::{mpsc, watch};
#[rocket::get("/maker-feed")] #[rocket::get("/maker-feed")]
pub async fn maker_feed( pub async fn maker_feed(
rx_cfds: &State<watch::Receiver<Vec<Cfd>>>, rx_cfds: &State<watch::Receiver<Vec<Cfd>>>,
rx_offer: &State<watch::Receiver<Option<CfdOffer>>>, rx_order: &State<watch::Receiver<Option<Order>>>,
rx_balance: &State<watch::Receiver<Amount>>, rx_balance: &State<watch::Receiver<Amount>>,
) -> EventStream![] { ) -> EventStream![] {
let mut rx_cfds = rx_cfds.inner().clone(); let mut rx_cfds = rx_cfds.inner().clone();
let mut rx_offer = rx_offer.inner().clone(); let mut rx_order = rx_order.inner().clone();
let mut rx_balance = rx_balance.inner().clone(); let mut rx_balance = rx_balance.inner().clone();
EventStream! { EventStream! {
let balance = rx_balance.borrow().clone(); let balance = rx_balance.borrow().clone();
yield balance.to_sse_event(); yield balance.to_sse_event();
let offer = rx_offer.borrow().clone(); let order = rx_order.borrow().clone();
yield offer.to_sse_event(); yield order.to_sse_event();
let cfds = rx_cfds.borrow().clone(); let cfds = rx_cfds.borrow().clone();
yield cfds.to_sse_event(); yield cfds.to_sse_event();
@ -38,9 +38,9 @@ pub async fn maker_feed(
let balance = rx_balance.borrow().clone(); let balance = rx_balance.borrow().clone();
yield balance.to_sse_event(); yield balance.to_sse_event();
}, },
Ok(()) = rx_offer.changed() => { Ok(()) = rx_order.changed() => {
let offer = rx_offer.borrow().clone(); let order = rx_order.borrow().clone();
yield offer.to_sse_event(); yield order.to_sse_event();
} }
Ok(()) = rx_cfds.changed() => { Ok(()) = rx_cfds.changed() => {
let cfds = rx_cfds.borrow().clone(); let cfds = rx_cfds.borrow().clone();
@ -51,10 +51,10 @@ pub async fn maker_feed(
} }
} }
/// The maker POSTs this to create a new CfdOffer /// The maker POSTs this to create a new CfdOrder
// TODO: Use Rocket form? // TODO: Use Rocket form?
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct CfdNewOfferRequest { pub struct CfdNewOrderRequest {
pub price: Usd, pub price: Usd,
// TODO: [post-MVP] Representation of the contract size; at the moment the contract size is // TODO: [post-MVP] Representation of the contract size; at the moment the contract size is
// always 1 USD // always 1 USD
@ -62,18 +62,18 @@ pub struct CfdNewOfferRequest {
pub max_quantity: Usd, pub max_quantity: Usd,
} }
#[rocket::post("/offer/sell", data = "<offer>")] #[rocket::post("/order/sell", data = "<order>")]
pub async fn post_sell_offer( pub async fn post_sell_order(
offer: Json<CfdNewOfferRequest>, order: Json<CfdNewOrderRequest>,
cfd_actor_inbox: &State<mpsc::UnboundedSender<maker_cfd_actor::Command>>, cfd_actor_inbox: &State<mpsc::UnboundedSender<maker_cfd_actor::Command>>,
) -> Result<status::Accepted<()>, status::BadRequest<String>> { ) -> Result<status::Accepted<()>, status::BadRequest<String>> {
let offer = CfdOffer::from_default_with_price(offer.price) let order = Order::from_default_with_price(order.price)
.map_err(|e| status::BadRequest(Some(e.to_string())))? .map_err(|e| status::BadRequest(Some(e.to_string())))?
.with_min_quantity(offer.min_quantity) .with_min_quantity(order.min_quantity)
.with_max_quantity(offer.max_quantity); .with_max_quantity(order.max_quantity);
cfd_actor_inbox cfd_actor_inbox
.send(maker_cfd_actor::Command::NewOffer(offer)) .send(maker_cfd_actor::Command::NewOrder(order))
.expect("actor to always be available"); .expect("actor to always be available");
Ok(status::Accepted(None)) Ok(status::Accepted(None))
@ -81,20 +81,20 @@ pub async fn post_sell_offer(
// // TODO: Shall we use a simpler struct for verification? AFAICT quantity is not // // 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 // // needed, no need to send the whole CFD either as the other fields can be generated from the
// offer #[rocket::post("/offer/confirm", data = "<cfd_confirm_offer_request>")] // order #[rocket::post("/order/confirm", data = "<cfd_confirm_order_request>")]
// pub async fn post_confirm_offer( // pub async fn post_confirm_order(
// cfd_confirm_offer_request: Json<CfdTakeRequest>, // cfd_confirm_order_request: Json<CfdTakeRequest>,
// queue: &State<mpsc::Sender<CfdOffer>>, // queue: &State<mpsc::Sender<CfdOrder>>,
// mut conn: Connection<Db>, // mut conn: Connection<Db>,
// ) -> Result<status::Accepted<()>, status::BadRequest<String>> { // ) -> Result<status::Accepted<()>, status::BadRequest<String>> {
// dbg!(&cfd_confirm_offer_request); // dbg!(&cfd_confirm_order_request);
// let offer = db::load_offer_by_id_from_conn(cfd_confirm_offer_request.offer_id, &mut conn) // let order = db::load_order_by_id_from_conn(cfd_confirm_order_request.order_id, &mut conn)
// .await // .await
// .map_err(|e| status::BadRequest(Some(e.to_string())))?; // .map_err(|e| status::BadRequest(Some(e.to_string())))?;
// let _res = queue // let _res = queue
// .send(offer) // .send(order)
// .await // .await
// .map_err(|_| status::BadRequest(Some("internal server error".to_string())))?; // .map_err(|_| status::BadRequest(Some("internal server error".to_string())))?;
@ -105,4 +105,4 @@ pub async fn post_sell_offer(
pub fn get_health_check() {} pub fn get_health_check() {}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct RetrieveCurrentOffer; struct RetrieveCurrentOrder;

22
daemon/src/routes_taker.rs

@ -1,4 +1,4 @@
use crate::model::cfd::{calculate_buy_margin, Cfd, CfdOffer, CfdOfferId}; use crate::model::cfd::{calculate_buy_margin, Cfd, Order, OrderId};
use crate::model::{Leverage, Usd}; use crate::model::{Leverage, Usd};
use crate::taker_cfd_actor; use crate::taker_cfd_actor;
use crate::to_sse_event::ToSseEvent; use crate::to_sse_event::ToSseEvent;
@ -14,19 +14,19 @@ use tokio::sync::{mpsc, watch};
#[rocket::get("/feed")] #[rocket::get("/feed")]
pub async fn feed( pub async fn feed(
rx_cfds: &State<watch::Receiver<Vec<Cfd>>>, rx_cfds: &State<watch::Receiver<Vec<Cfd>>>,
rx_offer: &State<watch::Receiver<Option<CfdOffer>>>, rx_order: &State<watch::Receiver<Option<Order>>>,
rx_balance: &State<watch::Receiver<Amount>>, rx_balance: &State<watch::Receiver<Amount>>,
) -> EventStream![] { ) -> EventStream![] {
let mut rx_cfds = rx_cfds.inner().clone(); let mut rx_cfds = rx_cfds.inner().clone();
let mut rx_offer = rx_offer.inner().clone(); let mut rx_order = rx_order.inner().clone();
let mut rx_balance = rx_balance.inner().clone(); let mut rx_balance = rx_balance.inner().clone();
EventStream! { EventStream! {
let balance = rx_balance.borrow().clone(); let balance = rx_balance.borrow().clone();
yield balance.to_sse_event(); yield balance.to_sse_event();
let offer = rx_offer.borrow().clone(); let order = rx_order.borrow().clone();
yield offer.to_sse_event(); yield order.to_sse_event();
let cfds = rx_cfds.borrow().clone(); let cfds = rx_cfds.borrow().clone();
yield cfds.to_sse_event(); yield cfds.to_sse_event();
@ -37,9 +37,9 @@ pub async fn feed(
let balance = rx_balance.borrow().clone(); let balance = rx_balance.borrow().clone();
yield balance.to_sse_event(); yield balance.to_sse_event();
}, },
Ok(()) = rx_offer.changed() => { Ok(()) = rx_order.changed() => {
let offer = rx_offer.borrow().clone(); let order = rx_order.borrow().clone();
yield offer.to_sse_event(); yield order.to_sse_event();
} }
Ok(()) = rx_cfds.changed() => { Ok(()) = rx_cfds.changed() => {
let cfds = rx_cfds.borrow().clone(); let cfds = rx_cfds.borrow().clone();
@ -52,7 +52,7 @@ pub async fn feed(
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CfdTakeRequest { pub struct CfdTakeRequest {
pub offer_id: CfdOfferId, pub order_id: OrderId,
pub quantity: Usd, pub quantity: Usd,
} }
@ -62,8 +62,8 @@ pub async fn post_cfd(
cfd_actor_inbox: &State<mpsc::UnboundedSender<taker_cfd_actor::Command>>, cfd_actor_inbox: &State<mpsc::UnboundedSender<taker_cfd_actor::Command>>,
) { ) {
cfd_actor_inbox cfd_actor_inbox
.send(taker_cfd_actor::Command::TakeOffer { .send(taker_cfd_actor::Command::TakeOrder {
offer_id: cfd_take_request.offer_id, order_id: cfd_take_request.order_id,
quantity: cfd_take_request.quantity, quantity: cfd_take_request.quantity,
}) })
.expect("actor to never disappear"); .expect("actor to never disappear");

8
daemon/src/taker.rs

@ -2,7 +2,7 @@ use anyhow::Result;
use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1}; use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1};
use bdk::bitcoin::{self, Amount}; use bdk::bitcoin::{self, Amount};
use bdk::blockchain::{ElectrumBlockchain, NoopProgress}; use bdk::blockchain::{ElectrumBlockchain, NoopProgress};
use model::cfd::{Cfd, CfdOffer}; use model::cfd::{Cfd, Order};
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
use rocket::figment::util::map; use rocket::figment::util::map;
use rocket::figment::value::{Map, Value}; use rocket::figment::value::{Map, Value};
@ -45,7 +45,7 @@ async fn main() -> Result<()> {
let oracle = schnorrsig::KeyPair::new(SECP256K1, &mut rand::thread_rng()); // TODO: Fetch oracle public key from oracle. let oracle = schnorrsig::KeyPair::new(SECP256K1, &mut rand::thread_rng()); // TODO: Fetch oracle public key from oracle.
let (cfd_feed_sender, cfd_feed_receiver) = watch::channel::<Vec<Cfd>>(vec![]); let (cfd_feed_sender, cfd_feed_receiver) = watch::channel::<Vec<Cfd>>(vec![]);
let (offer_feed_sender, offer_feed_receiver) = watch::channel::<Option<CfdOffer>>(None); let (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(None);
let (_balance_feed_sender, balance_feed_receiver) = watch::channel::<Amount>(Amount::ZERO); let (_balance_feed_sender, balance_feed_receiver) = watch::channel::<Amount>(Amount::ZERO);
let socket = tokio::net::TcpSocket::new_v4().unwrap(); let socket = tokio::net::TcpSocket::new_v4().unwrap();
@ -64,7 +64,7 @@ async fn main() -> Result<()> {
rocket::custom(figment) rocket::custom(figment)
.manage(cfd_feed_receiver) .manage(cfd_feed_receiver)
.manage(offer_feed_receiver) .manage(order_feed_receiver)
.manage(balance_feed_receiver) .manage(balance_feed_receiver)
.attach(Db::init()) .attach(Db::init())
.attach(AdHoc::try_on_ignite( .attach(AdHoc::try_on_ignite(
@ -94,7 +94,7 @@ async fn main() -> Result<()> {
wallet, wallet,
schnorrsig::PublicKey::from_keypair(SECP256K1, &oracle), schnorrsig::PublicKey::from_keypair(SECP256K1, &oracle),
cfd_feed_sender, cfd_feed_sender,
offer_feed_sender, order_feed_sender,
out_maker_actor_inbox, out_maker_actor_inbox,
); );
let inc_maker_messages_actor = let inc_maker_messages_actor =

58
daemon/src/taker_cfd_actor.rs

@ -1,10 +1,8 @@
use crate::db::{ use crate::db::{
insert_cfd, insert_cfd_offer, insert_new_cfd_state_by_offer_id, load_all_cfds, insert_cfd, insert_new_cfd_state_by_order_id, insert_order, load_all_cfds, load_order_by_id,
load_offer_by_id, Origin, Origin,
};
use crate::model::cfd::{
AsBlocks, Cfd, CfdOffer, CfdOfferId, CfdState, CfdStateCommon, FinalizedCfd,
}; };
use crate::model::cfd::{AsBlocks, Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId};
use crate::model::Usd; use crate::model::Usd;
use crate::wire; use crate::wire;
use crate::wire::{AdaptorSignature, Msg0, Msg1, SetupMsg}; use crate::wire::{AdaptorSignature, Msg0, Msg1, SetupMsg};
@ -23,14 +21,14 @@ use std::collections::HashMap;
use std::time::SystemTime; use std::time::SystemTime;
use tokio::sync::{mpsc, watch}; use tokio::sync::{mpsc, watch};
/// A factor to be added to the CFD offer term for calculating the refund timelock. /// A factor to be added to the CFD order term for calculating the refund timelock.
/// ///
/// The refund timelock is important in case the oracle disappears or never publishes a signature. /// The refund timelock is important in case the oracle disappears or never publishes a signature.
/// Ideally, both users collaboratively settle in the refund scenario. This factor is important if /// Ideally, both users collaboratively settle in the refund scenario. This factor is important if
/// the users do not settle collaboratively. /// the users do not settle collaboratively.
/// `1.5` times the term as defined in CFD offer should be safe in the extreme case where a user /// `1.5` times the term as defined in CFD order should be safe in the extreme case where a user
/// publishes the commit transaction right after the contract was initialized. In this case, the /// publishes the commit transaction right after the contract was initialized. In this case, the
/// oracle still has `1.0 * cfdoffer.term` time to attest and no one can publish the refund /// oracle still has `1.0 * cfdorder.term` time to attest and no one can publish the refund
/// transaction. /// transaction.
/// The downside is that if the oracle disappears: the users would only notice at the end /// The downside is that if the oracle disappears: the users would only notice at the end
/// of the cfd term. In this case the users has to wait for another `1.5` times of the /// of the cfd term. In this case the users has to wait for another `1.5` times of the
@ -40,9 +38,9 @@ pub const REFUND_THRESHOLD: f32 = 1.5;
#[derive(Debug)] #[derive(Debug)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum Command { pub enum Command {
TakeOffer { offer_id: CfdOfferId, quantity: Usd }, TakeOrder { order_id: OrderId, quantity: Usd },
NewOffer(Option<CfdOffer>), NewOrder(Option<Order>),
OfferAccepted(CfdOfferId), OrderAccepted(OrderId),
IncProtocolMsg(SetupMsg), IncProtocolMsg(SetupMsg),
CfdSetupCompleted(FinalizedCfd), CfdSetupCompleted(FinalizedCfd),
} }
@ -52,7 +50,7 @@ pub fn new<B, D>(
wallet: bdk::Wallet<B, D>, wallet: bdk::Wallet<B, D>,
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>,
offer_feed_actor_inbox: watch::Sender<Option<CfdOffer>>, order_feed_actor_inbox: watch::Sender<Option<Order>>,
out_msg_maker_inbox: mpsc::UnboundedSender<wire::TakerToMaker>, out_msg_maker_inbox: mpsc::UnboundedSender<wire::TakerToMaker>,
) -> (impl Future<Output = ()>, mpsc::UnboundedSender<Command>) ) -> (impl Future<Output = ()>, mpsc::UnboundedSender<Command>)
where where
@ -73,22 +71,22 @@ where
while let Some(message) = receiver.recv().await { while let Some(message) = receiver.recv().await {
match message { match message {
Command::TakeOffer { offer_id, quantity } => { Command::TakeOrder { order_id, quantity } => {
let mut conn = db.acquire().await.unwrap(); let mut conn = db.acquire().await.unwrap();
let current_offer = load_offer_by_id(offer_id, &mut conn).await.unwrap(); let current_order = load_order_by_id(order_id, &mut conn).await.unwrap();
println!("Accepting current offer: {:?}", &current_offer); println!("Accepting current order: {:?}", &current_order);
let cfd = Cfd::new( let cfd = Cfd::new(
current_offer.clone(), current_order.clone(),
quantity, quantity,
CfdState::PendingTakeRequest { CfdState::PendingTakeRequest {
common: CfdStateCommon { common: CfdStateCommon {
transition_timestamp: SystemTime::now(), transition_timestamp: SystemTime::now(),
}, },
}, },
current_offer.position.counter_position(), current_order.position.counter_position(),
); );
insert_cfd(cfd, &mut conn).await.unwrap(); insert_cfd(cfd, &mut conn).await.unwrap();
@ -97,24 +95,24 @@ where
.send(load_all_cfds(&mut conn).await.unwrap()) .send(load_all_cfds(&mut conn).await.unwrap())
.unwrap(); .unwrap();
out_msg_maker_inbox out_msg_maker_inbox
.send(wire::TakerToMaker::TakeOffer { offer_id, quantity }) .send(wire::TakerToMaker::TakeOrder { order_id, quantity })
.unwrap(); .unwrap();
} }
Command::NewOffer(Some(offer)) => { Command::NewOrder(Some(order)) => {
let mut conn = db.acquire().await.unwrap(); let mut conn = db.acquire().await.unwrap();
insert_cfd_offer(&offer, &mut conn, Origin::Theirs) insert_order(&order, &mut conn, Origin::Theirs)
.await .await
.unwrap(); .unwrap();
offer_feed_actor_inbox.send(Some(offer)).unwrap(); order_feed_actor_inbox.send(Some(order)).unwrap();
} }
Command::NewOffer(None) => { Command::NewOrder(None) => {
offer_feed_actor_inbox.send(None).unwrap(); order_feed_actor_inbox.send(None).unwrap();
} }
Command::OfferAccepted(offer_id) => { Command::OrderAccepted(order_id) => {
let mut conn = db.acquire().await.unwrap(); let mut conn = db.acquire().await.unwrap();
insert_new_cfd_state_by_offer_id( insert_new_cfd_state_by_order_id(
offer_id, order_id,
CfdState::ContractSetup { CfdState::ContractSetup {
common: CfdStateCommon { common: CfdStateCommon {
transition_timestamp: SystemTime::now(), transition_timestamp: SystemTime::now(),
@ -135,7 +133,7 @@ where
.build_party_params(bitcoin::Amount::ZERO, pk) // TODO: Load correct quantity from DB .build_party_params(bitcoin::Amount::ZERO, pk) // TODO: Load correct quantity from DB
.unwrap(); .unwrap();
let cfd = load_offer_by_id(offer_id, &mut conn).await.unwrap(); let cfd = load_order_by_id(order_id, &mut conn).await.unwrap();
let (actor, inbox) = setup_contract( let (actor, inbox) = setup_contract(
{ {
@ -188,7 +186,7 @@ fn setup_contract(
taker: PartyParams, taker: PartyParams,
sk: SecretKey, sk: SecretKey,
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
offer: CfdOffer, order: Order,
) -> ( ) -> (
impl Future<Output = FinalizedCfd>, impl Future<Output = FinalizedCfd>,
mpsc::UnboundedSender<SetupMsg>, mpsc::UnboundedSender<SetupMsg>,
@ -212,7 +210,7 @@ fn setup_contract(
(maker.clone(), maker_punish), (maker.clone(), maker_punish),
(taker.clone(), taker_punish), (taker.clone(), taker_punish),
oracle_pk, oracle_pk,
offer.term.mul_f32(REFUND_THRESHOLD).as_blocks().ceil() as u32, order.term.mul_f32(REFUND_THRESHOLD).as_blocks().ceil() as u32,
vec![], vec![],
sk, sk,
) )

10
daemon/src/taker_inc_message_actor.rs

@ -18,18 +18,18 @@ pub fn new(
async move { async move {
while let Some(message) = messages.next().await { while let Some(message) = messages.next().await {
match message { match message {
Ok(wire::MakerToTaker::CurrentOffer(offer)) => { Ok(wire::MakerToTaker::CurrentOrder(order)) => {
cfd_actor cfd_actor
.send(taker_cfd_actor::Command::NewOffer(offer)) .send(taker_cfd_actor::Command::NewOrder(order))
.unwrap(); .unwrap();
} }
Ok(wire::MakerToTaker::ConfirmTakeOffer(offer_id)) => { Ok(wire::MakerToTaker::ConfirmTakeOrder(order_id)) => {
// TODO: This naming is not well aligned. // TODO: This naming is not well aligned.
cfd_actor cfd_actor
.send(taker_cfd_actor::Command::OfferAccepted(offer_id)) .send(taker_cfd_actor::Command::OrderAccepted(order_id))
.unwrap(); .unwrap();
} }
Ok(wire::MakerToTaker::InvalidOfferId(_)) => { Ok(wire::MakerToTaker::InvalidOrderId(_)) => {
todo!() todo!()
} }
Ok(wire::MakerToTaker::Protocol(msg)) => { Ok(wire::MakerToTaker::Protocol(msg)) => {

36
daemon/src/to_sse_event.rs

@ -1,5 +1,5 @@
use crate::model; use crate::model;
use crate::model::cfd::CfdOfferId; use crate::model::cfd::OrderId;
use crate::model::{Leverage, Position, TradingPair, Usd}; use crate::model::{Leverage, Position, TradingPair, Usd};
use bdk::bitcoin::Amount; use bdk::bitcoin::Amount;
use rocket::response::stream::Event; use rocket::response::stream::Event;
@ -8,7 +8,7 @@ use std::time::UNIX_EPOCH;
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct Cfd { pub struct Cfd {
pub offer_id: CfdOfferId, pub order_id: OrderId,
pub initial_price: Usd, pub initial_price: Usd,
pub leverage: Leverage, pub leverage: Leverage,
@ -30,8 +30,8 @@ pub struct Cfd {
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct CfdOffer { pub struct CfdOrder {
pub id: CfdOfferId, pub id: OrderId,
pub trading_pair: TradingPair, pub trading_pair: TradingPair,
pub position: Position, pub position: Position,
@ -63,7 +63,7 @@ impl ToSseEvent for Vec<model::cfd::Cfd> {
let (profit_btc, profit_usd) = cfd.calc_profit(current_price).unwrap(); let (profit_btc, profit_usd) = cfd.calc_profit(current_price).unwrap();
Cfd { Cfd {
offer_id: cfd.offer_id, order_id: cfd.order_id,
initial_price: cfd.initial_price, initial_price: cfd.initial_price,
leverage: cfd.leverage, leverage: cfd.leverage,
trading_pair: cfd.trading_pair.clone(), trading_pair: cfd.trading_pair.clone(),
@ -91,26 +91,26 @@ impl ToSseEvent for Vec<model::cfd::Cfd> {
} }
} }
impl ToSseEvent for Option<model::cfd::CfdOffer> { impl ToSseEvent for Option<model::cfd::Order> {
fn to_sse_event(&self) -> Event { fn to_sse_event(&self) -> Event {
let offer = self.clone().map(|offer| CfdOffer { let order = self.clone().map(|order| CfdOrder {
id: offer.id, id: order.id,
trading_pair: offer.trading_pair, trading_pair: order.trading_pair,
position: offer.position, position: order.position,
price: offer.price, price: order.price,
min_quantity: offer.min_quantity, min_quantity: order.min_quantity,
max_quantity: offer.max_quantity, max_quantity: order.max_quantity,
leverage: offer.leverage, leverage: order.leverage,
liquidation_price: offer.liquidation_price, liquidation_price: order.liquidation_price,
creation_unix_timestamp: offer creation_unix_timestamp: order
.creation_timestamp .creation_timestamp
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("timestamp to be convertible to duration since epoch") .expect("timestamp to be convertible to duration since epoch")
.as_secs(), .as_secs(),
term_in_secs: offer.term.as_secs(), term_in_secs: order.term.as_secs(),
}); });
Event::json(&offer).event("offer") Event::json(&order).event("order")
} }
} }

16
daemon/src/wire.rs

@ -1,6 +1,6 @@
use crate::model::cfd::CfdOfferId; use crate::model::cfd::OrderId;
use crate::model::Usd; use crate::model::Usd;
use crate::CfdOffer; use crate::Order;
use bdk::bitcoin::secp256k1::Signature; use bdk::bitcoin::secp256k1::Signature;
use bdk::bitcoin::util::psbt::PartiallySignedTransaction; use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
use bdk::bitcoin::{Address, Amount, PublicKey, Txid}; use bdk::bitcoin::{Address, Amount, PublicKey, Txid};
@ -24,10 +24,10 @@ impl std::ops::Deref for AdaptorSignature {
#[serde(tag = "type", content = "payload")] #[serde(tag = "type", content = "payload")]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum TakerToMaker { pub enum TakerToMaker {
TakeOffer { offer_id: CfdOfferId, quantity: Usd }, TakeOrder { order_id: OrderId, quantity: Usd },
// TODO: Currently the taker starts, can already send some stuff for signing over in the first // TODO: Currently the taker starts, can already send some stuff for signing over in the first
// message. // message.
StartContractSetup(CfdOfferId), StartContractSetup(OrderId),
Protocol(SetupMsg), Protocol(SetupMsg),
} }
@ -35,10 +35,10 @@ pub enum TakerToMaker {
#[serde(tag = "type", content = "payload")] #[serde(tag = "type", content = "payload")]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum MakerToTaker { pub enum MakerToTaker {
CurrentOffer(Option<CfdOffer>), CurrentOrder(Option<Order>),
// TODO: Needs RejectOffer as well // TODO: Needs RejectOrder as well
ConfirmTakeOffer(CfdOfferId), // TODO: Include payout curve in "accept" message from maker ConfirmTakeOrder(OrderId), // TODO: Include payout curve in "accept" message from maker
InvalidOfferId(CfdOfferId), InvalidOrderId(OrderId),
Protocol(SetupMsg), Protocol(SetupMsg),
} }

6
docs/asset/mvp_maker_taker_db.puml

@ -6,7 +6,7 @@ hide circle
' avoid problems with angled crows feet ' avoid problems with angled crows feet
skinparam linetype ortho skinparam linetype ortho
entity "offers" as offer { entity "orders" as order {
*id : number <<PK>> <<generated>> *id : number <<PK>> <<generated>>
-- --
... ...
@ -15,7 +15,7 @@ entity "offers" as offer {
entity "cfds" as cfd { entity "cfds" as cfd {
*id : number <<PK>> <<generated>> *id : number <<PK>> <<generated>>
-- --
*offer_id : text <<FK>> *order_id : text <<FK>>
-- --
quantity_usd: long quantity_usd: long
creation_timestamp: Date creation_timestamp: Date
@ -29,7 +29,7 @@ entity "cfd_states" as cfd_states {
note left: state de-/serialized \nfrom rust state enum \nthis is not backwards\ncompatible, but that's \nOK for the MVP note left: state de-/serialized \nfrom rust state enum \nthis is not backwards\ncompatible, but that's \nOK for the MVP
offer ||--|| cfd order ||--|| cfd
cfd ||--|{ cfd_states cfd ||--|{ cfd_states

34
docs/asset/mvp_maker_taker_messaging.puml

@ -2,48 +2,48 @@
actor "Buyer=Taker \n[frontend]" as Buyer actor "Buyer=Taker \n[frontend]" as Buyer
participant "Buyer \n[daemon]" as BuyerApp participant "Buyer \n[daemon]" as BuyerApp
participant "Buyer Offer Feed \n[in daemon]" as BuyerOfferFeed participant "Buyer OrderTile Feed \n[in daemon]" as BuyerOrderFeed
participant "Buyer CFD Feed \n[in daemon]" as BuyerCfdFeed participant "Buyer CFD Feed \n[in daemon]" as BuyerCfdFeed
participant "Seller CFD Feed \n[in daemon]" as SellerCfdFeed participant "Seller CFD Feed \n[in daemon]" as SellerCfdFeed
participant "Seller Offer Feed \n[in daemon]" as SellerOfferFeed participant "Seller OrderTile Feed \n[in daemon]" as SellerOrderFeed
participant "Seller \n[daemon]" as SellerApp participant "Seller \n[daemon]" as SellerApp
actor "Seller=Maker \n[frontend]" as Seller actor "Seller=Maker \n[frontend]" as Seller
participant Oracle as Oracle participant Oracle as Oracle
note over Seller : currently static offer in the frontend note over Seller : currently static order in the frontend
Seller -> SellerOfferFeed: Subscribe Seller -> SellerOrderFeed: Subscribe
note over Seller: The seller should see the current active offer \nInitially there is none (until one POSTed) note over Seller: The seller should see the current active order \nInitially there is none (until one POSTed)
Seller -> SellerApp: POST sell offer Seller -> SellerApp: POST sell order
group Oracle stuff? group Oracle stuff?
SellerApp -> Oracle: Attestation for sell offer SellerApp -> Oracle: Attestation for sell order
Oracle --> SellerApp: Attestation pubkey Oracle --> SellerApp: Attestation pubkey
end group end group
SellerApp -> SellerApp: Store current offer SellerApp -> SellerApp: Store current order
SellerApp -> SellerOfferFeed: Push offer SellerApp -> SellerOrderFeed: Push order
SellerOfferFeed --> Seller: offer [Untaken] SellerOrderFeed --> Seller: order [Untaken]
Buyer -> BuyerApp: Start daemon & UI Buyer -> BuyerApp: Start daemon & UI
BuyerApp -> BuyerOfferFeed: Subscribe BuyerApp -> BuyerOrderFeed: Subscribe
BuyerApp -> BuyerCfdFeed: Subscribe BuyerApp -> BuyerCfdFeed: Subscribe
BuyerApp -> SellerApp: Open TCP (socket) connection BuyerApp -> SellerApp: Open TCP (socket) connection
SellerApp -> SellerApp: New connection SellerApp -> SellerApp: New connection
SellerApp -> BuyerApp: {TCP} Current offer SellerApp -> BuyerApp: {TCP} Current order
note over SellerOfferFeed : Assumption: Current offer \nalways available for new subscriptions note over SellerOrderFeed : Assumption: Current order \nalways available for new subscriptions
BuyerApp -> BuyerOfferFeed: push offer BuyerApp -> BuyerOrderFeed: push order
BuyerOfferFeed --> Buyer: offer BuyerOrderFeed --> Buyer: order
Buyer -> Buyer: Click BUY Buyer -> Buyer: Click BUY
Buyer -> BuyerApp: POST cfd_take_request Buyer -> BuyerApp: POST cfd_take_request
BuyerApp -> BuyerApp: Create cfd [TakeRequested] BuyerApp -> BuyerApp: Create cfd [TakeRequested]
note over BuyerApp: Must include offer_id note over BuyerApp: Must include order_id
BuyerApp -> BuyerCfdFeed: Push cfd BuyerApp -> BuyerCfdFeed: Push cfd
BuyerCfdFeed --> Buyer: cfd [TakeRequested] BuyerCfdFeed --> Buyer: cfd [TakeRequested]
BuyerApp -> SellerApp: {TCP} cfd_take_request (offer_id, quantity) BuyerApp -> SellerApp: {TCP} cfd_take_request (order_id, quantity)
SellerApp -> SellerApp: Create cfd [TakeRequested] SellerApp -> SellerApp: Create cfd [TakeRequested]
SellerApp -> SellerCfdFeed: cfd [TakeRequested] SellerApp -> SellerCfdFeed: cfd [TakeRequested]
SellerCfdFeed --> Seller: cfd [TakeRequested] SellerCfdFeed --> Seller: cfd [TakeRequested]

42
frontend/src/Maker.tsx

@ -17,28 +17,28 @@ import { useAsync } from "react-async";
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { useEventSource } from "react-sse-hooks"; import { useEventSource } from "react-sse-hooks";
import "./App.css"; import "./App.css";
import CfdOffer from "./components/CfdOffer"; import OrderTile from "./components/OrderTile";
import CfdTile from "./components/CfdTile"; import CfdTile from "./components/CfdTile";
import CurrencyInputField from "./components/CurrencyInputField"; import CurrencyInputField from "./components/CurrencyInputField";
import useLatestEvent from "./components/Hooks"; import useLatestEvent from "./components/Hooks";
import NavLink from "./components/NavLink"; import NavLink from "./components/NavLink";
import { Cfd, Offer } from "./components/Types"; import { Cfd, Order } from "./components/Types";
/* TODO: Change from localhost:8001 */ /* TODO: Change from localhost:8001 */
const BASE_URL = "http://localhost:8001"; const BASE_URL = "http://localhost:8001";
interface CfdSellOfferPayload { interface CfdSellOrderPayload {
price: number; price: number;
min_quantity: number; min_quantity: number;
max_quantity: number; max_quantity: number;
} }
async function postCfdSellOfferRequest(payload: CfdSellOfferPayload) { async function postCfdSellOrderRequest(payload: CfdSellOrderPayload) {
let res = await axios.post(BASE_URL + `/offer/sell`, JSON.stringify(payload)); let res = await axios.post(BASE_URL + `/order/sell`, JSON.stringify(payload));
if (!res.status.toString().startsWith("2")) { if (!res.status.toString().startsWith("2")) {
console.log("Status: " + res.status + ", " + res.statusText); console.log("Status: " + res.status + ", " + res.statusText);
throw new Error("failed to publish new offer"); throw new Error("failed to publish new order");
} }
} }
@ -46,7 +46,7 @@ export default function App() {
let source = useEventSource({ source: BASE_URL + "/maker-feed" }); let source = useEventSource({ source: BASE_URL + "/maker-feed" });
const cfds = useLatestEvent<Cfd[]>(source, "cfds"); const cfds = useLatestEvent<Cfd[]>(source, "cfds");
const offer = useLatestEvent<Offer>(source, "offer"); const order = useLatestEvent<Order>(source, "order");
console.log(cfds); console.log(cfds);
@ -55,15 +55,15 @@ export default function App() {
const toast = useToast(); const toast = useToast();
let [minQuantity, setMinQuantity] = useState<string>("100"); let [minQuantity, setMinQuantity] = useState<string>("100");
let [maxQuantity, setMaxQuantity] = useState<string>("1000"); let [maxQuantity, setMaxQuantity] = useState<string>("1000");
let [offerPrice, setOfferPrice] = useState<string>("10000"); let [orderPrice, setOrderPrice] = useState<string>("10000");
const format = (val: any) => `$` + val; const format = (val: any) => `$` + val;
const parse = (val: any) => val.replace(/^\$/, ""); const parse = (val: any) => val.replace(/^\$/, "");
let { run: makeNewCfdSellOffer, isLoading: isCreatingNewCfdOffer } = useAsync({ let { run: makeNewCfdSellOrder, isLoading: isCreatingNewCfdOrder } = useAsync({
deferFn: async ([payload]: any[]) => { deferFn: async ([payload]: any[]) => {
try { try {
await postCfdSellOfferRequest(payload as CfdSellOfferPayload); await postCfdSellOrderRequest(payload as CfdSellOrderPayload);
} catch (e) { } catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e); const description = typeof e === "string" ? e : JSON.stringify(e);
@ -139,11 +139,11 @@ export default function App() {
/> />
</HStack> </HStack>
<HStack> <HStack>
<Text>Offer Price:</Text> <Text>Order Price:</Text>
</HStack> </HStack>
<CurrencyInputField <CurrencyInputField
onChange={(valueString: string) => setOfferPrice(parse(valueString))} onChange={(valueString: string) => setOrderPrice(parse(valueString))}
value={format(offerPrice)} value={format(orderPrice)}
/> />
<Text>Leverage:</Text> <Text>Leverage:</Text>
<Flex justifyContent={"space-between"}> <Flex justifyContent={"space-between"}>
@ -154,26 +154,26 @@ export default function App() {
<VStack> <VStack>
<Center><Text>Maker UI</Text></Center> <Center><Text>Maker UI</Text></Center>
<Button <Button
disabled={isCreatingNewCfdOffer} disabled={isCreatingNewCfdOrder}
variant={"solid"} variant={"solid"}
colorScheme={"blue"} colorScheme={"blue"}
onClick={() => { onClick={() => {
let payload: CfdSellOfferPayload = { let payload: CfdSellOrderPayload = {
price: Number.parseFloat(offerPrice), price: Number.parseFloat(orderPrice),
min_quantity: Number.parseFloat(minQuantity), min_quantity: Number.parseFloat(minQuantity),
max_quantity: Number.parseFloat(maxQuantity), max_quantity: Number.parseFloat(maxQuantity),
}; };
makeNewCfdSellOffer(payload); makeNewCfdSellOrder(payload);
}} }}
> >
{offer ? "Update Sell Offer" : "Create Sell Offer"} {order ? "Update Sell Order" : "Create Sell Order"}
</Button> </Button>
<Divider /> <Divider />
<Box width={"100%"} overflow={"scroll"}> <Box width={"100%"} overflow={"scroll"}>
<Box> <Box>
{offer {order
&& <CfdOffer && <OrderTile
offer={offer} order={order}
/>} />}
</Box> </Box>
</Box> </Box>

24
frontend/src/Taker.tsx

@ -9,13 +9,13 @@ import CfdTile from "./components/CfdTile";
import CurrencyInputField from "./components/CurrencyInputField"; import CurrencyInputField from "./components/CurrencyInputField";
import useLatestEvent from "./components/Hooks"; import useLatestEvent from "./components/Hooks";
import NavLink from "./components/NavLink"; import NavLink from "./components/NavLink";
import { Cfd, Offer } from "./components/Types"; import { Cfd, Order } from "./components/Types";
/* TODO: Change from localhost:8000 */ /* TODO: Change from localhost:8000 */
const BASE_URL = "http://localhost:8000"; const BASE_URL = "http://localhost:8000";
interface CfdTakeRequestPayload { interface CfdTakeRequestPayload {
offer_id: string; order_id: string;
quantity: number; quantity: number;
} }
@ -51,7 +51,7 @@ export default function App() {
let source = useEventSource({ source: BASE_URL + "/feed" }); let source = useEventSource({ source: BASE_URL + "/feed" });
const cfds = useLatestEvent<Cfd[]>(source, "cfds"); const cfds = useLatestEvent<Cfd[]>(source, "cfds");
const offer = useLatestEvent<Offer>(source, "offer"); const order = useLatestEvent<Order>(source, "order");
const balance = useLatestEvent<number>(source, "balance"); const balance = useLatestEvent<number>(source, "balance");
const toast = useToast(); const toast = useToast();
@ -140,13 +140,13 @@ export default function App() {
<Text>{balance}</Text> <Text>{balance}</Text>
</HStack> </HStack>
<HStack> <HStack>
{/*TODO: Do we need this? does it make sense to only display the price from the offer?*/} {/*TODO: Do we need this? does it make sense to only display the price from the order?*/}
<Text align={"left"}>Current Price (Kraken):</Text> <Text align={"left"}>Current Price (Kraken):</Text>
<Text>tbd</Text> <Text>tbd</Text>
</HStack> </HStack>
<HStack> <HStack>
<Text align={"left"}>Offer Price:</Text> <Text align={"left"}>Order Price:</Text>
<Text>{offer?.price}</Text> <Text>{order?.price}</Text>
</HStack> </HStack>
<HStack> <HStack>
<Text>Quantity:</Text> <Text>Quantity:</Text>
@ -154,14 +154,14 @@ export default function App() {
onChange={(valueString: string) => { onChange={(valueString: string) => {
setQuantity(parse(valueString)) setQuantity(parse(valueString))
if (!offer) { if (!order) {
return; return;
} }
let quantity = valueString ? Number.parseFloat(valueString) : 0; let quantity = valueString ? Number.parseFloat(valueString) : 0;
let payload: MarginRequestPayload = { let payload: MarginRequestPayload = {
leverage: offer.leverage, leverage: order.leverage,
price: offer.price, price: order.price,
quantity quantity
} }
calculateMargin(payload); calculateMargin(payload);
@ -178,15 +178,15 @@ export default function App() {
<Flex justifyContent={"space-between"}> <Flex justifyContent={"space-between"}>
<Button disabled={true}>x1</Button> <Button disabled={true}>x1</Button>
<Button disabled={true}>x2</Button> <Button disabled={true}>x2</Button>
<Button colorScheme="blue" variant="solid">x{offer?.leverage}</Button> <Button colorScheme="blue" variant="solid">x{order?.leverage}</Button>
</Flex> </Flex>
{<Button {<Button
disabled={isCreatingNewTakeRequest || !offer} disabled={isCreatingNewTakeRequest || !order}
variant={"solid"} variant={"solid"}
colorScheme={"blue"} colorScheme={"blue"}
onClick={() => { onClick={() => {
let payload: CfdTakeRequestPayload = { let payload: CfdTakeRequestPayload = {
offer_id: offer!.id, order_id: order!.id,
quantity: Number.parseFloat(quantity), quantity: Number.parseFloat(quantity),
}; };
makeNewTakeRequest(payload); makeNewTakeRequest(payload);

30
frontend/src/components/CfdOffer.tsx → frontend/src/components/OrderTile.tsx

@ -1,21 +1,21 @@
import { Box, SimpleGrid, Text, VStack } from "@chakra-ui/react"; import { Box, SimpleGrid, Text, VStack } from "@chakra-ui/react";
import React from "react"; import React from "react";
import { Offer } from "./Types"; import { Order } from "./Types";
interface CfdOfferProps { interface OrderProps {
offer: Offer; order: Order;
} }
function CfdOffer( function OrderTile(
{ {
offer, order,
}: CfdOfferProps, }: OrderProps,
) { ) {
return ( return (
<Box borderRadius={"md"} borderColor={"blue.800"} borderWidth={2} bg={"gray.50"}> <Box borderRadius={"md"} borderColor={"blue.800"} borderWidth={2} bg={"gray.50"}>
<VStack> <VStack>
<Box bg="blue.800" w="100%"> <Box bg="blue.800" w="100%">
<Text padding={2} color={"white"} fontWeight={"bold"}>Current CFD Sell Offer</Text> <Text padding={2} color={"white"} fontWeight={"bold"}>Current CFD Sell Order</Text>
</Box> </Box>
<SimpleGrid padding={5} columns={2} spacing={5}> <SimpleGrid padding={5} columns={2} spacing={5}>
<Text>ID</Text> <Text>ID</Text>
@ -25,18 +25,18 @@ function CfdOffer(
whiteSpace="nowrap" whiteSpace="nowrap"
_hover={{ overflow: "visible" }} _hover={{ overflow: "visible" }}
> >
{offer.id} {order.id}
</Text> </Text>
<Text>Trading Pair</Text> <Text>Trading Pair</Text>
<Text>{offer.trading_pair}</Text> <Text>{order.trading_pair}</Text>
<Text>Price</Text> <Text>Price</Text>
<Text>{offer.price}</Text> <Text>{order.price}</Text>
<Text>Min Quantity</Text> <Text>Min Quantity</Text>
<Text>{offer.min_quantity}</Text> <Text>{order.min_quantity}</Text>
<Text>Max Quantity</Text> <Text>Max Quantity</Text>
<Text>{offer.max_quantity}</Text> <Text>{order.max_quantity}</Text>
<Text>Leverage</Text> <Text>Leverage</Text>
<Text>{offer.leverage}</Text> <Text>{order.leverage}</Text>
<Text>Liquidation Price</Text> <Text>Liquidation Price</Text>
<Text <Text
overflow="hidden" overflow="hidden"
@ -44,7 +44,7 @@ function CfdOffer(
whiteSpace="nowrap" whiteSpace="nowrap"
_hover={{ overflow: "visible" }} _hover={{ overflow: "visible" }}
> >
{offer.liquidation_price} {order.liquidation_price}
</Text> </Text>
</SimpleGrid> </SimpleGrid>
</VStack> </VStack>
@ -52,4 +52,4 @@ function CfdOffer(
); );
} }
export default CfdOffer; export default OrderTile;

4
frontend/src/components/Types.tsx

@ -1,4 +1,4 @@
export interface Offer { export interface Order {
id: string; id: string;
trading_pair: string; trading_pair: string;
position: string; position: string;
@ -12,7 +12,7 @@ export interface Offer {
} }
export interface Cfd { export interface Cfd {
offer_id: string; order_id: string;
initial_price: number; initial_price: number;
leverage: number; leverage: number;

Loading…
Cancel
Save