mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-04-20 16:06:00 -04:00
refactor
This commit is contained in:
parent
94484c390f
commit
d3b2b5b2e8
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -11777,6 +11777,7 @@ dependencies = [
|
||||
"tauri-plugin-store",
|
||||
"tauri-plugin-updater",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4,7 +4,7 @@ import { useAppSelector } from '../../../store/hooks'; // Adjust path
|
||||
import { ConfirmationRequestPayload } from '../../../store/features/rpcSlice'; // Adjust path
|
||||
import {
|
||||
Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, Box, LinearProgress
|
||||
} from '@material-ui/core'; // Assuming Material-UI is used
|
||||
} from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
@ -1,16 +1,152 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||
import { SatsAmount } from "renderer/components/other/Units";
|
||||
import { SatsAmount, MoneroAmount, PiconeroAmount, MoneroSatsExchangeRate, MoneroBitcoinExchangeRateFromAmounts } from "renderer/components/other/Units";
|
||||
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
|
||||
import {
|
||||
Box, Button, Typography, LinearProgress, Divider,
|
||||
CircularProgress
|
||||
} from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { ConfirmationRequestPayload } from 'store/features/rpcSlice';
|
||||
import { useAppSelector } from 'store/hooks';
|
||||
import PromiseInvokeButton from 'renderer/components/PromiseInvokeButton';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
confirmationBox: {
|
||||
width: '100%',
|
||||
},
|
||||
timerProgress: {
|
||||
marginTop: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
actions: {
|
||||
marginTop: theme.spacing(2),
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
gap: theme.spacing(2),
|
||||
},
|
||||
valueHighlight: {
|
||||
fontWeight: 'bold',
|
||||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
timerContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: theme.spacing(2),
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
// Helper to find the relevant confirmation request
|
||||
const findPreBtcLockRequest = (confirmations: { [key: string]: ConfirmationRequestPayload }): ConfirmationRequestPayload | undefined => {
|
||||
return Object.values(confirmations).find(req => req.details.type === 'PreBtcLock');
|
||||
};
|
||||
|
||||
export default function SwapSetupInflightPage({
|
||||
btc_lock_amount,
|
||||
btc_tx_lock_fee,
|
||||
}: TauriSwapProgressEventContent<"SwapSetupInflight">) {
|
||||
const classes = useStyles();
|
||||
const pendingConfirmations = useAppSelector((state) => state.rpc.state.pendingConfirmations);
|
||||
const request = findPreBtcLockRequest(pendingConfirmations);
|
||||
|
||||
const [timeLeft, setTimeLeft] = useState<number | null>(null);
|
||||
const [progress, setProgress] = useState(100);
|
||||
|
||||
// Timer effect
|
||||
useEffect(() => {
|
||||
if (request) {
|
||||
setTimeLeft(request.timeout_secs);
|
||||
setProgress(100);
|
||||
const interval = setInterval(() => {
|
||||
setTimeLeft((prevTime) => {
|
||||
if (prevTime === null || prevTime <= 1) {
|
||||
clearInterval(interval);
|
||||
return 0;
|
||||
}
|
||||
const newTime = prevTime - 1;
|
||||
if(request) {
|
||||
setProgress((newTime / request.timeout_secs) * 100);
|
||||
} else {
|
||||
setProgress(0); // Or handle error/reset state
|
||||
clearInterval(interval);
|
||||
}
|
||||
return newTime;
|
||||
});
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
} else {
|
||||
setTimeLeft(null);
|
||||
setProgress(100);
|
||||
}
|
||||
}, [request]);
|
||||
|
||||
if (request) {
|
||||
const {btc_lock_amount, btc_network_fee, xmr_receive_amount} = request.details.content;
|
||||
|
||||
return (
|
||||
<Box className={classes.confirmationBox}>
|
||||
<Typography variant="h6" gutterBottom>Confirm Swap Details</Typography>
|
||||
<Divider />
|
||||
<Box mt={2} mb={2} textAlign="left">
|
||||
<Typography gutterBottom>
|
||||
Please review and confirm the swap amounts below before locking your Bitcoin.
|
||||
<br />
|
||||
You lock <SatsAmount amount={btc_lock_amount} />
|
||||
<br />
|
||||
You pay <SatsAmount amount={btc_network_fee} /> in network fees
|
||||
<br />
|
||||
You receive <PiconeroAmount amount={xmr_receive_amount} />
|
||||
</Typography>
|
||||
<Typography>
|
||||
Exchange rate: <MoneroBitcoinExchangeRateFromAmounts displayMarkup satsAmount={btc_lock_amount} piconeroAmount={xmr_receive_amount} />
|
||||
</Typography>
|
||||
|
||||
{timeLeft !== null && (
|
||||
<Box className={classes.timerContainer}>
|
||||
<CircularProgress variant="determinate" value={progress} size={24} />
|
||||
<Typography variant="body2">Time remaining: {timeLeft}s</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box className={classes.actions}>
|
||||
<PromiseInvokeButton
|
||||
variant="outlined"
|
||||
disabled={timeLeft === 0 || !request}
|
||||
onInvoke={() =>
|
||||
invoke('deny_confirmation', { requestId: request.request_id })
|
||||
}
|
||||
displayErrorSnackbar={true}
|
||||
requiresContext={true} // Assuming context is needed for the command
|
||||
>
|
||||
Deny
|
||||
</PromiseInvokeButton>
|
||||
|
||||
<PromiseInvokeButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={timeLeft === 0 || !request}
|
||||
onInvoke={ () =>
|
||||
invoke('accept_confirmation', { requestId: request.request_id })
|
||||
}
|
||||
displayErrorSnackbar={true}
|
||||
requiresContext={true} // Assuming context is needed for the command
|
||||
>
|
||||
Accept
|
||||
</PromiseInvokeButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CircularProgressWithSubtitle
|
||||
description={
|
||||
<>
|
||||
Starting swap with maker to lock <SatsAmount amount={btc_lock_amount} />
|
||||
Negotiating with maker to swap <SatsAmount amount={btc_lock_amount} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Box, makeStyles, Typography } from "@material-ui/core";
|
||||
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import BitcoinIcon from "../../../../icons/BitcoinIcon";
|
||||
import { MoneroSatsExchangeRate, SatsAmount } from "../../../../other/Units";
|
||||
import DepositAddressInfoBox from "../../DepositAddressInfoBox";
|
||||
@ -57,18 +56,17 @@ export default function WaitingForBtcDepositPage({
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
All Bitcoin sent to this this address will converted into
|
||||
Monero at an exchance rate of{" "}
|
||||
Bitcoin sent to this this address will be converted into
|
||||
Monero at an exchange rate of{" ≈ "}
|
||||
<MoneroSatsExchangeRate rate={quote.price} displayMarkup={true} />
|
||||
</li>
|
||||
<li>
|
||||
The network fee of{" "}
|
||||
The Network fee of{" ≈ "}
|
||||
<SatsAmount amount={min_bitcoin_lock_tx_fee} /> will
|
||||
automatically be deducted from the deposited coins
|
||||
</li>
|
||||
<li>
|
||||
The swap will start automatically as soon as the minimum
|
||||
amount is deposited.
|
||||
After the deposit is detected, you'll get to confirm the exact details before your funds are locked
|
||||
</li>
|
||||
<li>
|
||||
<DepositAmountHelper
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
confirmationResolved,
|
||||
ConfirmationRequestPayload,
|
||||
} from "../../../../store/features/rpcSlice";
|
||||
import ConfirmationModal from "../../modal/ConfirmationModal";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
outer: {
|
||||
@ -66,7 +65,6 @@ export default function SwapPage() {
|
||||
<Box className={classes.outer}>
|
||||
<ApiAlertsBox />
|
||||
<SwapWidget />
|
||||
<ConfirmationModal />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -20,11 +20,12 @@ import logger from "utils/logger";
|
||||
// interface PreBtcLockConfirmationData { ... }
|
||||
// type ConfirmationRequestData = ...
|
||||
|
||||
// Define the payload structure correctly, matching tauriModel.ts
|
||||
// This interface represents the actual payload received from the Tauri event `confirmation_request`
|
||||
// It includes the request_id, timeout, and the flattened generated type
|
||||
export interface ConfirmationRequestPayload extends ConfirmationRequestType {
|
||||
export interface ConfirmationRequestPayload {
|
||||
request_id: string;
|
||||
timeout_secs: number;
|
||||
details: ConfirmationRequestType; // The enum type is nested under details
|
||||
}
|
||||
|
||||
// --- End Refactored Confirmation Types ---
|
||||
@ -160,6 +161,7 @@ export const rpcSlice = createSlice({
|
||||
};
|
||||
},
|
||||
confirmationRequested(slice, action: PayloadAction<ConfirmationRequestPayload>) {
|
||||
console.log("received confirmation request", action.payload);
|
||||
slice.state.pendingConfirmations[action.payload.request_id] = action.payload;
|
||||
},
|
||||
confirmationResolved(slice, action: PayloadAction<{ requestId: string }>) {
|
||||
|
@ -28,6 +28,7 @@ tauri-plugin-shell = "^2.0.0"
|
||||
tauri-plugin-store = "^2.0.0"
|
||||
tauri-plugin-updater = "^2.1.0"
|
||||
tracing = "0.1"
|
||||
uuid = "1.16.0"
|
||||
|
||||
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||
tauri-plugin-cli = "^2.0.0"
|
||||
|
@ -194,7 +194,6 @@ pub struct Context {
|
||||
}
|
||||
|
||||
/// A conveniant builder struct for [`Context`].
|
||||
#[derive(Debug)]
|
||||
#[must_use = "ContextBuilder must be built to be useful"]
|
||||
pub struct ContextBuilder {
|
||||
monero: Option<Monero>,
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::bitcoin;
|
||||
use crate::{bitcoin::ExpiredTimelocks, monero, network::quote::BidQuote};
|
||||
use anyhow::{anyhow, Result};
|
||||
use bitcoin::Txid;
|
||||
@ -22,11 +23,28 @@ const CONTEXT_INIT_PROGRESS_EVENT_NAME: &str = "context-init-progress-update";
|
||||
const BALANCE_CHANGE_EVENT_NAME: &str = "balance-change";
|
||||
const BACKGROUND_REFUND_EVENT_NAME: &str = "background-refund";
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PreBtcLockDetails {
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
pub btc_lock_amount: bitcoin::Amount,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
pub btc_network_fee: bitcoin::Amount,
|
||||
#[typeshare(serialized_as = "number")]
|
||||
pub xmr_receive_amount: monero::Amount,
|
||||
#[typeshare(serialized_as = "string")]
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
pub enum ConfirmationRequestType {
|
||||
PreBtcLock { state2_json: String },
|
||||
/// Request confirmation before locking Bitcoin.
|
||||
/// Contains specific details for review.
|
||||
PreBtcLock(PreBtcLockDetails),
|
||||
}
|
||||
|
||||
struct PendingConfirmation {
|
||||
@ -87,7 +105,6 @@ impl TauriHandle {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Confirmation Methods ---
|
||||
pub async fn request_confirmation(
|
||||
&self,
|
||||
request_type: ConfirmationRequestType,
|
||||
@ -95,10 +112,8 @@ impl TauriHandle {
|
||||
) -> Result<bool> {
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
{
|
||||
// If Tauri feature is not enabled, we cannot show UI.
|
||||
// Decide behavior: maybe auto-deny?
|
||||
tracing::warn!("Confirmation requested but Tauri feature not enabled. Auto-denying.");
|
||||
return Ok(false);
|
||||
// We want the CLI to be non-interactive. Therefore, we accept by default if no TauriHandle is available
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
@ -201,6 +216,12 @@ impl TauriHandle {
|
||||
}
|
||||
|
||||
pub trait TauriEmitter {
|
||||
async fn request_confirmation(
|
||||
&self,
|
||||
request_type: ConfirmationRequestType,
|
||||
timeout_secs: u64,
|
||||
) -> Result<bool>;
|
||||
|
||||
fn emit_tauri_event<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()>;
|
||||
|
||||
fn emit_swap_progress_event(&self, swap_id: Uuid, event: TauriSwapProgressEvent) {
|
||||
@ -252,6 +273,14 @@ pub trait TauriEmitter {
|
||||
}
|
||||
|
||||
impl TauriEmitter for TauriHandle {
|
||||
async fn request_confirmation(
|
||||
&self,
|
||||
request_type: ConfirmationRequestType,
|
||||
timeout_secs: u64,
|
||||
) -> Result<bool> {
|
||||
self.request_confirmation(request_type, timeout_secs).await
|
||||
}
|
||||
|
||||
fn emit_tauri_event<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
|
||||
self.emit_tauri_event(event, payload)
|
||||
}
|
||||
@ -261,9 +290,24 @@ impl TauriEmitter for Option<TauriHandle> {
|
||||
fn emit_tauri_event<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
|
||||
match self {
|
||||
Some(tauri) => tauri.emit_tauri_event(event, payload),
|
||||
|
||||
// If no TauriHandle is available, we just ignore the event and pretend as if it was emitted
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_confirmation(
|
||||
&self,
|
||||
request_type: ConfirmationRequestType,
|
||||
timeout_secs: u64,
|
||||
) -> Result<bool> {
|
||||
match self {
|
||||
Some(tauri) => tauri.request_confirmation(request_type, timeout_secs).await,
|
||||
|
||||
// We want the CLI to be non-interactive. Therefore, we accept by default if no TauriHandle is available
|
||||
None => Ok(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
|
@ -1,7 +1,9 @@
|
||||
use crate::bitcoin::wallet::ScriptStatus;
|
||||
use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund};
|
||||
use crate::cli::api::tauri_bindings::ConfirmationRequestType;
|
||||
use crate::cli::api::tauri_bindings::{TauriEmitter, TauriHandle, TauriSwapProgressEvent};
|
||||
use crate::cli::api::tauri_bindings::{
|
||||
PreBtcLockDetails, TauriEmitter, TauriHandle, TauriSwapProgressEvent,
|
||||
};
|
||||
use crate::cli::api::Context;
|
||||
use crate::cli::EventLoopHandle;
|
||||
use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled, Rejected};
|
||||
@ -135,8 +137,6 @@ async fn next_state(
|
||||
|
||||
tracing::info!(%swap_id, "Starting new swap");
|
||||
|
||||
// Ensure confirmation logic is NOT here
|
||||
|
||||
BobState::SwapSetupCompleted(state2)
|
||||
}
|
||||
BobState::SwapSetupCompleted(state2) => {
|
||||
@ -151,43 +151,7 @@ async fn next_state(
|
||||
// which can lead to the wallet not detect the transaction.
|
||||
let monero_wallet_restore_blockheight = monero_wallet.block_height().await?;
|
||||
|
||||
// --- Start Confirmation Logic ---
|
||||
const CONFIRMATION_TIMEOUT_SECS: u64 = 120;
|
||||
let state2_json = serde_json::to_string(&state2)
|
||||
.context("Failed to serialize State2 for confirmation")?;
|
||||
let request_type = ConfirmationRequestType::PreBtcLock { state2_json };
|
||||
|
||||
tracing::info!("Requesting user confirmation before locking BTC...");
|
||||
// Use the event_emitter Option<TauriHandle>
|
||||
if let Some(handle) = &event_emitter {
|
||||
let confirmation_result = handle
|
||||
.request_confirmation(request_type, CONFIRMATION_TIMEOUT_SECS)
|
||||
.await;
|
||||
|
||||
match confirmation_result {
|
||||
Ok(true) => {
|
||||
tracing::info!("User accepted BTC lock confirmation.");
|
||||
// Proceed
|
||||
}
|
||||
Ok(false) => {
|
||||
tracing::warn!("User denied or timed out on BTC lock confirmation.");
|
||||
return Err(anyhow!(
|
||||
"Swap aborted by user/timeout before locking Bitcoin."
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error during confirmation request: {}", e);
|
||||
return Err(e.context("Failed to get user confirmation for BTC lock"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle case where no UI is available
|
||||
tracing::warn!("Confirmation required, but no UI handle available. Aborting swap.");
|
||||
return Err(anyhow!(
|
||||
"Confirmation required, but no UI handle available."
|
||||
));
|
||||
}
|
||||
// --- End Confirmation Logic ---
|
||||
let xmr_receive_amount = state2.xmr;
|
||||
|
||||
// Alice and Bob have exchanged info
|
||||
// Sign the Bitcoin lock transaction
|
||||
@ -197,12 +161,51 @@ async fn next_state(
|
||||
.await
|
||||
.context("Failed to sign Bitcoin lock transaction")?;
|
||||
|
||||
// Publish the signed Bitcoin lock transaction
|
||||
let (..) = bitcoin_wallet.broadcast(signed_tx, "lock").await?;
|
||||
let btc_network_fee = tx_lock.fee().context("Failed to get fee")?;
|
||||
let btc_lock_amount = bitcoin::Amount::from_sat(
|
||||
signed_tx
|
||||
.output
|
||||
.get(0)
|
||||
.context("Failed to get lock amount")?
|
||||
.value,
|
||||
);
|
||||
|
||||
BobState::BtcLocked {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
const CONFIRMATION_TIMEOUT_SECS: u64 = 120;
|
||||
|
||||
let request = ConfirmationRequestType::PreBtcLock(PreBtcLockDetails {
|
||||
btc_lock_amount,
|
||||
btc_network_fee,
|
||||
xmr_receive_amount,
|
||||
swap_id,
|
||||
});
|
||||
|
||||
// We request confirmation before locking the Bitcoin, as the exchange rate determined at this step might be different from the
|
||||
// we previously received from Alice.
|
||||
let confirmation_result = event_emitter
|
||||
.request_confirmation(request, CONFIRMATION_TIMEOUT_SECS)
|
||||
.await;
|
||||
|
||||
match confirmation_result {
|
||||
Ok(true) => {
|
||||
tracing::info!("User accepted swap details");
|
||||
|
||||
// Publish the signed Bitcoin lock transaction
|
||||
let (..) = bitcoin_wallet.broadcast(signed_tx, "lock").await?;
|
||||
|
||||
BobState::BtcLocked {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
}
|
||||
}
|
||||
Ok(false) => {
|
||||
tracing::warn!("User denied or timed out on swap details confirmation");
|
||||
|
||||
BobState::SafelyAborted
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error during confirmation request: {}", e);
|
||||
return Err(e.context("Failed to get user confirmation for swap details"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Bob has locked Bitcoin
|
||||
|
Loading…
x
Reference in New Issue
Block a user