feat: cargo project at root

This commit is contained in:
binarybaron 2024-08-08 00:49:04 +02:00
parent aa0c0623ca
commit 709a2820c4
No known key found for this signature in database
GPG key ID: 99B75D3E1476A26E
313 changed files with 1 additions and 740 deletions

View file

@ -0,0 +1,30 @@
import { Button } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { useNavigate } from 'react-router-dom';
import { useAppSelector } from 'store/hooks';
export default function FundsLeftInWalletAlert() {
const fundsLeft = useAppSelector((state) => state.rpc.state.balance);
const navigate = useNavigate();
if (fundsLeft != null && fundsLeft > 0) {
return (
<Alert
variant="filled"
severity="info"
action={
<Button
color="inherit"
size="small"
onClick={() => navigate('/wallet')}
>
View
</Button>
}
>
There are some Bitcoin left in your wallet
</Alert>
);
}
return null;
}

View file

@ -0,0 +1,30 @@
import { Alert } from '@material-ui/lab';
import { Box, LinearProgress } from '@material-ui/core';
import { useAppSelector } from 'store/hooks';
export default function MoneroWalletRpcUpdatingAlert() {
const updateState = useAppSelector(
(s) => s.rpc.state.moneroWalletRpc.updateState,
);
if (updateState === false) {
return null;
}
const progress = Number.parseFloat(
updateState.progress.substring(0, updateState.progress.length - 1),
);
return (
<Alert severity="info">
<Box style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
<span>The Monero wallet is updating. This may take a few moments</span>
<LinearProgress
variant="determinate"
value={progress}
title="Download progress"
/>
</Box>
</Alert>
);
}

View file

@ -0,0 +1,35 @@
import { Alert } from '@material-ui/lab';
import { Box, makeStyles } from '@material-ui/core';
import { useAppSelector } from 'store/hooks';
import WalletRefreshButton from '../pages/wallet/WalletRefreshButton';
import { SatsAmount } from '../other/Units';
const useStyles = makeStyles((theme) => ({
outer: {
paddingBottom: theme.spacing(1),
},
}));
export default function RemainingFundsWillBeUsedAlert() {
const classes = useStyles();
const balance = useAppSelector((s) => s.rpc.state.balance);
if (balance == null || balance <= 0) {
return <></>;
}
return (
<Box className={classes.outer}>
<Alert
severity="warning"
action={<WalletRefreshButton />}
variant="filled"
>
The remaining funds of <SatsAmount amount={balance} /> in the wallet
will be used for the next swap. If the remaining funds exceed the
minimum swap amount of the provider, a swap will be initiated
instantaneously.
</Alert>
</Box>
);
}

View file

@ -0,0 +1,27 @@
import { Alert } from '@material-ui/lab';
import { CircularProgress } from '@material-ui/core';
import { useAppSelector } from 'store/hooks';
import { RpcProcessStateType } from 'models/rpcModel';
export default function RpcStatusAlert() {
const rpcProcess = useAppSelector((s) => s.rpc.process);
if (rpcProcess.type === RpcProcessStateType.STARTED) {
return (
<Alert severity="warning" icon={<CircularProgress size={22} />}>
The swap daemon is starting
</Alert>
);
}
if (rpcProcess.type === RpcProcessStateType.LISTENING_FOR_CONNECTIONS) {
return <Alert severity="success">The swap daemon is running</Alert>;
}
if (rpcProcess.type === RpcProcessStateType.NOT_STARTED) {
return <Alert severity="warning">The swap daemon is being started</Alert>;
}
if (rpcProcess.type === RpcProcessStateType.EXITED) {
return (
<Alert severity="error">The swap daemon has stopped unexpectedly</Alert>
);
}
return <></>;
}

View file

