Browse Source

Wallet updates in the UI 🎉

- wallet feed that sends balance + current address
- display in UI (balance + address) in separate wallet component (shared for taker and maker for now)

Includes two seed files that are already funded with some testnet coins. We can share those for testing for now. If more funds are needed I'm happy to top them up.
no-contract-setup-message
Daniel Karzel 3 years ago
parent
commit
d5bbee3edd
No known key found for this signature in database GPG Key ID: 30C3FC2E438ADB6E
  1. 1
      Cargo.lock
  2. 1
      daemon/Cargo.toml
  3. 23
      daemon/src/maker.rs
  4. 8
      daemon/src/maker_cfd_actor.rs
  5. 9
      daemon/src/model.rs
  6. 18
      daemon/src/routes_maker.rs
  7. 16
      daemon/src/routes_taker.rs
  8. 22
      daemon/src/taker.rs
  9. 8
      daemon/src/taker_cfd_actor.rs
  10. 30
      daemon/src/to_sse_event.rs
  11. 23
      daemon/src/wallet.rs
  12. 1
      daemon/util/testnet_seeds/maker_seed
  13. BIN
      daemon/util/testnet_seeds/taker_seed
  14. 14
      frontend/src/Maker.tsx
  15. 10
      frontend/src/Taker.tsx
  16. 4
      frontend/src/components/CfdTile.tsx
  17. 14
      frontend/src/components/Types.tsx
  18. 49
      frontend/src/components/Wallet.tsx

1
Cargo.lock

@ -427,6 +427,7 @@ dependencies = [
"sha2", "sha2",
"sqlx", "sqlx",
"tempfile", "tempfile",
"thiserror",
"tokio", "tokio",
"tokio-util", "tokio-util",
"uuid", "uuid",

1
daemon/Cargo.toml

@ -20,6 +20,7 @@ serde_json = "1"
serde_with = { version = "1", features = ["macros"] } serde_with = { version = "1", features = ["macros"] }
sha2 = "0.9" sha2 = "0.9"
sqlx = { version = "0.5", features = ["offline"] } sqlx = { version = "0.5", features = ["offline"] }
thiserror = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "net"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "net"] }
tokio-util = { version = "0.6", features = ["codec"] } tokio-util = { version = "0.6", features = ["codec"] }
uuid = { version = "0.8", features = ["serde", "v4"] } uuid = { version = "0.8", features = ["serde", "v4"] }

23
daemon/src/maker.rs

