mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-24 22:19:37 -04:00

* feat(gui): Implement base structure for new swap ux - refactored file structure to match common projecte structure - implement step get bitcoin * feat(gui): Implement basic multi step modal * feat(gui): Add outline of add choose maker and offer step * feat(gui): Add receive address selector * refactor(gui): format code * feat(gui): Make Swap Overveiw interactive * feat(gui): Add action to swap amount selector to quickly go to deposit bitcoin step * progress * feat(gui, cli): Request quotes concurrently at all sellers * refresh offers occasionally, display progress * progress * feat(gui, cli): Request quotes concurrently at all sellers * refresh offers occasionally, display progress * progress, works again * allow closing dialog without warning if no funds have been locked * progress * feat(gui): Rewrite Swap Components to have flow directly on swap page * feat: log monero_rpc_pool only at >= INFO level * remove full_url, add migration to change scheme of node.monerodevs.org to http * feat: send known_quotes with WaitingForBitcoinDeposit Tauri progress event (even if our balance is too low) * lock swap lock later * refactor(monero-rpc-pool): Pass around tuple of (scheme, host, port) as nodes * refactor(gui): Remove modal for swap and adjust few pages for swap process - Moved files from swap modal to page directory - Use new layouts for init page - Use new layout for depositBTC Step - Use new layout for Offer Page * allow cancel before lock * remove unused code * dynamic layout, chips for amounts * feat(gui): Add breakpoints * remove continue button, add select button on each maker box * add GetCurrentSwapArgs tauri command to only suspend swap if one is actually running * feat(gui): Show all known quotes and disable the ones that aren't available * fix get_current_swap, kill tasks when buy_xmr is cancelled * cleanup: remove CleanupGuard * feat(gui): Add cancel button on every page * refactor(gui): Fix merge issues * refactor(gui): Unify Cancel Button insertion by using a swap base page * refactor(gui): Unify Cancel Button insertion by using a swap base page * refactor(gui): Remove deeply nested relative paths * refactor(gui): Made BaseSwapPage obsolete by moving Cancel Button to SwapStatePage * refactor(gui): Adjust condition for showing SwapSuspendAlert * fix(gui): Fetch previous monero redeem addresses repeatedly * refactor(gui): Remove QR Code from deposit and choose maker page * refactor(gui): Don't display dialog on History page * fix(gui): If no swap was running "suspend_current_swap" will still return success now, less logic in the CancelButton * get offer select working * refactor: dont display cancel button on set redeem address page * feat: add pagination to offers * refactor * emit partial events for list_sellers * refactor: remove torSlice * refactor: use sync (non tokio) mutex for approvals * throttle getSwapInfo calls * feat: add debug page back, add info in suspend dialog about what will happen * refactor: format files * refactor(gui): Remove sortMakers method and replace with method that sorts approvals * refactor(gui): Refactor swap page structure * fix(gui): Add breakpoints to swapSetupInflightPage * feat(gui): Add flag for outdated makers * refactor(gui): Reduce fetch rate for maker quotes * fix(gui): Debug Window size * no unwrap --------- Co-authored-by: b-enedict <benedict.seuss@gmail.com>
195 lines
6.3 KiB
TypeScript
195 lines
6.3 KiB
TypeScript
import { Step, StepLabel, Stepper, Typography } from "@mui/material";
|
|
import { SwapState } from "models/storeModel";
|
|
import { useAppSelector } from "store/hooks";
|
|
import logger from "utils/logger";
|
|
|
|
export enum PathType {
|
|
HAPPY_PATH = "happy path",
|
|
UNHAPPY_PATH = "unhappy path",
|
|
}
|
|
|
|
type PathStep = [type: PathType, step: number, isError: boolean];
|
|
|
|
/**
|
|
* Determines the current step in the swap process based on the previous and latest state.
|
|
* @param prevState - The previous state of the swap process (null if it's the initial state)
|
|
* @param latestState - The latest state of the swap process
|
|
* @returns A tuple containing [PathType, activeStep, errorFlag]
|
|
*/
|
|
function getActiveStep(state: SwapState | null): PathStep | null {
|
|
// In case we cannot infer a correct step from the state
|
|
function fallbackStep(reason: string) {
|
|
logger.error(
|
|
`Unable to choose correct stepper type (reason: ${reason}, state: ${JSON.stringify(state)}`,
|
|
);
|
|
return null;
|
|
}
|
|
|
|
if (state === null) {
|
|
return [PathType.HAPPY_PATH, 0, false];
|
|
}
|
|
|
|
const prevState = state.prev;
|
|
const isReleased = state.curr.type === "Released";
|
|
|
|
// If the swap is released we use the previous state to display the correct step
|
|
const latestState = isReleased ? prevState : state.curr;
|
|
|
|
// If the swap is released but we do not have a previous state we fallback
|
|
if (latestState === null) {
|
|
return fallbackStep(
|
|
"Swap has been released but we do not have a previous state saved to display",
|
|
);
|
|
}
|
|
|
|
// This should really never happen. For this statement to be true, the host has to submit a "Released" event twice
|
|
if (latestState.type === "Released") {
|
|
return fallbackStep(
|
|
"Both the current and previous states are both of type 'Released'.",
|
|
);
|
|
}
|
|
|
|
switch (latestState.type) {
|
|
// Step 0: Initializing the swap
|
|
// These states represent the very beginning of the swap process
|
|
// No funds have been locked
|
|
case "RequestingQuote":
|
|
case "ReceivedQuote":
|
|
case "WaitingForBtcDeposit":
|
|
case "SwapSetupInflight":
|
|
return null; // No funds have been locked yet
|
|
|
|
// Step 1: Waiting for Bitcoin lock confirmation
|
|
// Bitcoin has been locked, waiting for the counterparty to lock their XMR
|
|
case "BtcLockTxInMempool":
|
|
// We only display the first step as completed if the Bitcoin lock has been confirmed
|
|
if (
|
|
latestState.content.btc_lock_confirmations !== undefined &&
|
|
latestState.content.btc_lock_confirmations > 0
|
|
) {
|
|
return [PathType.HAPPY_PATH, 1, isReleased];
|
|
}
|
|
return [PathType.HAPPY_PATH, 0, isReleased];
|
|
|
|
// Still Step 1: Both Bitcoin and XMR have been locked, waiting for Monero lock to be confirmed
|
|
case "XmrLockTxInMempool":
|
|
return [PathType.HAPPY_PATH, 1, isReleased];
|
|
|
|
// Step 2: Waiting for encrypted signature to be sent to Alice
|
|
// and for Alice to redeem the Bitcoin
|
|
case "XmrLocked":
|
|
case "EncryptedSignatureSent":
|
|
return [PathType.HAPPY_PATH, 2, isReleased];
|
|
|
|
// Step 3: Waiting for XMR redemption
|
|
// Bitcoin has been redeemed by Alice, now waiting for us to redeem Monero
|
|
case "WaitingForXmrConfirmationsBeforeRedeem":
|
|
case "RedeemingMonero":
|
|
return [PathType.HAPPY_PATH, 3, isReleased];
|
|
|
|
// Step 4: Swap completed successfully
|
|
// XMR redemption transaction is in mempool, swap is essentially complete
|
|
case "XmrRedeemInMempool":
|
|
return [PathType.HAPPY_PATH, 4, false];
|
|
|
|
// Unhappy Path States
|
|
|
|
// Step 1: Cancel timelock has expired. Waiting for cancel transaction to be published
|
|
case "CancelTimelockExpired":
|
|
return [PathType.UNHAPPY_PATH, 0, isReleased];
|
|
|
|
// Step 2: Swap has been cancelled. Waiting for Bitcoin to be refunded
|
|
case "BtcCancelled":
|
|
return [PathType.UNHAPPY_PATH, 1, isReleased];
|
|
|
|
// Step 2: One of the two Bitcoin refund transactions have been published
|
|
// but they haven't been confirmed yet
|
|
case "BtcRefundPublished":
|
|
case "BtcEarlyRefundPublished":
|
|
return [PathType.UNHAPPY_PATH, 1, isReleased];
|
|
|
|
// Step 2: One of the two Bitcoin refund transactions have been confirmed
|
|
case "BtcRefunded":
|
|
case "BtcEarlyRefunded":
|
|
return [PathType.UNHAPPY_PATH, 2, false];
|
|
|
|
// Step 2 (Failed): Failed to refund Bitcoin
|
|
// The timelock expired before we could refund, resulting in punishment
|
|
case "BtcPunished":
|
|
return [PathType.UNHAPPY_PATH, 1, true];
|
|
|
|
// Attempting cooperative redemption after punishment
|
|
case "AttemptingCooperativeRedeem":
|
|
case "CooperativeRedeemAccepted":
|
|
return [PathType.UNHAPPY_PATH, 1, isReleased];
|
|
case "CooperativeRedeemRejected":
|
|
return [PathType.UNHAPPY_PATH, 1, true];
|
|
|
|
case "Resuming":
|
|
return null;
|
|
default:
|
|
return fallbackStep("No step is assigned to the current state");
|
|
// TODO: Make this guard work. It should force the compiler to check if we have covered all possible cases.
|
|
// return exhaustiveGuard(latestState.type);
|
|
}
|
|
}
|
|
|
|
function SwapStepper({
|
|
steps,
|
|
activeStep,
|
|
error,
|
|
}: {
|
|
steps: Array<{ label: string; duration: string }>;
|
|
activeStep: number;
|
|
error: boolean;
|
|
}) {
|
|
return (
|
|
<Stepper activeStep={activeStep}>
|
|
{steps.map((step, index) => (
|
|
<Step key={index}>
|
|
<StepLabel
|
|
optional={
|
|
<Typography variant="caption">{step.duration}</Typography>
|
|
}
|
|
error={error && activeStep === index}
|
|
>
|
|
{step.label}
|
|
</StepLabel>
|
|
</Step>
|
|
))}
|
|
</Stepper>
|
|
);
|
|
}
|
|
|
|
const HAPPY_PATH_STEP_LABELS = [
|
|
{ label: "Locking your BTC", duration: "~12min" },
|
|
{ label: "They lock their XMR", duration: "~10min" },
|
|
{ label: "They redeem the BTC", duration: "~2min" },
|
|
{ label: "Redeeming your XMR", duration: "~10min" },
|
|
];
|
|
|
|
const UNHAPPY_PATH_STEP_LABELS = [
|
|
{ label: "Cancelling swap", duration: "~1min" },
|
|
{ label: "Attempting recovery", duration: "~5min" },
|
|
];
|
|
|
|
export default function SwapStateStepper({
|
|
state,
|
|
}: {
|
|
state: SwapState | null;
|
|
}) {
|
|
const result = getActiveStep(state);
|
|
|
|
if (result === null) {
|
|
return null;
|
|
}
|
|
|
|
const [pathType, activeStep, error] = result;
|
|
|
|
const steps =
|
|
pathType === PathType.HAPPY_PATH
|
|
? HAPPY_PATH_STEP_LABELS
|
|
: UNHAPPY_PATH_STEP_LABELS;
|
|
|
|
return <SwapStepper steps={steps} activeStep={activeStep} error={error} />;
|
|
}
|