mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-11-27 03:06:24 -05:00
feat: cargo project at root
This commit is contained in:
parent
aa0c0623ca
commit
709a2820c4
313 changed files with 1 additions and 740 deletions
|
|
@ -0,0 +1,75 @@
|
|||
import { makeStyles, Box, Typography, Chip, Tooltip } from '@material-ui/core';
|
||||
import { VerifiedUser } from '@material-ui/icons';
|
||||
import { satsToBtc, secondsToDays } from 'utils/conversionUtils';
|
||||
import { ExtendedProviderStatus } from 'models/apiModel';
|
||||
import {
|
||||
MoneroBitcoinExchangeRate,
|
||||
SatsAmount,
|
||||
} from 'renderer/components/other/Units';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
content: {
|
||||
flex: 1,
|
||||
'& *': {
|
||||
lineBreak: 'anywhere',
|
||||
},
|
||||
},
|
||||
chipsOuter: {
|
||||
display: 'flex',
|
||||
marginTop: theme.spacing(1),
|
||||
gap: theme.spacing(0.5),
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
}));
|
||||
|
||||
export default function ProviderInfo({
|
||||
provider,
|
||||
}: {
|
||||
provider: ExtendedProviderStatus;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Box className={classes.content}>
|
||||
<Typography color="textSecondary" gutterBottom>
|
||||
Swap Provider
|
||||
</Typography>
|
||||
<Typography variant="h5" component="h2">
|
||||
{provider.multiAddr}
|
||||
</Typography>
|
||||
<Typography color="textSecondary" gutterBottom>
|
||||
{provider.peerId.substring(0, 8)}...{provider.peerId.slice(-8)}
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
Exchange rate:{' '}
|
||||
<MoneroBitcoinExchangeRate rate={satsToBtc(provider.price)} />
|
||||
<br />
|
||||
Minimum swap amount: <SatsAmount amount={provider.minSwapAmount} />
|
||||
<br />
|
||||
Maximum swap amount: <SatsAmount amount={provider.maxSwapAmount} />
|
||||
</Typography>
|
||||
<Box className={classes.chipsOuter}>
|
||||
<Chip label={provider.testnet ? 'Testnet' : 'Mainnet'} />
|
||||
{provider.uptime && (
|
||||
<Tooltip title="A high uptime indicates reliability. Providers with low uptime may be unreliable and cause swaps to take longer to complete or fail entirely.">
|
||||
<Chip label={`${Math.round(provider.uptime * 100)} % uptime`} />
|
||||
</Tooltip>
|
||||
)}
|
||||
{provider.age ? (
|
||||
<Chip
|
||||
label={`Went online ${Math.round(secondsToDays(provider.age))} ${
|
||||
provider.age === 1 ? 'day' : 'days'
|
||||
} ago`}
|
||||
/>
|
||||
) : (
|
||||
<Chip label="Discovered via rendezvous point" />
|
||||
)}
|
||||
{provider.recommended === true && (
|
||||
<Tooltip title="This provider has shown to be exceptionally reliable">
|
||||
<Chip label="Recommended" icon={<VerifiedUser />} color="primary" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
import {
|
||||
Avatar,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
DialogTitle,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
Button,
|
||||
DialogContent,
|
||||
makeStyles,
|
||||
CircularProgress,
|
||||
} from '@material-ui/core';
|
||||
import AddIcon from '@material-ui/icons/Add';
|
||||
import { useState } from 'react';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
import { ExtendedProviderStatus } from 'models/apiModel';
|
||||
import {
|
||||
useAllProviders,
|
||||
useAppDispatch,
|
||||
useIsRpcEndpointBusy,
|
||||
} from 'store/hooks';
|
||||
import { setSelectedProvider } from 'store/features/providersSlice';
|
||||
import { RpcMethod } from 'models/rpcModel';
|
||||
import ProviderSubmitDialog from './ProviderSubmitDialog';
|
||||
import ListSellersDialog from '../listSellers/ListSellersDialog';
|
||||
import ProviderInfo from './ProviderInfo';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
dialogContent: {
|
||||
padding: 0,
|
||||
},
|
||||
});
|
||||
|
||||
type ProviderSelectDialogProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function ProviderSubmitDialogOpenButton() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
autoFocus
|
||||
button
|
||||
onClick={() => {
|
||||
// Prevents background from being clicked and reopening dialog
|
||||
if (!open) {
|
||||
setOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ProviderSubmitDialog open={open} onClose={() => setOpen(false)} />
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<AddIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Add a new provider to public registry" />
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
export function ListSellersDialogOpenButton() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const running = useIsRpcEndpointBusy(RpcMethod.LIST_SELLERS);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
autoFocus
|
||||
button
|
||||
disabled={running}
|
||||
onClick={() => {
|
||||
// Prevents background from being clicked and reopening dialog
|
||||
if (!open) {
|
||||
setOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListSellersDialog open={open} onClose={() => setOpen(false)} />
|
||||
<ListItemAvatar>
|
||||
<Avatar>{running ? <CircularProgress /> : <SearchIcon />}</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Discover providers by connecting to a rendezvous point" />
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProviderListDialog({
|
||||
open,
|
||||
onClose,
|
||||
}: ProviderSelectDialogProps) {
|
||||
const classes = useStyles();
|
||||
const providers = useAllProviders();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
function handleProviderChange(provider: ExtendedProviderStatus) {
|
||||
dispatch(setSelectedProvider(provider));
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<DialogTitle>Select a swap provider</DialogTitle>
|
||||
|
||||
<DialogContent className={classes.dialogContent} dividers>
|
||||
<List>
|
||||
{providers.map((provider) => (
|
||||
<ListItem
|
||||
button
|
||||
onClick={() => handleProviderChange(provider)}
|
||||
key={provider.peerId}
|
||||
>
|
||||
<ProviderInfo provider={provider} key={provider.peerId} />
|
||||
</ListItem>
|
||||
))}
|
||||
<ListSellersDialogOpenButton />
|
||||
<ProviderSubmitDialogOpenButton />
|
||||
</List>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import {
|
||||
makeStyles,
|
||||
Card,
|
||||
CardContent,
|
||||
Box,
|
||||
IconButton,
|
||||
} from '@material-ui/core';
|
||||
import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
|
||||
import { useState } from 'react';
|
||||
import { useAppSelector } from 'store/hooks';
|
||||
import ProviderInfo from './ProviderInfo';
|
||||
import ProviderListDialog from './ProviderListDialog';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
inner: {
|
||||
textAlign: 'left',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
providerCard: {
|
||||
width: '100%',
|
||||
},
|
||||
providerCardContent: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default function ProviderSelect() {
|
||||
const classes = useStyles();
|
||||
const [selectDialogOpen, setSelectDialogOpen] = useState(false);
|
||||
const selectedProvider = useAppSelector(
|
||||
(state) => state.providers.selectedProvider,
|
||||
);
|
||||
|
||||
if (!selectedProvider) return <>No provider selected</>;
|
||||
|
||||
function handleSelectDialogClose() {
|
||||
setSelectDialogOpen(false);
|
||||
}
|
||||
|
||||
function handleSelectDialogOpen() {
|
||||
setSelectDialogOpen(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<ProviderListDialog
|
||||
open={selectDialogOpen}
|
||||
onClose={handleSelectDialogClose}
|
||||
/>
|
||||
<Card variant="outlined" className={classes.providerCard}>
|
||||
<CardContent className={classes.providerCardContent}>
|
||||
<ProviderInfo provider={selectedProvider} />
|
||||
<IconButton onClick={handleSelectDialogOpen} size="small">
|
||||
<ArrowForwardIosIcon />
|
||||
</IconButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import { ChangeEvent, useState } from 'react';
|
||||
import {
|
||||
DialogTitle,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
TextField,
|
||||
DialogActions,
|
||||
Button,
|
||||
} from '@material-ui/core';
|
||||
import { Multiaddr } from 'multiaddr';
|
||||
|
||||
type ProviderSubmitDialogProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export default function ProviderSubmitDialog({
|
||||
open,
|
||||
onClose,
|
||||
}: ProviderSubmitDialogProps) {
|
||||
const [multiAddr, setMultiAddr] = useState('');
|
||||
const [peerId, setPeerId] = useState('');
|
||||
|
||||
async function handleProviderSubmit() {
|
||||
if (multiAddr && peerId) {
|
||||
await fetch('https://api.unstoppableswap.net/api/submit-provider', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
multiAddr,
|
||||
peerId,
|
||||
}),
|
||||
});
|
||||
setMultiAddr('');
|
||||
setPeerId('');
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function handleMultiAddrChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
setMultiAddr(event.target.value);
|
||||
}
|
||||
|
||||
function handlePeerIdChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
setPeerId(event.target.value);
|
||||
}
|
||||
|
||||
function getMultiAddressError(): string | null {
|
||||
try {
|
||||
const multiAddress = new Multiaddr(multiAddr);
|
||||
if (multiAddress.protoNames().includes('p2p')) {
|
||||
return 'The multi address should not contain the peer id (/p2p/)';
|
||||
}
|
||||
if (multiAddress.protoNames().find((name) => name.includes('onion'))) {
|
||||
return 'It is currently not possible to add a provider that is only reachable via Tor';
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
return 'Not a valid multi address';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<DialogTitle>Submit a provider to the public registry</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<DialogContentText>
|
||||
If the provider is valid and reachable, it will be displayed to all
|
||||
other users to trade with.
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="Multiaddress"
|
||||
fullWidth
|
||||
helperText={
|
||||
getMultiAddressError() ||
|
||||
'Tells the swap client where the provider can be reached'
|
||||
}
|
||||
value={multiAddr}
|
||||
onChange={handleMultiAddrChange}
|
||||
placeholder="/ip4/182.3.21.93/tcp/9939"
|
||||
error={!!getMultiAddressError()}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Peer ID"
|
||||
fullWidth
|
||||
helperText="Identifies the provider and allows for secure communication"
|
||||
value={peerId}
|
||||
onChange={handlePeerIdChange}
|
||||
placeholder="12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleProviderSubmit}
|
||||
disabled={!(multiAddr && peerId && !getMultiAddressError())}
|
||||
color="primary"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue