Skip to content
This repository has been archived by the owner on Sep 12, 2023. It is now read-only.

Commit

Permalink
Merge #2172
Browse files Browse the repository at this point in the history
2172: Minor wallet improvements r=bonomat a=bonomat

Fixes #2158 

Minor UI change: 

Maker UI: 
<img width="486" alt="image" src="https://user-images.githubusercontent.com/224613/172093037-b1dfc492-9789-4524-a781-afc77c9de069.png">

Taker UI: 

<img width="750" alt="image" src="https://user-images.githubusercontent.com/224613/172093106-79a31577-4d09-4e33-9dbb-e2f3a51d3a08.png">


While syncing: 

<img width="803" alt="image" src="https://user-images.githubusercontent.com/224613/172093127-a58e6f27-44f9-499d-866b-55dcfa2243f6.png">



Co-authored-by: Philipp Hoenisch <[email protected]>
  • Loading branch information
bors[bot] and bonomat authored Jun 7, 2022
2 parents e626da2 + 867915a commit 51eb120
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Allow maker to provide extended private key as argument when starting. This key will be used to derive the internal wallet according to (Bip84)[https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki]
- Added new HTTP endpoint to manually trigger a wallet sync under `/api/sync`

### Changed

Expand Down
7 changes: 7 additions & 0 deletions daemon-tests/src/mocks/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ impl WalletActor {
async fn handle(&mut self, msg: wallet::Withdraw) -> Result<Txid> {
self.mock.lock().await.withdraw(msg)
}
async fn handle(&mut self, msg: wallet::Sync) {
self.mock.lock().await.sync(msg)
}
}

#[automock]
Expand All @@ -69,6 +72,10 @@ pub trait Wallet {
fn withdraw(&mut self, _msg: wallet::Withdraw) -> Result<Txid> {
unreachable!("mockall will reimplement this method")
}

fn sync(&mut self, _msg: wallet::Sync) {
unreachable!("mockall will reimplement this method")
}
}

#[allow(dead_code)]
Expand Down
6 changes: 6 additions & 0 deletions daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ where
W: Handler<wallet::BuildPartyParams>
+ Handler<wallet::Sign>
+ Handler<wallet::Withdraw>
+ Handler<wallet::Sync>
+ Actor<Stop = ()>,
P: Handler<xtra_bitmex_price_feed::LatestQuote> + Actor<Stop = xtra_bitmex_price_feed::Error>,
{
Expand Down Expand Up @@ -350,6 +351,11 @@ where
})
.await?
}

pub async fn sync_wallet(&self) -> Result<()> {
self.wallet_actor.send(wallet::Sync).await?;
Ok(())
}
}

#[derive(Debug, Copy, Clone, Display)]
Expand Down
9 changes: 6 additions & 3 deletions daemon/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use tokio_tasks::Tasks;
use xtra_productivity::xtra_productivity;
use xtras::SendInterval;

const SYNC_INTERVAL: Duration = Duration::from_secs(60);
const SYNC_INTERVAL: Duration = Duration::from_secs(3 * 60);

