Browse Source

Merge pull request #85 from comit-network/testnet-funding

Wallet updates in the UI 🎉
no-contract-setup-message
Daniel Karzel 3 years ago
committed by GitHub
parent
commit
eadc5ea42c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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",
"sqlx",
"tempfile",
"thiserror",
"tokio",
"tokio-util",
"uuid",

1
daemon/Cargo.toml

@ -20,6 +20,7 @@ serde_json = "1"
serde_with = { version = "1", features = ["macros"] }
sha2 = "0.9"
sqlx = { version = "0.5", features = ["offline"] }
thiserror = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "net"] }
tokio-util = { version = "0.6", features = ["codec"] }
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::wallet::Wallet;
use anyhow::Result;
use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1};
use bdk::bitcoin::{Amount, Network};
use bdk::bitcoin::Network;
use clap::Clap;
use model::cfd::{Cfd, Order};
use model::WalletInfo;
use rocket::fairing::AdHoc;
use rocket_db_pools::Database;
use std::path::PathBuf;
use std::time::Duration;
use tokio::sync::{mpsc, watch};
mod db;
@ -72,12 +75,13 @@ async fn main() -> Result<()> {
ext_priv_key,
)
.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 (cfd_feed_sender, cfd_feed_receiver) = watch::channel::<Vec<Cfd>>(vec![]);
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()
.merge(("databases.maker.url", data_dir.join("maker.sqlite")))
@ -91,7 +95,7 @@ async fn main() -> Result<()> {
rocket::custom(figment)
.manage(cfd_feed_receiver)
.manage(order_feed_receiver)
.manage(balance_feed_receiver)
.manage(wallet_feed_receiver)
.attach(Db::init())
.attach(AdHoc::try_on_ignite(
"SQL migrations",
@ -123,6 +127,7 @@ async fn main() -> Result<()> {
connections_actor_inbox_sender,
cfd_feed_sender,
order_feed_sender,
wallet_feed_sender,
);
let connections_actor = maker_inc_connections_actor::new(
listener,
@ -130,6 +135,18 @@ async fn main() -> Result<()> {
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(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::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::wire::SetupMsg;
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)]
#[derive(Debug)]
pub enum Command {
SyncWallet,
TakeOrder {
taker_id: TakerId,
order_id: OrderId,
@ -36,6 +37,7 @@ pub fn new(
takers: mpsc::UnboundedSender<maker_inc_connections_actor::Command>,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>,
order_feed_sender: watch::Sender<Option<Order>>,
wallet_feed_sender: watch::Sender<WalletInfo>,
) -> (
impl Future<Output = ()>,
mpsc::UnboundedSender<maker_cfd_actor::Command>,
@ -57,6 +59,10 @@ pub fn new(
while let Some(message) = receiver.recv().await {
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 {
taker_id,
order_id,

9
daemon/src/model.rs

@ -5,6 +5,8 @@ use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use bdk::bitcoin::{Address, Amount};
use std::time::SystemTime;
use uuid::Uuid;
pub mod cfd;
@ -83,3 +85,10 @@ impl Display for TakerId {
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::model::cfd::{Cfd, Order, Origin};
use crate::model::Usd;
use crate::model::{Usd, WalletInfo};
use crate::to_sse_event::ToSseEvent;
use anyhow::Result;
use bdk::bitcoin::Amount;
use rocket::response::status;
use rocket::response::stream::EventStream;
use rocket::serde::json::Json;
@ -16,15 +16,15 @@ use tokio::sync::{mpsc, watch};
pub async fn maker_feed(
rx_cfds: &State<watch::Receiver<Vec<Cfd>>>,
rx_order: &State<watch::Receiver<Option<Order>>>,
rx_balance: &State<watch::Receiver<Amount>>,
rx_wallet: &State<watch::Receiver<WalletInfo>>,
) -> EventStream![] {
let mut rx_cfds = rx_cfds.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! {
let balance = rx_balance.borrow().clone();
yield balance.to_sse_event();
let wallet_info = rx_wallet.borrow().clone();
yield wallet_info.to_sse_event();
let order = rx_order.borrow().clone();
yield order.to_sse_event();
@ -34,9 +34,9 @@ pub async fn maker_feed(
loop{
select! {
Ok(()) = rx_balance.changed() => {
let balance = rx_balance.borrow().clone();
yield balance.to_sse_event();
Ok(()) = rx_wallet.changed() => {
let wallet_info = rx_wallet.borrow().clone();
yield wallet_info.to_sse_event();
},
Ok(()) = rx_order.changed() => {
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::{Leverage, Usd};
use crate::model::{Leverage, Usd, WalletInfo};
use crate::taker_cfd_actor;
use crate::to_sse_event::ToSseEvent;
use bdk::bitcoin::Amount;
@ -15,15 +15,15 @@ use tokio::sync::{mpsc, watch};
pub async fn feed(
rx_cfds: &State<watch::Receiver<Vec<Cfd>>>,
rx_order: &State<watch::Receiver<Option<Order>>>,
rx_balance: &State<watch::Receiver<Amount>>,
rx_wallet: &State<watch::Receiver<WalletInfo>>,
) -> EventStream![] {
let mut rx_cfds = rx_cfds.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! {
let balance = rx_balance.borrow().clone();
yield balance.to_sse_event();
let wallet_info = rx_wallet.borrow().clone();
yield wallet_info.to_sse_event();
let order = rx_order.borrow().clone();
yield order.to_sse_event();
@ -33,9 +33,9 @@ pub async fn feed(
loop{
select! {
Ok(()) = rx_balance.changed() => {
let balance = rx_balance.borrow().clone();
yield balance.to_sse_event();
Ok(()) = rx_wallet.changed() => {
let wallet_info = rx_wallet.borrow().clone();
yield wallet_info.to_sse_event();
},
Ok(()) = rx_order.changed() => {
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 anyhow::Result;
use bdk::bitcoin::secp256k1::{schnorrsig, SECP256K1};
use bdk::bitcoin::{Amount, Network};
use bdk::bitcoin::Network;
use clap::Clap;
use model::cfd::{Cfd, Order};
use rocket::fairing::AdHoc;
@ -77,12 +79,13 @@ async fn main() -> Result<()> {
ext_priv_key,
)
.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 (cfd_feed_sender, cfd_feed_receiver) = watch::channel::<Vec<Cfd>>(vec![]);
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 socket = tokio::net::TcpSocket::new_v4()?;
@ -104,7 +107,7 @@ async fn main() -> Result<()> {
rocket::custom(figment)
.manage(cfd_feed_receiver)
.manage(order_feed_receiver)
.manage(balance_feed_receiver)
.manage(wallet_feed_receiver)
.attach(Db::init())
.attach(AdHoc::try_on_ignite(
"SQL migrations",
@ -135,10 +138,23 @@ async fn main() -> Result<()> {
cfd_feed_sender,
order_feed_sender,
out_maker_actor_inbox,
wallet_feed_sender,
);
let inc_maker_messages_actor =
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(inc_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,
};
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::wire::SetupMsg;
use crate::{setup_contract_actor, wire};
@ -16,6 +16,7 @@ use tokio::sync::{mpsc, watch};
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Command {
SyncWallet,
TakeOrder { order_id: OrderId, quantity: Usd },
NewOrder(Option<Order>),
OrderAccepted(OrderId),
@ -30,6 +31,7 @@ pub fn new(
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>,
order_feed_actor_inbox: watch::Sender<Option<Order>>,
out_msg_maker_inbox: mpsc::UnboundedSender<wire::TakerToMaker>,
wallet_feed_sender: watch::Sender<WalletInfo>,
) -> (impl Future<Output = ()>, mpsc::UnboundedSender<Command>) {
let (sender, mut receiver) = mpsc::unbounded_channel();
let mut current_contract_setup = None;
@ -46,6 +48,10 @@ pub fn new(
while let Some(message) = receiver.recv().await {
match message {
Command::SyncWallet => {
let wallet_info = wallet.sync().unwrap();
wallet_feed_sender.send(wallet_info).unwrap();
}
Command::TakeOrder { order_id, quantity } => {
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 state: String,
pub state_transition_unix_timestamp: u64,
pub state_transition_timestamp: u64,
}
#[derive(Debug, Clone, Serialize)]
@ -44,7 +44,7 @@ pub struct CfdOrder {
pub leverage: Leverage,
pub liquidation_price: Usd,
pub creation_unix_timestamp: u64,
pub creation_timestamp: u64,
pub term_in_secs: u64,
}
@ -73,7 +73,7 @@ impl ToSseEvent for Vec<model::cfd::Cfd> {
profit_btc,
profit_usd,
state: cfd.state.to_string(),
state_transition_unix_timestamp: cfd
state_transition_timestamp: cfd
.state
.get_transition_timestamp()
.duration_since(UNIX_EPOCH)
@ -102,7 +102,7 @@ impl ToSseEvent for Option<model::cfd::Order> {
max_quantity: order.max_quantity,
leverage: order.leverage,
liquidation_price: order.liquidation_price,
creation_unix_timestamp: order
creation_timestamp: order
.creation_timestamp
.duration_since(UNIX_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 {
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 bdk::bitcoin::util::bip32::ExtendedPrivKey;
use bdk::bitcoin::{Amount, PublicKey};
use bdk::blockchain::{ElectrumBlockchain, NoopProgress};
use bdk::wallet::AddressIndex;
use bdk::KeychainKind;
use cfd_protocol::{PartyParams, WalletExt};
use std::path::Path;
use std::time::SystemTime;
const SLED_TREE_NAME: &str = "wallet";
@ -32,10 +35,6 @@ impl Wallet {
ElectrumBlockchain::from(client),
)?;
wallet
.sync(NoopProgress, None)
.context("Failed to sync the wallet")?; // TODO: Use LogProgress once we have logging.
Ok(Self { wallet })
}
@ -46,4 +45,20 @@ impl Wallet {
) -> Result<PartyParams> {
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 NavLink from "./components/NavLink";
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 {
price: number;
@ -47,7 +48,7 @@ export default function App() {
console.log(cfds);
const balance = useLatestEvent<number>(source, "balance");
const walletInfo = useLatestEvent<WalletInfo>(source, "wallet");
const toast = useToast();
let [minQuantity, setMinQuantity] = useState<string>("100");
@ -111,12 +112,9 @@ export default function App() {
</Box>
</VStack>
</Flex>
<Flex width={"50%"} marginLeft={5}>
<VStack spacing={5} shadow={"md"} padding={5} align={"stretch"}>
<HStack>
<Text align={"left"}>Your balance:</Text>
<Text>{balance}</Text>
</HStack>
<Flex width={"50%"} marginLeft={5} direction={"column"}>
<Wallet walletInfo={walletInfo} />
<VStack spacing={5} shadow={"md"} padding={5} align={"stretch"} height={"100%"}>
<HStack>
<Text align={"left"}>Current Price:</Text>
<Text>{49000}</Text>

10
frontend/src/Taker.tsx

@ -9,7 +9,8 @@ import CfdTile from "./components/CfdTile";
import CurrencyInputField from "./components/CurrencyInputField";
import useLatestEvent from "./components/Hooks";
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 {
order_id: string;
@ -49,7 +50,7 @@ export default function App() {
const cfds = useLatestEvent<Cfd[]>(source, "cfds");
const order = useLatestEvent<Order>(source, "order");
const balance = useLatestEvent<number>(source, "balance");
const walletInfo = useLatestEvent<WalletInfo>(source, "wallet");
const toast = useToast();
let [quantity, setQuantity] = useState("0");
@ -132,10 +133,7 @@ export default function App() {
</Flex>
<Flex width={"50%"} marginLeft={5}>
<VStack spacing={5} shadow={"md"} padding={5} align={"stretch"}>
<HStack>
<Text align={"left"}>Your balance:</Text>
<Text>{balance}</Text>
</HStack>
<Wallet walletInfo={walletInfo} />
<HStack>
{/*TODO: Do we need this? does it make sense to only display the price from the order?*/}
<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 React from "react";
import { Cfd } from "./Types";
import { Cfd, unixTimestampToDate } from "./Types";
interface CfdTileProps {
index: number;
@ -46,7 +46,7 @@ export default function CfdTile(
<Text>Open since</Text>
{/* TODO: Format date in a more compact way */}
<Text>
{(new Date(cfd.state_transition_unix_timestamp * 1000).toString())}
{unixTimestampToDate(cfd.state_transition_timestamp).toString()}
</Text>
<Text>Status</Text>
<Text>{cfd.state}</Text>

14
frontend/src/components/Types.tsx

@ -7,7 +7,7 @@ export interface Order {
max_quantity: number;
leverage: number;
liquidation_price: number;
creation_unix_timestamp: number;
creation_timestamp: number;
term_in_secs: number;
}
@ -28,5 +28,15 @@ export interface Cfd {
profit_usd: number;
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