@ -0,0 +1,97 @@
import { makeStyles } from '@material-ui/core';
import { Alert, AlertTitle } from '@material-ui/lab';
import { useActiveSwapInfo } from 'store/hooks';
import {
isSwapTimelockInfoCancelled,
isSwapTimelockInfoNone,
} from 'models/rpcModel';
import HumanizedBitcoinBlockDuration from '../other/HumanizedBitcoinBlockDuration';
const useStyles = makeStyles((theme) => ({
outer: {
marginBottom: theme.spacing(1),
},
list: {
margin: theme.spacing(0.25),
},
}));
export default function SwapMightBeCancelledAlert({
bobBtcLockTxConfirmations,
}: {
bobBtcLockTxConfirmations: number;
}) {
const classes = useStyles();
const swap = useActiveSwapInfo();
if (
bobBtcLockTxConfirmations < 5 ||
swap === null ||
swap.timelock === null
) {
return <></>;
}
const { timelock } = swap;
const punishTimelockOffset = swap.punishTimelock;
return (
<Alert severity="warning" className={classes.outer} variant="filled">
<AlertTitle>Be careful!</AlertTitle>
The swap provider has taken a long time to lock their Monero. This might
mean that:
<ul className={classes.list}>
<li>
There is a technical issue that prevents them from locking their funds
</li>
<li>They are a malicious actor (unlikely)</li>
</ul>
<br />
There is still hope for the swap to be successful but you have to be extra
careful. Regardless of why it has taken them so long, it is important that
you refund the swap within the required time period if the swap is not
completed. If you fail to to do so, you will be punished and lose your
money.
<ul className={classes.list}>
{isSwapTimelockInfoNone(timelock) && (
<>
<li>
<strong>
You will be able to refund in about{' '}
<HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left}
/>
</strong>
</li>
<li>
<strong>
If you have not refunded or completed the swap in about{' '}
<HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left + punishTimelockOffset}
/>
, you will lose your funds.
</strong>
</li>
</>
)}
{isSwapTimelockInfoCancelled(timelock) && (
<li>
<strong>
If you have not refunded or completed the swap in about{' '}
<HumanizedBitcoinBlockDuration
blocks={timelock.Cancel.blocks_left}
/>
, you will lose your funds.
</strong>
</li>
)}
<li>
As long as you see this screen, the swap will be refunded
automatically when the time comes. If this fails, you have to manually
refund by navigating to the History page.
</li>
</ul>
</Alert>
);
}

View file

@ -0,0 +1,233 @@
import { Alert, AlertTitle } from '@material-ui/lab/';
import { Box, makeStyles } from '@material-ui/core';
import { ReactNode } from 'react';
import { exhaustiveGuard } from 'utils/typescriptUtils';
import {
SwapCancelRefundButton,
SwapResumeButton,
} from '../pages/history/table/HistoryRowActions';
import HumanizedBitcoinBlockDuration from '../other/HumanizedBitcoinBlockDuration';
import {
GetSwapInfoResponse,
GetSwapInfoResponseRunningSwap,
isGetSwapInfoResponseRunningSwap,
isSwapTimelockInfoCancelled,
isSwapTimelockInfoNone,
isSwapTimelockInfoPunished,
SwapStateName,
SwapTimelockInfoCancelled,
SwapTimelockInfoNone,
} from '../../../models/rpcModel';
import { SwapMoneroRecoveryButton } from '../pages/history/table/SwapMoneroRecoveryButton';
const useStyles = makeStyles({
box: {
display: 'flex',
flexDirection: 'column',
gap: '0.5rem',
},
list: {
padding: '0px',
margin: '0px',
},
});
/**
* Component for displaying a list of messages.
* @param messages - Array of messages to display.
* @returns JSX.Element
*/
const MessageList = ({ messages }: { messages: ReactNode[] }) => {
const classes = useStyles();
return (
<ul className={classes.list}>
{messages.map((msg, i) => (
// eslint-disable-next-line react/no-array-index-key
<li key={i}>{msg}</li>
))}
</ul>
);
};
/**
* Sub-component for displaying alerts when the swap is in a safe state.
* @param swap - The swap information.
* @returns JSX.Element
*/
const BitcoinRedeemedStateAlert = ({ swap }: { swap: GetSwapInfoResponse }) => {
const classes = useStyles();
return (
<Box className={classes.box}>
<MessageList
messages={[
'The Bitcoin has been redeemed by the other party',
'There is no risk of losing funds. You can take your time',
'The Monero will be automatically redeemed to the address you provided as soon as you resume the swap',
'If this step fails, you can manually redeem the funds',
]}
/>
<SwapMoneroRecoveryButton swap={swap} size="small" variant="contained" />
</Box>
);
};
/**
* Sub-component for displaying alerts when the swap is in a state with no timelock info.
* @param swap - The swap information.
* @param punishTimelockOffset - The punish timelock offset.
* @returns JSX.Element
*/
const BitcoinLockedNoTimelockExpiredStateAlert = ({
timelock,
punishTimelockOffset,
}: {
timelock: SwapTimelockInfoNone;
punishTimelockOffset: number;
}) => (
<MessageList
messages={[
<>
Your Bitcoin is locked. If the swap is not completed in approximately{' '}
<HumanizedBitcoinBlockDuration blocks={timelock.None.blocks_left} />,
you need to refund
</>,
<>
You will lose your funds if you do not refund or complete the swap
within{' '}
<HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left + punishTimelockOffset}
/>
</>,
]}
/>
);
/**
* Sub-component for displaying alerts when the swap timelock is expired
* The swap could be cancelled but not necessarily (the transaction might not have been published yet)
* But it doesn't matter because the swap cannot be completed anymore
* @param swap - The swap information.
* @returns JSX.Element
*/
const BitcoinPossiblyCancelledAlert = ({
swap,
timelock,
}: {
swap: GetSwapInfoResponse;
timelock: SwapTimelockInfoCancelled;
}) => {
const classes = useStyles();
return (
<Box className={classes.box}>
<MessageList
messages={[
'The swap was cancelled because it did not complete in time',
'You must resume the swap immediately to refund your Bitcoin. If that fails, you can manually refund it',
<>
You will lose your funds if you do not refund within{' '}
<HumanizedBitcoinBlockDuration
blocks={timelock.Cancel.blocks_left}
/>
</>,
]}
/>
<SwapCancelRefundButton swap={swap} size="small" variant="contained" />
</Box>
);
};
/**
* Sub-component for displaying alerts requiring immediate action.
* @returns JSX.Element
*/
const ImmediateActionAlert = () => (
<>Resume the swap immediately to avoid losing your funds</>
);
/**
* Main component for displaying the appropriate swap alert status text.
* @param swap - The swap information.
* @returns JSX.Element | null
*/
function SwapAlertStatusText({
swap,
}: {
swap: GetSwapInfoResponseRunningSwap;
}) {
switch (swap.stateName) {
// This is the state where the swap is safe because the other party has redeemed the Bitcoin
// It cannot be punished anymore
case SwapStateName.BtcRedeemed:
return <BitcoinRedeemedStateAlert swap={swap} />;
// These are states that are at risk of punishment because the Bitcoin have been locked
// but has not been redeemed yet by the other party
case SwapStateName.BtcLocked:
case SwapStateName.XmrLockProofReceived:
case SwapStateName.XmrLocked:
case SwapStateName.EncSigSent:
case SwapStateName.CancelTimelockExpired:
case SwapStateName.BtcCancelled:
if (swap.timelock !== null) {
if (isSwapTimelockInfoNone(swap.timelock)) {
return (
<BitcoinLockedNoTimelockExpiredStateAlert
punishTimelockOffset={swap.punishTimelock}
timelock={swap.timelock}
/>
);
}
if (isSwapTimelockInfoCancelled(swap.timelock)) {
return (
<BitcoinPossiblyCancelledAlert
timelock={swap.timelock}
swap={swap}
/>
);
}
if (isSwapTimelockInfoPunished(swap.timelock)) {
return <ImmediateActionAlert />;
}
// We have covered all possible timelock states above
// If we reach this point, it means we have missed a case
return exhaustiveGuard(swap.timelock);
}
return <ImmediateActionAlert />;
default:
return exhaustiveGuard(swap.stateName);
}
}
/**
* Main component for displaying the swap status alert.
* @param swap - The swap information.
* @returns JSX.Element | null
*/
export default function SwapStatusAlert({
swap,
}: {
swap: GetSwapInfoResponse;
}): JSX.Element | null {
// If the swap is not running, there is no need to display the alert
// This is either because the swap is finished or has not started yet (e.g. in the setup phase, no Bitcoin locked)
if (!isGetSwapInfoResponseRunningSwap(swap)) {
return null;
}
return (
<Alert
key={swap.swapId}
severity="warning"
action={<SwapResumeButton swap={swap} />}
variant="filled"
>
<AlertTitle>
Swap {swap.swapId.substring(0, 5)}... is unfinished
</AlertTitle>
<SwapAlertStatusText swap={swap} />
</Alert>
);
}

