feat(protocol, wallet): Reveal enc sig fast (#357)

* feat(asb, cli): Add safety margin to chosen Bitcoin fee for pre-signed transactions

* feat(gui): Add Context init overlay

* feat(protocol): Reveal enc sig fast (before full 10 confirmations)

* feat(wallet): Use mempool.space as a secondary fee estimation source

* log libp2p crates

* revert useless stuff

* remove unused elements in state machine

* remove redundant diff

* minimize diff

* dont make xmr_lock_tx_target_confirmations optional

* pass target conf in listener callback for monero txs

* refactor

* refactor

* nitpicks

* feat: add migration file for xmr field in state3, state4, state5 and state6

* revert .gitignore

* add monero_double_spend_safe_confirmations to env.rs

* change durations in  SwapStateStepper.tsx

* remove unused helper functions

* use env_config.monero_double_spend_safe_confirmations in state machine

* refactor

* Update src-gui/src/renderer/components/modal/swap/pages/in_progress/WaitingForXmrConfirmationsBeforeRedeemPage.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix label for retry op

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
Mohan 2025-06-30 16:42:53 +02:00 committed by GitHub
parent cc4069ebad
commit 9a04bd5682
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 427 additions and 63 deletions

View file

@ -239,7 +239,7 @@ export default function SwapStatusAlert({
swap: GetSwapInfoResponseExt;
isRunning: boolean;
onlyShowIfUnusualAmountOfTimeHasPassed?: boolean;
}): JSX.Element | null {
}) {
// If the swap is completed, we do not need to display anything
if (!isGetSwapInfoResponseRunningSwap(swap)) {
return null;

View file

@ -83,7 +83,8 @@ function getActiveStep(state: SwapState | null): PathStep | null {
// Step 3: Waiting for XMR redemption
// Bitcoin has been redeemed by Alice, now waiting for us to redeem Monero
case "BtcRedeemed":
case "WaitingForXmrConfirmationsBeforeRedeem":
case "RedeemingMonero":
return [PathType.HAPPY_PATH, 3, isReleased];
// Step 4: Swap completed successfully
@ -162,9 +163,9 @@ function SwapStepper({
const HAPPY_PATH_STEP_LABELS = [
{ label: "Locking your BTC", duration: "~12min" },
{ label: "They lock their XMR", duration: "~18min" },
{ label: "They lock their XMR", duration: "~10min" },
{ label: "They redeem the BTC", duration: "~2min" },
{ label: "Redeeming your XMR", duration: "~2min" },
{ label: "Redeeming your XMR", duration: "~10min" },
];
const UNHAPPY_PATH_STEP_LABELS = [

View file

@ -9,7 +9,7 @@ export type TransactionInfoBoxProps = {
explorerUrlCreator: ((txId: string) => string) | null;
additionalContent: ReactNode;
loading: boolean;
icon: JSX.Element;
icon: ReactNode;
};
export default function TransactionInfoBox({

View file

@ -12,11 +12,12 @@ import XmrRedeemInMempoolPage from "./done/XmrRedeemInMempoolPage";
import ProcessExitedPage from "./exited/ProcessExitedPage";
import BitcoinCancelledPage from "./in_progress/BitcoinCancelledPage";
import BitcoinLockTxInMempoolPage from "./in_progress/BitcoinLockTxInMempoolPage";
import BitcoinRedeemedPage from "./in_progress/BitcoinRedeemedPage";
import RedeemingMoneroPage from "./in_progress/RedeemingMoneroPage";
import CancelTimelockExpiredPage from "./in_progress/CancelTimelockExpiredPage";
import EncryptedSignatureSentPage from "./in_progress/EncryptedSignatureSentPage";
import ReceivedQuotePage from "./in_progress/ReceivedQuotePage";
import SwapSetupInflightPage from "./in_progress/SwapSetupInflightPage";
import WaitingForXmrConfirmationsBeforeRedeemPage from "./in_progress/WaitingForXmrConfirmationsBeforeRedeemPage";
import XmrLockedPage from "./in_progress/XmrLockedPage";
import XmrLockTxInMempoolPage from "./in_progress/XmrLockInMempoolPage";
import InitPage from "./init/InitPage";
@ -62,8 +63,15 @@ export default function SwapStatePage({ state }: { state: SwapState | null }) {
return <XmrLockedPage />;
case "EncryptedSignatureSent":
return <EncryptedSignatureSentPage />;
case "BtcRedeemed":
return <BitcoinRedeemedPage />;
case "RedeemingMonero":
return <RedeemingMoneroPage />;
case "WaitingForXmrConfirmationsBeforeRedeem":
if (state.curr.type === "WaitingForXmrConfirmationsBeforeRedeem") {
return (
<WaitingForXmrConfirmationsBeforeRedeemPage {...state.curr.content} />
);
}
break;
case "XmrRedeemInMempool":
if (state.curr.type === "XmrRedeemInMempool") {
return <XmrRedeemInMempoolPage {...state.curr.content} />;

View file

@ -1,5 +0,0 @@
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export default function BitcoinRedeemedPage() {
return <CircularProgressWithSubtitle description="Redeeming your Monero" />;
}

View file

@ -0,0 +1,7 @@
import CircularProgressWithSubtitle from "../../CircularProgressWithSubtitle";
export default function RedeemingMoneroPage() {
return (
<CircularProgressWithSubtitle description="Preparing to redeem your Monero" />
);
}

View file

@ -481,7 +481,8 @@ const MoneroSecondaryContent = ({
// Arrow animation styling extracted for reuse
const arrowSx = {
fontSize: "3rem",
color: (theme: any) => theme.palette.primary.main,
color: (theme: { palette: { primary: { main: string } } }) =>
theme.palette.primary.main,
animation: "slideArrow 2s infinite",
"@keyframes slideArrow": {
"0%": {

View file

@ -0,0 +1,29 @@
import { Box, DialogContentText } from "@mui/material";
import { TauriSwapProgressEventContent } from "models/tauriModelExt";
import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
export default function WaitingForXmrConfirmationsBeforeRedeemPage({
xmr_lock_txid,
xmr_lock_tx_confirmations,
xmr_lock_tx_target_confirmations,
}: TauriSwapProgressEventContent<"WaitingForXmrConfirmationsBeforeRedeem">) {
return (
<Box>
<DialogContentText>
We are waiting for the Monero lock transaction to receive enough
confirmations before we can sweep them to your address.
</DialogContentText>
<MoneroTransactionInfoBox
title="Monero Lock Transaction"
txId={xmr_lock_txid}
additionalContent={
additionalContent={
`Confirmations: ${xmr_lock_tx_confirmations}/${xmr_lock_tx_target_confirmations}`
}
}
loading
/>
</Box>
);
}

View file

@ -6,8 +6,9 @@ import MoneroTransactionInfoBox from "../../MoneroTransactionInfoBox";
export default function XmrLockTxInMempoolPage({
xmr_lock_tx_confirmations,
xmr_lock_txid,
xmr_lock_tx_target_confirmations,
}: TauriSwapProgressEventContent<"XmrLockTxInMempool">) {
const additionalContent = `Confirmations: ${formatConfirmations(xmr_lock_tx_confirmations, 10)}`;
const additionalContent = `Confirmations: ${formatConfirmations(xmr_lock_tx_confirmations, xmr_lock_tx_target_confirmations)}`;
return (
<Box>

View file

@ -1,10 +1,11 @@
import React from "react";
import { Badge } from "@mui/material";
import { useResumeableSwapsCountExcludingPunished } from "store/hooks";
export default function UnfinishedSwapsBadge({
children,
}: {
children: JSX.Element;
children: React.ReactNode;
}) {
const resumableSwapsCount = useResumeableSwapsCountExcludingPunished();

View file

@ -16,7 +16,7 @@ function RenderedCliLog({ log }: { log: CliLog }) {
};
return (
<Box>
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Box
style={{
display: "flex",
@ -33,7 +33,6 @@ function RenderedCliLog({ log }: { log: CliLog }) {
<Chip label={target.split("::")[0]} size="small" variant="outlined" />
)}
<Chip label={timestamp} size="small" variant="outlined" />
<Typography variant="subtitle2">{fields.message}</Typography>
</Box>
<Box
sx={{
@ -43,6 +42,7 @@ function RenderedCliLog({ log }: { log: CliLog }) {
flexDirection: "column",
}}
>
<Typography variant="subtitle2">{fields.message}</Typography>
{Object.entries(fields).map(([key, value]) => {
if (key !== "message") {
return (
@ -62,10 +62,12 @@ export default function CliLogsBox({
label,
logs,
topRightButton = null,
autoScroll = false,
}: {
label: string;
logs: (CliLog | string)[];
topRightButton?: ReactNode;
autoScroll?: boolean;
}) {
const [searchQuery, setSearchQuery] = useState<string>("");
@ -85,6 +87,7 @@ export default function CliLogsBox({
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
topRightButton={topRightButton}
autoScroll={autoScroll}
rows={memoizedLogs.map((log) =>
typeof log === "string" ? (
<Typography key={log} component="pre">

View file

@ -16,6 +16,7 @@ export default function ScrollablePaperTextBox({
setSearchQuery = null,
topRightButton = null,
minHeight = MIN_HEIGHT,
autoScroll = false,
}: {
rows: ReactNode[];
title: string;
@ -24,6 +25,7 @@ export default function ScrollablePaperTextBox({
setSearchQuery?: ((query: string) => void) | null;
minHeight?: string;
topRightButton?: ReactNode | null;
autoScroll?: boolean;
}) {
const virtuaEl = useRef<VListHandle | null>(null);
@ -39,6 +41,12 @@ export default function ScrollablePaperTextBox({
virtuaEl.current?.scrollToIndex(0);
}
useEffect(() => {
if (autoScroll) {
scrollToBottom();
}
}, [rows.length, autoScroll]);
return (
<Paper
variant="outlined"