static BALANCE_GAUGE: conquer_once::Lazy<prometheus::Gauge> = conquer_once::Lazy::new(|| {
prometheus::register_gauge!(
Expand Down Expand Up @@ -136,6 +136,8 @@ impl Actor<ElectrumBlockchain> {
impl Actor<ElectrumBlockchain> {
fn sync_internal(&mut self) -> Result<WalletInfo> {
let now = Instant::now();
tracing::trace!(target : "wallet", "Wallet sync started");

self.wallet
.sync(&self.blockchain_client, SyncOptions::default())
.context("Failed to sync wallet")?;
Expand Down Expand Up @@ -306,8 +308,9 @@ pub struct BuildPartyParams {
pub fee_rate: TxFeeRate,
}

/// Private message to trigger a sync.
struct Sync;
/// Message to trigger a sync.
#[derive(Clone, Copy)]
pub struct Sync;

pub struct Sign {
pub psbt: PartiallySignedTransaction,
Expand Down
19 changes: 17 additions & 2 deletions maker-frontend/src/MakerApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import useLatestEvent from "./components/Hooks";
import OrderTile from "./components/OrderTile";
import { Cfd, intoCfd, MakerOffer, PriceInfo, StateGroupKey, WalletInfo } from "./components/Types";
import Wallet from "./components/Wallet";
import { CfdNewOfferParamsPayload, putCfdNewOfferParamsRequest } from "./MakerClient";
import { CfdNewOfferParamsPayload, putCfdNewOfferParamsRequest, triggerWalletSync } from "./MakerClient";

const SPREAD_ASK = 1.01;
const SPREAD_BID = 0.99;
Expand Down Expand Up @@ -82,6 +82,15 @@ export default function App() {
}
},
});
let { run: syncWallet, isLoading: isSyncingWallet } = useAsync({
deferFn: async () => {
try {
await triggerWalletSync();
} catch (e) {
createErrorToast(toast, e);
}
},
});

const pendingOrders = cfds.filter((value) => value.state.getGroup() === StateGroupKey.PENDING_ORDER);
const pendingSettlements = cfds.filter((value) => value.state.getGroup() === StateGroupKey.PENDING_SETTLEMENT);
Expand All @@ -94,7 +103,13 @@ export default function App() {
<Container maxWidth="120ch" marginTop="1rem">
<HStack spacing={5}>
<VStack>
<Wallet walletInfo={walletInfo} />
<Wallet
walletInfo={walletInfo}
syncWallet={() => {
syncWallet();
}}
isSyncingWallet={isSyncingWallet}
/>

<Grid
gridTemplateColumns="max-content auto"
Expand Down
13 changes: 13 additions & 0 deletions maker-frontend/src/MakerClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@ export async function putCfdNewOfferParamsRequest(payload: CfdNewOfferParamsPayl
throw new HttpError(resp);
}
}

export async function triggerWalletSync() {
let res = await fetch(`/api/sync`, {
method: "PUT",
credentials: "include",
});

if (!res.status.toString().startsWith("2")) {
console.log("Status: " + res.status + ", " + res.statusText);
const resp = await res.json();
throw new HttpError(resp);
}
}
16 changes: 14 additions & 2 deletions maker-frontend/src/components/Wallet.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { CheckIcon, CopyIcon } from "@chakra-ui/icons";
import { Box, Center, Divider, HStack, IconButton, Skeleton, Text, useClipboard } from "@chakra-ui/react";
import { CheckIcon, CopyIcon, RepeatIcon } from "@chakra-ui/icons";
import { Box, Center, Divider, HStack, IconButton, Skeleton, Spacer, Text, useClipboard } from "@chakra-ui/react";
import React from "react";
import Timestamp from "./Timestamp";
import { WalletInfo } from "./Types";

interface WalletProps {
walletInfo: WalletInfo | null;
syncWallet: () => void;
isSyncingWallet: boolean;
}

export default function Wallet(
{
walletInfo,
syncWallet,
isSyncingWallet,
}: WalletProps,
) {
const { hasCopied, onCopy } = useClipboard(walletInfo ? walletInfo.address : "");
Expand All @@ -26,6 +30,14 @@ export default function Wallet(
<Skeleton isLoaded={balance != null}>
<Text>{balance} BTC</Text>
</Skeleton>
<Spacer />
<IconButton
aria-label="Sync Wallet"
disabled={balance == null}
isLoading={isSyncingWallet}
icon={<RepeatIcon />}
onClick={syncWallet}
/>
</HStack>
<Divider marginTop={2} marginBottom={2} />
<Skeleton isLoaded={address != null}>
Expand Down
6 changes: 6 additions & 0 deletions maker/src/actor_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ where
W: Handler<wallet::BuildPartyParams>
+ Handler<wallet::Sign>
+ Handler<wallet::Withdraw>
+ Handler<wallet::Sync>
+ Actor<Stop = ()>,
{
#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -323,4 +324,9 @@ where
})
.await?
}

pub async fn sync_wallet(&self) -> Result<()> {
self.wallet_actor.send(wallet::Sync).await?;
Ok(())
}
}
2 changes: 1 addition & 1 deletion maker/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@ async fn main() -> Result<()> {
routes::put_offer_params,
routes::post_cfd_action,
routes::get_health_check,
routes::post_withdraw_request,
routes::get_cfds,
routes::get_takers,
routes::get_metrics,
routes::put_sync_wallet,
],
)
.register("/api", default_catchers())
Expand Down
47 changes: 9 additions & 38 deletions maker/src/routes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::actor_system::ActorSystem;
use anyhow::Result;
use daemon::bdk;
use daemon::bdk::bitcoin::Network;
use daemon::bdk::blockchain::ElectrumBlockchain;
use daemon::oracle;
use daemon::projection::Cfd;
Expand Down Expand Up @@ -210,45 +208,18 @@ pub fn index<'r>(_paths: PathBuf, _auth: Authenticated) -> impl Responder<'r, 's
Ok::<(ContentType, Cow<[u8]>), Status>((ContentType::HTML, asset.data))
}

#[derive(Debug, Clone, Deserialize)]
pub struct WithdrawRequest {
address: bdk::bitcoin::Address,
#[serde(with = "bdk::bitcoin::util::amount::serde::as_btc")]
amount: bdk::bitcoin::Amount,
fee: f32,
}

#[rocket::post("/withdraw", data = "<withdraw_request>")]
pub async fn post_withdraw_request(
withdraw_request: Json<WithdrawRequest>,
#[rocket::put("/sync")]
pub async fn put_sync_wallet(
maker: &State<Maker>,
network: &State<Network>,
_auth: Authenticated,
) -> Result<String, HttpApiProblem> {
let amount =
(withdraw_request.amount != bdk::bitcoin::Amount::ZERO).then(|| withdraw_request.amount);

let txid = maker
.withdraw(
amount,
withdraw_request.address.clone(),
withdraw_request.fee,
)
.await
.map_err(|e| {
HttpApiProblem::new(StatusCode::INTERNAL_SERVER_ERROR)
.title("Could not proceed with withdraw request")
.detail(format!("{e:#}"))
})?;

let url = match network.inner() {
Network::Bitcoin => format!("https://mempool.space/tx/{txid}"),
Network::Testnet => format!("https://mempool.space/testnet/tx/{txid}"),
Network::Signet => format!("https://mempool.space/signet/tx/{txid}"),
Network::Regtest => txid.to_string(),
};
) -> Result<(), HttpApiProblem> {
maker.sync_wallet().await.map_err(|e| {
HttpApiProblem::new(StatusCode::INTERNAL_SERVER_ERROR)
.title("Could not sync wallet")
.detail(format!("{e:#}"))
})?;

Ok(url)
Ok(())
}

#[rocket::get("/cfds")]
Expand Down
45 changes: 44 additions & 1 deletion taker-frontend/src/components/Wallet.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CheckIcon, CopyIcon } from "@chakra-ui/icons";
import { CheckIcon, CopyIcon, RepeatIcon } from "@chakra-ui/icons";
import {
Box,
Button,
Expand All @@ -18,6 +18,7 @@ import {
NumberInputField,
NumberInputStepper,
Skeleton,
Spacer,
Text,
useClipboard,
useColorModeValue,
Expand All @@ -27,6 +28,7 @@ import {
import { QRCodeCanvas } from "qrcode.react";
import * as React from "react";
import { useState } from "react";
import { useAsync } from "react-async";
import { WalletInfo, WithdrawRequest } from "../types";
import usePostRequest from "../usePostRequest";
import Timestamp from "./Timestamp";
Expand Down Expand Up @@ -62,6 +64,37 @@ export default function Wallet(
});
});

let { run: syncWallet, isLoading: isSyncingWallet } = useAsync({
deferFn: async () => {
try {
let res = await fetch(`/api/sync`, {
method: "PUT",
credentials: "include",
});

if (!res.status.toString().startsWith("2")) {
console.log("Status: " + res.status + ", " + res.statusText);
const resp = await res.json();
toast({
title: "Error: Syncing Wallet",
description: resp.description,
status: "error",
duration: 10000,
isClosable: true,
});
}
} catch (e: any) {
toast({
title: "Error: Syncing Wallet",
description: e.detail,
status: "error",
duration: 10000,
isClosable: true,
});
}
},
});

return (
<Center>
<Box shadow={"lg"} rounded={"md"} marginBottom={5} padding={5} bg={useColorModeValue("white", "gray.700")}>
Expand All @@ -75,6 +108,15 @@ export default function Wallet(
<Skeleton isLoaded={balance != null}>
<Text>{balance} BTC</Text>
</Skeleton>
<Spacer />
<IconButton
aria-label="Sync Wallet"
variant={"outline"}
disabled={balance == null}
isLoading={isSyncingWallet}
icon={<RepeatIcon />}
onClick={syncWallet}
/>
</HStack>
<Divider marginTop={2} marginBottom={2} />
<Skeleton isLoaded={address != null}>
Expand All @@ -96,6 +138,7 @@ export default function Wallet(
</Skeleton>
</HStack>
</Box>
<Spacer />
<Box>
<QRCodeCanvas value={`bitcoin:${address!}`} />
</Box>
Expand Down
1 change: 1 addition & 0 deletions taker/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ async fn main() -> Result<()> {
routes::post_cfd_action,
routes::post_withdraw_request,
routes::get_metrics,
routes::put_sync_wallet,
],
)
.register("/api", default_catchers())
Expand Down
14 changes: 14 additions & 0 deletions taker/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,20 @@ pub async fn get_metrics<'r>(_auth: Authenticated) -> Result<String, HttpApiProb
Ok(metrics)
}

#[rocket::put("/sync")]
pub async fn put_sync_wallet(
taker: &State<Taker>,
_auth: Authenticated,
) -> Result<(), HttpApiProblem> {
taker.sync_wallet().await.map_err(|e| {
HttpApiProblem::new(StatusCode::INTERNAL_SERVER_ERROR)
.title("Could not sync wallet")
.detail(format!("{e:#}"))
})?;

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit 51eb120

Please sign in to comment.