View file

@ -0,0 +1,28 @@
import { Box, makeStyles } from '@material-ui/core';
import { useSwapInfosSortedByDate } from 'store/hooks';
import SwapStatusAlert from './SwapStatusAlert';
const useStyles = makeStyles((theme) => ({
outer: {
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
},
}));
export default function SwapTxLockAlertsBox() {
const classes = useStyles();
// We specifically choose ALL swaps here
// If a swap is in a state where an Alert is not needed (becaue no Bitcoin have been locked or because the swap has been completed)
// the SwapStatusAlert component will not render an Alert
const swaps = useSwapInfosSortedByDate();
return (
<Box className={classes.outer}>
{swaps.map((swap) => (
<SwapStatusAlert key={swap.swapId} swap={swap} />
))}
</Box>
);
}

View file

@ -0,0 +1,33 @@
import { Button } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { useNavigate } from 'react-router-dom';
import { useResumeableSwapsCount } from 'store/hooks';
export default function UnfinishedSwapsAlert() {
const resumableSwapsCount = useResumeableSwapsCount();
const navigate = useNavigate();
if (resumableSwapsCount > 0) {
return (
<Alert
severity="warning"
variant="filled"
action={
<Button
color="inherit"
size="small"
onClick={() => navigate('/history')}
>
VIEW
</Button>
}
>
You have{' '}
{resumableSwapsCount > 1
? `${resumableSwapsCount} unfinished swaps`
: 'one unfinished swap'}
</Alert>
);
}
return null;
}