@ -1,13 +1,16 @@
use crate::maker_cfd_actor::Command;
use crate::seed::Seed; use crate::seed::Seed;
use crate::wallet::Wallet; use crate::wallet::Wallet;
use anyhow::Result; use anyhow::Result;
use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1}; use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1};
use bdk::bitcoin::{Amount, Network}; use bdk::bitcoin::Network;
use clap::Clap; use clap::Clap;
use model::cfd::{Cfd, Order}; use model::cfd::{Cfd, Order};
use model::WalletInfo;
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
use rocket_db_pools::Database; use rocket_db_pools::Database;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration;
use tokio::sync::{mpsc, watch}; use tokio::sync::{mpsc, watch};
mod db; mod db;
@ -72,12 +75,13 @@ async fn main() -> Result<()> {
ext_priv_key, ext_priv_key,
) )
.await?; .await?;
let wallet_info = wallet.sync().unwrap();
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 (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(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 (wallet_feed_sender, wallet_feed_receiver) = watch::channel::<WalletInfo>(wallet_info);
let figment = rocket::Config::figment() let figment = rocket::Config::figment()
.merge(("databases.maker.url", data_dir.join("maker.sqlite"))) .merge(("databases.maker.url", data_dir.join("maker.sqlite")))
@ -91,7 +95,7 @@ async fn main() -> Result<()> {
rocket::custom(figment) rocket::custom(figment)
.manage(cfd_feed_receiver) .manage(cfd_feed_receiver)
.manage(order_feed_receiver) .manage(order_feed_receiver)
.manage(balance_feed_receiver) .manage(wallet_feed_receiver)
.attach(Db::init()) .attach(Db::init())
.attach(AdHoc::try_on_ignite( .attach(AdHoc::try_on_ignite(
"SQL migrations", "SQL migrations",
@ -123,6 +127,7 @@ async fn main() -> Result<()> {
connections_actor_inbox_sender, connections_actor_inbox_sender,
cfd_feed_sender, cfd_feed_sender,
order_feed_sender, order_feed_sender,
wallet_feed_sender,
); );
let connections_actor = maker_inc_connections_actor::new( let connections_actor = maker_inc_connections_actor::new(
listener, listener,
@ -130,6 +135,18 @@ async fn main() -> Result<()> {
connections_actor_inbox_recv, connections_actor_inbox_recv,
); );
// consecutive wallet syncs handled by task that triggers sync
let wallet_sync_interval = Duration::from_secs(10);
tokio::spawn({
let cfd_actor_inbox = cfd_maker_actor_inbox.clone();
async move {
loop {
cfd_actor_inbox.send(Command::SyncWallet).unwrap();
tokio::time::sleep(wallet_sync_interval).await;
}
}
});
tokio::spawn(cfd_maker_actor); tokio::spawn(cfd_maker_actor);
tokio::spawn(connections_actor); tokio::spawn(connections_actor);

8
daemon/src/maker_cfd_actor.rs

@ -1,6 +1,6 @@
use crate::db::{insert_cfd, insert_order, load_all_cfds, load_cfd_by_order_id, load_order_by_id}; use crate::db::{insert_cfd, insert_order, load_all_cfds, load_cfd_by_order_id, load_order_by_id};
use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId}; use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId};
use crate::model::{TakerId, Usd}; use crate::model::{TakerId, Usd, WalletInfo};
use crate::wallet::Wallet; use crate::wallet::Wallet;
use crate::wire::SetupMsg; use crate::wire::SetupMsg;
use crate::{maker_cfd_actor, maker_inc_connections_actor, setup_contract_actor}; use crate::{maker_cfd_actor, maker_inc_connections_actor, setup_contract_actor};
@ -12,6 +12,7 @@ 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 {
SyncWallet,
TakeOrder { TakeOrder {
taker_id: TakerId, taker_id: TakerId,
order_id: OrderId, order_id: OrderId,
@ -36,6 +37,7 @@ pub fn new(
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>>,
order_feed_sender: watch::Sender<Option<Order>>, order_feed_sender: watch::Sender<Option<Order>>,
wallet_feed_sender: watch::Sender<WalletInfo>,
) -> ( ) -> (
impl Future<Output = ()>, impl Future<Output = ()>,
mpsc::UnboundedSender<maker_cfd_actor::Command>, mpsc::UnboundedSender<maker_cfd_actor::Command>,
@ -57,6 +59,10 @@ pub fn new(
while let Some(message) = receiver.recv().await { while let Some(message) = receiver.recv().await {
match message { match message {
maker_cfd_actor::Command::SyncWallet => {
let wallet_info = wallet.sync().unwrap();
wallet_feed_sender.send(wallet_info).unwrap();
}
maker_cfd_actor::Command::TakeOrder { maker_cfd_actor::Command::TakeOrder {
taker_id, taker_id,
order_id, order_id,

9
daemon/src/model.rs

@ -5,6 +5,8 @@ use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal; use rust_decimal::Decimal;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use bdk::bitcoin::{Address, Amount};
use std::time::SystemTime;
use uuid::Uuid; use uuid::Uuid;
pub mod cfd; pub mod cfd;
@ -83,3 +85,10 @@ impl Display for TakerId {
self.0.fmt(f) self.0.fmt(f)
} }
} }
#[derive(Debug, Clone)]
pub struct WalletInfo {
pub balance: Amount,
pub address: Address,
pub last_updated_at: SystemTime,
}

18
daemon/src/routes_maker.rs

@ -1,9 +1,9 @@
use crate::maker_cfd_actor; use crate::maker_cfd_actor;
use crate::model::cfd::{Cfd, Order, Origin}; use crate::model::cfd::{Cfd, Order, Origin};
use crate::model::Usd; use crate::model::{Usd, WalletInfo};
use crate::to_sse_event::ToSseEvent; use crate::to_sse_event::ToSseEvent;
use anyhow::Result; use anyhow::Result;
use bdk::bitcoin::Amount;
use rocket::response::status; use rocket::response::status;
use rocket::response::stream::EventStream; use rocket::response::stream::EventStream;
use rocket::serde::json::Json; use rocket::serde::json::Json;
@ -16,15 +16,15 @@ use tokio::sync::{mpsc, watch};
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_order: &State<watch::Receiver<Option<Order>>>, rx_order: &State<watch::Receiver<Option<Order>>>,
rx_balance: &State<watch::Receiver<Amount>>, rx_wallet: &State<watch::Receiver<WalletInfo>>,
) -> EventStream![] { ) -> EventStream![] {
let mut rx_cfds = rx_cfds.inner().clone(); let mut rx_cfds = rx_cfds.inner().clone();
let mut rx_order = rx_order.inner().clone(); let mut rx_order = rx_order.inner().clone();
let mut rx_balance = rx_balance.inner().clone(); let mut rx_wallet = rx_wallet.inner().clone();
EventStream! { EventStream! {
let balance = rx_balance.borrow().clone(); let wallet_info = rx_wallet.borrow().clone();
yield balance.to_sse_event(); yield wallet_info.to_sse_event();
let order = rx_order.borrow().clone(); let order = rx_order.borrow().clone();
yield order.to_sse_event(); yield order.to_sse_event();
@ -34,9 +34,9 @@ pub async fn maker_feed(
loop{ loop{
select! { select! {
Ok(()) = rx_balance.changed() => { Ok(()) = rx_wallet.changed() => {
let balance = rx_balance.borrow().clone(); let wallet_info = rx_wallet.borrow().clone();
yield balance.to_sse_event(); yield wallet_info.to_sse_event();
}, },
Ok(()) = rx_order.changed() => { Ok(()) = rx_order.changed() => {
let order = rx_order.borrow().clone(); let order = rx_order.borrow().clone();

16
daemon/src/routes_taker.rs

@ -1,5 +1,5 @@
use crate::model::cfd::{calculate_buy_margin, Cfd, Order, OrderId}; use crate::model::cfd::{calculate_buy_margin, Cfd, Order, OrderId};
use crate::model::{Leverage, Usd}; use crate::model::{Leverage, Usd, WalletInfo};
use crate::taker_cfd_actor; use crate::taker_cfd_actor;
use crate::to_sse_event::ToSseEvent; use crate::to_sse_event::ToSseEvent;
use bdk::bitcoin::Amount; use bdk::bitcoin::Amount;
@ -15,15 +15,15 @@ use tokio::sync::{mpsc, watch};
pub async fn feed( pub async fn feed(
rx_cfds: &State<watch::Receiver<Vec<Cfd>>>, rx_cfds: &State<watch::Receiver<Vec<Cfd>>>,
rx_order: &State<watch::Receiver<Option<Order>>>, rx_order: &State<watch::Receiver<Option<Order>>>,
rx_balance: &State<watch::Receiver<Amount>>, rx_wallet: &State<watch::Receiver<WalletInfo>>,
) -> EventStream![] { ) -> EventStream![] {
let mut rx_cfds = rx_cfds.inner().clone(); let mut rx_cfds = rx_cfds.inner().clone();
let mut rx_order = rx_order.inner().clone(); let mut rx_order = rx_order.inner().clone();
let mut rx_balance = rx_balance.inner().clone(); let mut rx_wallet = rx_wallet.inner().clone();
EventStream! { EventStream! {
let balance = rx_balance.borrow().clone(); let wallet_info = rx_wallet.borrow().clone();
yield balance.to_sse_event(); yield wallet_info.to_sse_event();
let order = rx_order.borrow().clone(); let order = rx_order.borrow().clone();
yield order.to_sse_event(); yield order.to_sse_event();
@ -33,9 +33,9 @@ pub async fn feed(
loop{ loop{
select! { select! {
Ok(()) = rx_balance.changed() => { Ok(()) = rx_wallet.changed() => {
let balance = rx_balance.borrow().clone(); let wallet_info = rx_wallet.borrow().clone();
yield balance.to_sse_event(); yield wallet_info.to_sse_event();
}, },
Ok(()) = rx_order.changed() => { Ok(()) = rx_order.changed() => {
let order = rx_order.borrow().clone(); let order = rx_order.borrow().clone();

22
daemon/src/taker.rs

@ -1,7 +1,9 @@
use crate::model::WalletInfo;
use crate::taker_cfd_actor::Command;
use crate::wallet::Wallet; use crate::wallet::Wallet;
use anyhow::Result; use anyhow::Result;
use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1}; use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1};
use bdk::bitcoin::{Amount, Network}; use bdk::bitcoin::Network;
use clap::Clap; use clap::Clap;
use model::cfd::{Cfd, Order}; use model::cfd::{Cfd, Order};
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
@ -77,12 +79,13 @@ async fn main() -> Result<()> {
ext_priv_key, ext_priv_key,
) )
.await?; .await?;
let wallet_info = wallet.sync().unwrap();
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 (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(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 (wallet_feed_sender, wallet_feed_receiver) = watch::channel::<WalletInfo>(wallet_info);
let (read, write) = loop { let (read, write) = loop {
let socket = tokio::net::TcpSocket::new_v4()?; let socket = tokio::net::TcpSocket::new_v4()?;
@ -104,7 +107,7 @@ async fn main() -> Result<()> {
rocket::custom(figment) rocket::custom(figment)
.manage(cfd_feed_receiver) .manage(cfd_feed_receiver)
.manage(order_feed_receiver) .manage(order_feed_receiver)
.manage(balance_feed_receiver) .manage(wallet_feed_receiver)
.attach(Db::init()) .attach(Db::init())
.attach(AdHoc::try_on_ignite( .attach(AdHoc::try_on_ignite(
"SQL migrations", "SQL migrations",
@ -135,10 +138,23 @@ async fn main() -> Result<()> {
cfd_feed_sender, cfd_feed_sender,
order_feed_sender, order_feed_sender,
out_maker_actor_inbox, out_maker_actor_inbox,
wallet_feed_sender,
); );
let inc_maker_messages_actor = let inc_maker_messages_actor =
taker_inc_message_actor::new(read, cfd_actor_inbox.clone()); taker_inc_message_actor::new(read, cfd_actor_inbox.clone());
// consecutive wallet syncs handled by task that triggers sync
let wallet_sync_interval = Duration::from_secs(10);
tokio::spawn({
let cfd_actor_inbox = cfd_actor_inbox.clone();
async move {
loop {
cfd_actor_inbox.send(Command::SyncWallet).unwrap();
tokio::time::sleep(wallet_sync_interval).await;
}
}
});
tokio::spawn(cfd_actor); tokio::spawn(cfd_actor);
tokio::spawn(inc_maker_messages_actor); tokio::spawn(inc_maker_messages_actor);
tokio::spawn(out_maker_messages_actor); tokio::spawn(out_maker_messages_actor);

8
daemon/src/taker_cfd_actor.rs

@ -3,7 +3,7 @@ use crate::db::{
load_cfd_by_order_id, load_order_by_id, load_cfd_by_order_id, load_order_by_id,
}; };
use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId}; use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId};
use crate::model::Usd; use crate::model::{Usd, WalletInfo};
use crate::wallet::Wallet; use crate::wallet::Wallet;
use crate::wire::SetupMsg; use crate::wire::SetupMsg;
use crate::{setup_contract_actor, wire}; use crate::{setup_contract_actor, wire};
@ -16,6 +16,7 @@ use tokio::sync::{mpsc, watch};
#[derive(Debug)] #[derive(Debug)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum Command { pub enum Command {
SyncWallet,
TakeOrder { order_id: OrderId, quantity: Usd }, TakeOrder { order_id: OrderId, quantity: Usd },
NewOrder(Option<Order>), NewOrder(Option<Order>),
OrderAccepted(OrderId), OrderAccepted(OrderId),
@ -30,6 +31,7 @@ pub fn new(
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>,
order_feed_actor_inbox: watch::Sender<Option<Order>>, order_feed_actor_inbox: watch::Sender<Option<Order>>,
out_msg_maker_inbox: mpsc::UnboundedSender<wire::TakerToMaker>, out_msg_maker_inbox: mpsc::UnboundedSender<wire::TakerToMaker>,
wallet_feed_sender: watch::Sender<WalletInfo>,
) -> (impl Future<Output = ()>, mpsc::UnboundedSender<Command>) { ) -> (impl Future<Output = ()>, mpsc::UnboundedSender<Command>) {
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;
@ -46,6 +48,10 @@ pub fn new(
while let Some(message) = receiver.recv().await { while let Some(message) = receiver.recv().await {
match message { match message {
Command::SyncWallet => {
let wallet_info = wallet.sync().unwrap();
wallet_feed_sender.send(wallet_info).unwrap();
}
Command::TakeOrder { order_id, quantity } => { Command::TakeOrder { order_id, quantity } => {
let mut conn = db.acquire().await.unwrap(); let mut conn = db.acquire().await.unwrap();

30
daemon/src/to_sse_event.rs

@ -26,7 +26,7 @@ pub struct Cfd {
pub profit_usd: Usd, pub profit_usd: Usd,
pub state: String, pub state: String,
pub state_transition_unix_timestamp: u64, pub state_transition_timestamp: u64,
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
@ -44,7 +44,7 @@ pub struct CfdOrder {
pub leverage: Leverage, pub leverage: Leverage,
pub liquidation_price: Usd, pub liquidation_price: Usd,
pub creation_unix_timestamp: u64, pub creation_timestamp: u64,
pub term_in_secs: u64, pub term_in_secs: u64,
} }
@ -73,7 +73,7 @@ impl ToSseEvent for Vec<model::cfd::Cfd> {
profit_btc, profit_btc,
profit_usd, profit_usd,
state: cfd.state.to_string(), state: cfd.state.to_string(),
state_transition_unix_timestamp: cfd state_transition_timestamp: cfd
.state .state
.get_transition_timestamp() .get_transition_timestamp()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
@ -102,7 +102,7 @@ impl ToSseEvent for Option<model::cfd::Order> {
max_quantity: order.max_quantity, max_quantity: order.max_quantity,
leverage: order.leverage, leverage: order.leverage,
liquidation_price: order.liquidation_price, liquidation_price: order.liquidation_price,
creation_unix_timestamp: order creation_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")
@ -114,8 +114,26 @@ impl ToSseEvent for Option<model::cfd::Order> {
} }
} }
impl ToSseEvent for Amount { #[derive(Debug, Clone, Serialize)]
pub struct WalletInfo {
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_btc")]
balance: Amount,
address: String,
last_updated_at: u64,
}
impl ToSseEvent for model::WalletInfo {
fn to_sse_event(&self) -> Event { fn to_sse_event(&self) -> Event {
Event::json(&self.as_btc()).event("balance") let wallet_info = WalletInfo {
balance: self.balance,
address: self.address.to_string(),
last_updated_at: self
.last_updated_at
.duration_since(UNIX_EPOCH)
.expect("timestamp to be convertible to duration since epoch")
.as_secs(),
};
Event::json(&wallet_info).event("wallet")
} }
} }

23
daemon/src/wallet.rs

@ -1,10 +1,13 @@
use crate::model::WalletInfo;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use bdk::bitcoin::util::bip32::ExtendedPrivKey; use bdk::bitcoin::util::bip32::ExtendedPrivKey;
use bdk::bitcoin::{Amount, PublicKey}; use bdk::bitcoin::{Amount, PublicKey};
use bdk::blockchain::{ElectrumBlockchain, NoopProgress}; use bdk::blockchain::{ElectrumBlockchain, NoopProgress};
use bdk::wallet::AddressIndex;
use bdk::KeychainKind; use bdk::KeychainKind;
use cfd_protocol::{PartyParams, WalletExt}; use cfd_protocol::{PartyParams, WalletExt};
use std::path::Path; use std::path::Path;
use std::time::SystemTime;
const SLED_TREE_NAME: &str = "wallet"; const SLED_TREE_NAME: &str = "wallet";
@ -32,10 +35,6 @@ impl Wallet {
ElectrumBlockchain::from(client), ElectrumBlockchain::from(client),
)?; )?;
wallet
.sync(NoopProgress, None)
.context("Failed to sync the wallet")?; // TODO: Use LogProgress once we have logging.
Ok(Self { wallet }) Ok(Self { wallet })
} }
@ -46,4 +45,20 @@ impl Wallet {
) -> Result<PartyParams> { ) -> Result<PartyParams> {
self.wallet.build_party_params(amount, identity_pk) self.wallet.build_party_params(amount, identity_pk)
} }
pub fn sync(&self) -> Result<WalletInfo> {
self.wallet.sync(NoopProgress, None)?;
let balance = self.wallet.get_balance()?;
let address = self.wallet.get_address(AddressIndex::LastUnused)?.address;
let wallet_info = WalletInfo {
balance: Amount::from_sat(balance),
address,
last_updated_at: SystemTime::now(),
};
Ok(wallet_info)
}
} }

1
daemon/util/testnet_seeds/maker_seed

@ -0,0 +1 @@
%÷_×U›“�]ÿv@cz¸ìáSÆRJ£ƒ®ºÕo–¤o{ø³ôâT¸&hìö‡ÜƬu_ÏÖ¬÷œh*( ÷èBBm^ö[6ÅNm»IÇ+댵[cm‹ £GºjëuC¸òó>Áx…A´¼L¯Ccx¨ÎZEåÞX#ßz…´}øÒÚ€œlA*Áº}"Ú¬ˆIü¥pµ`�ólr=BþÙ³¯ôT7_)DÓpÃ,k�TÑRór–‚T“ÖA¡$ìÕ¯šÎ¨ žÜ^¯¯ÙVSA�+Ñ“‡Â”Íò^F¶\=¿ÑU^H{÷€KŠ/ ,‰¥Ã¨(ŸñEß#8KO†Ðɨ–è×¹

BIN
daemon/util/testnet_seeds/taker_seed

Binary file not shown.

14
frontend/src/Maker.tsx

@ -22,7 +22,8 @@ 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 OrderTile from "./components/OrderTile"; import OrderTile from "./components/OrderTile";
import { Cfd, Order } from "./components/Types"; import { Cfd, Order, WalletInfo } from "./components/Types";
import Wallet from "./components/Wallet";
interface CfdSellOrderPayload { interface CfdSellOrderPayload {
price: number; price: number;
@ -47,7 +48,7 @@ export default function App() {
console.log(cfds); console.log(cfds);
const balance = useLatestEvent<number>(source, "balance"); const walletInfo = useLatestEvent<WalletInfo>(source, "wallet");
const toast = useToast(); const toast = useToast();
let [minQuantity, setMinQuantity] = useState<string>("100"); let [minQuantity, setMinQuantity] = useState<string>("100");
@ -111,12 +112,9 @@ export default function App() {
</Box> </Box>
</VStack> </VStack>
</Flex> </Flex>
<Flex width={"50%"} marginLeft={5}> <Flex width={"50%"} marginLeft={5} direction={"column"}>
<VStack spacing={5} shadow={"md"} padding={5} align={"stretch"}> <Wallet walletInfo={walletInfo} />
<HStack> <VStack spacing={5} shadow={"md"} padding={5} align={"stretch"} height={"100%"}>
<Text align={"left"}>Your balance:</Text>
<Text>{balance}</Text>
</HStack>
<HStack> <HStack>
<Text align={"left"}>Current Price:</Text> <Text align={"left"}>Current Price:</Text>
<Text>{49000}</Text> <Text>{49000}</Text>

10
frontend/src/Taker.tsx

@ -9,7 +9,8 @@ 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, Order } from "./components/Types"; import { Cfd, Order, WalletInfo } from "./components/Types";
import Wallet from "./components/Wallet";
interface CfdTakeRequestPayload { interface CfdTakeRequestPayload {
order_id: string; order_id: string;
@ -49,7 +50,7 @@ export default function App() {
const cfds = useLatestEvent<Cfd[]>(source, "cfds"); const cfds = useLatestEvent<Cfd[]>(source, "cfds");
const order = useLatestEvent<Order>(source, "order"); const order = useLatestEvent<Order>(source, "order");
const balance = useLatestEvent<number>(source, "balance"); const walletInfo = useLatestEvent<WalletInfo>(source, "wallet");
const toast = useToast(); const toast = useToast();
let [quantity, setQuantity] = useState("0"); let [quantity, setQuantity] = useState("0");
@ -132,10 +133,7 @@ export default function App() {
</Flex> </Flex>
<Flex width={"50%"} marginLeft={5}> <Flex width={"50%"} marginLeft={5}>
<VStack spacing={5} shadow={"md"} padding={5} align={"stretch"}> <VStack spacing={5} shadow={"md"} padding={5} align={"stretch"}>
<HStack> <Wallet walletInfo={walletInfo} />
<Text align={"left"}>Your balance:</Text>
<Text>{balance}</Text>
</HStack>
<HStack> <HStack>
{/*TODO: Do we need this? does it make sense to only display the price from the order?*/} {/*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>

4
frontend/src/components/CfdTile.tsx

@ -1,6 +1,6 @@
import { Box, Button, SimpleGrid, Text, VStack } from "@chakra-ui/react"; import { Box, Button, SimpleGrid, Text, VStack } from "@chakra-ui/react";
import React from "react"; import React from "react";
import { Cfd } from "./Types"; import { Cfd, unixTimestampToDate } from "./Types";
interface CfdTileProps { interface CfdTileProps {
index: number; index: number;
@ -46,7 +46,7 @@ export default function CfdTile(
<Text>Open since</Text> <Text>Open since</Text>
{/* TODO: Format date in a more compact way */} {/* TODO: Format date in a more compact way */}
<Text> <Text>
{(new Date(cfd.state_transition_unix_timestamp * 1000).toString())} {unixTimestampToDate(cfd.state_transition_timestamp).toString()}
</Text> </Text>
<Text>Status</Text> <Text>Status</Text>
<Text>{cfd.state}</Text> <Text>{cfd.state}</Text>

14
frontend/src/components/Types.tsx

@ -7,7 +7,7 @@ export interface Order {
max_quantity: number; max_quantity: number;
leverage: number; leverage: number;
liquidation_price: number; liquidation_price: number;
creation_unix_timestamp: number; creation_timestamp: number;
term_in_secs: number; term_in_secs: number;
} }
@ -28,5 +28,15 @@ export interface Cfd {
profit_usd: number; profit_usd: number;
state: string; state: string;
state_transition_unix_timestamp: number; state_transition_timestamp: number;
}
export interface WalletInfo {
balance: number;
address: string;
last_updated_at: number;
}
export function unixTimestampToDate(unixTimestamp: number): Date {
return new Date(unixTimestamp * 1000);
} }

49
frontend/src/components/Wallet.tsx

@ -0,0 +1,49 @@
import { CheckIcon, CopyIcon } from "@chakra-ui/icons";
import { Box, Center, Divider, HStack, IconButton, Skeleton, Text, useClipboard } from "@chakra-ui/react";
import React from "react";
import { unixTimestampToDate, WalletInfo } from "./Types";
interface WalletProps {
walletInfo: WalletInfo | null;
}
export default function Wallet(
{
walletInfo,
}: WalletProps,
) {
const { hasCopied, onCopy } = useClipboard(walletInfo ? walletInfo.address : "");
let balance = <Skeleton height="20px" />;
let address = <Skeleton height="20px" />;
let timestamp = <Skeleton height="20px" />;
if (walletInfo) {
balance = <Text>{walletInfo.balance} BTC</Text>;
address = (
<HStack>
<Text>{walletInfo.address}</Text>
<IconButton
aria-label="Copy to clipboard"
icon={hasCopied ? <CheckIcon /> : <CopyIcon />}
onClick={onCopy}
/>
</HStack>
);
timestamp = <Text>{unixTimestampToDate(walletInfo.last_updated_at).toString()}</Text>;
}
return (
<Box shadow={"md"} marginBottom={5} padding={5}>
<Center><Text fontWeight={"bold"}>Your wallet</Text></Center>
<HStack>
<Text align={"left"}>Balance:</Text>
{balance}
</HStack>
<Divider marginTop={2} marginBottom={2} />
{address}
<Divider marginTop={2} marginBottom={2} />
{timestamp}
</Box>
);
}
Loading…
Cancel
Save