mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-12-18 18:14:03 -05:00
feat: Maker avatar (#205)
- GUI: Changed terminology from "swap providers" to "makers" - GUI: For each maker, we now display a unique deterministically generated avatar derived from the maker's public key
This commit is contained in:
parent
23d22b5792
commit
b2e74df37e
36 changed files with 511 additions and 429 deletions
|
|
@ -27,7 +27,7 @@ export default function RemainingFundsWillBeUsedAlert() {
|
|||
>
|
||||
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
|
||||
minimum swap amount of the maker, a swap will be initiated
|
||||
instantaneously.
|
||||
</Alert>
|
||||
</Box>
|
||||
|
|
|
|||
29
src-gui/src/renderer/components/icons/IdentIcon.tsx
Normal file
29
src-gui/src/renderer/components/icons/IdentIcon.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import * as jdenticon from 'jdenticon';
|
||||
|
||||
interface IdentIconProps {
|
||||
value: string;
|
||||
size?: number | string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function IdentIcon({ value, size = 40, className = '' }: IdentIconProps) {
|
||||
const iconRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (iconRef.current) {
|
||||
jdenticon.update(iconRef.current, value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<svg
|
||||
ref={iconRef}
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
data-jdenticon-value={value} />
|
||||
);
|
||||
}
|
||||
|
||||
export default IdentIcon;
|
||||
|
|
@ -17,7 +17,7 @@ import { ChangeEvent, useState } from "react";
|
|||
import TruncatedText from "renderer/components/other/TruncatedText";
|
||||
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||
import { listSellersAtRendezvousPoint, PRESET_RENDEZVOUS_POINTS } from "renderer/rpc";
|
||||
import { discoveredProvidersByRendezvous } from "store/features/providersSlice";
|
||||
import { discoveredMakersByRendezvous } from "store/features/makersSlice";
|
||||
import { useAppDispatch } from "store/hooks";
|
||||
import { isValidMultiAddressWithPeerId } from "utils/parseUtils";
|
||||
|
||||
|
|
@ -54,20 +54,20 @@ export default function ListSellersDialog({
|
|||
}
|
||||
|
||||
function handleSuccess({ sellers }: ListSellersResponse) {
|
||||
dispatch(discoveredProvidersByRendezvous(sellers));
|
||||
dispatch(discoveredMakersByRendezvous(sellers));
|
||||
|
||||
const discoveredSellersCount = sellers.length;
|
||||
let message: string;
|
||||
|
||||
switch (discoveredSellersCount) {
|
||||
case 0:
|
||||
message = `No providers were discovered at the rendezvous point`;
|
||||
message = `No makers were discovered at the rendezvous point`;
|
||||
break;
|
||||
case 1:
|
||||
message = `Discovered one provider at the rendezvous point`;
|
||||
message = `Discovered one maker at the rendezvous point`;
|
||||
break;
|
||||
default:
|
||||
message = `Discovered ${discoveredSellersCount} providers at the rendezvous point`;
|
||||
message = `Discovered ${discoveredSellersCount} makers at the rendezvous point`;
|
||||
}
|
||||
|
||||
enqueueSnackbar(message, {
|
||||
|
|
@ -80,13 +80,13 @@ export default function ListSellersDialog({
|
|||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<DialogTitle>Discover swap providers</DialogTitle>
|
||||
<DialogTitle>Discover makers</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<DialogContentText>
|
||||
The rendezvous protocol provides a way to discover providers (trading
|
||||
The rendezvous protocol provides a way to discover makers (trading
|
||||
partners) without relying on one singular centralized institution. By
|
||||
manually connecting to a rendezvous point run by a volunteer, you can
|
||||
discover providers and then connect and swap with them.
|
||||
discover makers and then connect and swap with them.
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
|
|
|
|||
127
src-gui/src/renderer/components/modal/provider/MakerInfo.tsx
Normal file
127
src-gui/src/renderer/components/modal/provider/MakerInfo.tsx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import { Box, Chip, makeStyles, Paper, Tooltip, Typography } from "@material-ui/core";
|
||||
import { VerifiedUser } from "@material-ui/icons";
|
||||
import { ExtendedMakerStatus } from "models/apiModel";
|
||||
import TruncatedText from "renderer/components/other/TruncatedText";
|
||||
import {
|
||||
MoneroBitcoinExchangeRate,
|
||||
SatsAmount,
|
||||
} from "renderer/components/other/Units";
|
||||
import { satsToBtc, secondsToDays } from "utils/conversionUtils";
|
||||
import { isMakerOutdated } from 'utils/multiAddrUtils';
|
||||
import WarningIcon from '@material-ui/icons/Warning';
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import IdentIcon from "renderer/components/icons/IdentIcon";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
content: {
|
||||
flex: 1,
|
||||
"& *": {
|
||||
lineBreak: "anywhere",
|
||||
},
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
chipsOuter: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: theme.spacing(0.5),
|
||||
},
|
||||
quoteOuter: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
peerIdContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* A chip that displays the markup of the maker's exchange rate compared to the market rate.
|
||||
*/
|
||||
function MakerMarkupChip({ maker }: { maker: ExtendedMakerStatus }) {
|
||||
const marketExchangeRate = useAppSelector(s => s.rates?.xmrBtcRate);
|
||||
if (marketExchangeRate === null)
|
||||
return null;
|
||||
|
||||
const makerExchangeRate = satsToBtc(maker.price);
|
||||
/** The markup of the exchange rate compared to the market rate in percent */
|
||||
const markup = (makerExchangeRate - marketExchangeRate) / marketExchangeRate * 100;
|
||||
|
||||
return (
|
||||
<Tooltip title="The markup this maker charges compared to centralized markets. A lower markup means that you get more Monero for your Bitcoin.">
|
||||
<Chip label={`Markup ${markup.toFixed(2)}%`} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MakerInfo({
|
||||
maker,
|
||||
}: {
|
||||
maker: ExtendedMakerStatus;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const isOutdated = isMakerOutdated(maker);
|
||||
|
||||
return (
|
||||
<Box className={classes.content}>
|
||||
<Box className={classes.peerIdContainer}>
|
||||
<Tooltip title={"This avatar is deterministically derived from the public key of the maker"} arrow>
|
||||
<Box className={classes.peerIdContainer}>
|
||||
<IdentIcon value={maker.peerId} size={"3rem"} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Box>
|
||||
<Typography variant="subtitle1">
|
||||
<TruncatedText limit={16} truncateMiddle>{maker.peerId}</TruncatedText>
|
||||
</Typography>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
{maker.multiAddr}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className={classes.quoteOuter}>
|
||||
<Typography variant="caption">
|
||||
Exchange rate:{" "}
|
||||
<MoneroBitcoinExchangeRate rate={satsToBtc(maker.price)} />
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
Minimum amount: <SatsAmount amount={maker.minSwapAmount} />
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
Maximum amount: <SatsAmount amount={maker.maxSwapAmount} />
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box className={classes.chipsOuter}>
|
||||
{maker.testnet && <Chip label="Testnet" />}
|
||||
{maker.uptime && (
|
||||
<Tooltip title="A high uptime (>90%) indicates reliability. Makers with very low uptime may be unreliable and cause swaps to take longer to complete or fail entirely.">
|
||||
<Chip label={`${Math.round(maker.uptime * 100)}% uptime`} />
|
||||
</Tooltip>
|
||||
)}
|
||||
{maker.age ? (
|
||||
<Chip
|
||||
label={`Went online ${Math.round(secondsToDays(maker.age))} ${maker.age === 1 ? "day" : "days"
|
||||
} ago`}
|
||||
/>
|
||||
) : (
|
||||
<Chip label="Discovered via rendezvous point" />
|
||||
)}
|
||||
{maker.recommended === true && (
|
||||
<Tooltip title="This maker has shown to be exceptionally reliable">
|
||||
<Chip label="Recommended" icon={<VerifiedUser />} color="primary" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{isOutdated && (
|
||||
<Tooltip title="This maker is running an older version of the software. Outdated makers may be unreliable and cause swaps to take longer to complete or fail entirely.">
|
||||
<Chip label="Outdated" icon={<WarningIcon />} color="primary" />
|
||||
</Tooltip>
|
||||
)}
|
||||
<MakerMarkupChip maker={maker} />
|
||||
</Box>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -13,13 +13,13 @@ import {
|
|||
} from "@material-ui/core";
|
||||
import AddIcon from "@material-ui/icons/Add";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import { ExtendedProviderStatus } from "models/apiModel";
|
||||
import { ExtendedMakerStatus } from "models/apiModel";
|
||||
import { useState } from "react";
|
||||
import { setSelectedProvider } from "store/features/providersSlice";
|
||||
import { useAllProviders, useAppDispatch } from "store/hooks";
|
||||
import { setSelectedMaker } from "store/features/makersSlice";
|
||||
import { useAllMakers, useAppDispatch } from "store/hooks";
|
||||
import ListSellersDialog from "../listSellers/ListSellersDialog";
|
||||
import ProviderInfo from "./ProviderInfo";
|
||||
import ProviderSubmitDialog from "./ProviderSubmitDialog";
|
||||
import MakerInfo from "./MakerInfo";
|
||||
import MakerSubmitDialog from "./MakerSubmitDialog";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
dialogContent: {
|
||||
|
|
@ -27,12 +27,12 @@ const useStyles = makeStyles({
|
|||
},
|
||||
});
|
||||
|
||||
type ProviderSelectDialogProps = {
|
||||
type MakerSelectDialogProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function ProviderSubmitDialogOpenButton() {
|
||||
export function MakerSubmitDialogOpenButton() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
|
|
@ -46,13 +46,13 @@ export function ProviderSubmitDialogOpenButton() {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<ProviderSubmitDialog open={open} onClose={() => setOpen(false)} />
|
||||
<MakerSubmitDialog open={open} onClose={() => setOpen(false)} />
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<AddIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Add a new provider to public registry" />
|
||||
<ListItemText primary="Add a new maker to public registry" />
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
|
@ -77,41 +77,41 @@ export function ListSellersDialogOpenButton() {
|
|||
<SearchIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Discover providers by connecting to a rendezvous point" />
|
||||
<ListItemText primary="Discover makers by connecting to a rendezvous point" />
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProviderListDialog({
|
||||
export default function MakerListDialog({
|
||||
open,
|
||||
onClose,
|
||||
}: ProviderSelectDialogProps) {
|
||||
}: MakerSelectDialogProps) {
|
||||
const classes = useStyles();
|
||||
const providers = useAllProviders();
|
||||
const makers = useAllMakers();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
function handleProviderChange(provider: ExtendedProviderStatus) {
|
||||
dispatch(setSelectedProvider(provider));
|
||||
function handleMakerChange(maker: ExtendedMakerStatus) {
|
||||
dispatch(setSelectedMaker(maker));
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<DialogTitle>Select a swap provider</DialogTitle>
|
||||
<DialogTitle>Select a maker</DialogTitle>
|
||||
|
||||
<DialogContent className={classes.dialogContent} dividers>
|
||||
<List>
|
||||
{providers.map((provider) => (
|
||||
{makers.map((maker) => (
|
||||
<ListItem
|
||||
button
|
||||
onClick={() => handleProviderChange(provider)}
|
||||
key={provider.peerId}
|
||||
onClick={() => handleMakerChange(maker)}
|
||||
key={maker.peerId}
|
||||
>
|
||||
<ProviderInfo provider={provider} key={provider.peerId} />
|
||||
<MakerInfo maker={maker} key={maker.peerId} />
|
||||
</ListItem>
|
||||
))}
|
||||
<ListSellersDialogOpenButton />
|
||||
<ProviderSubmitDialogOpenButton />
|
||||
<MakerSubmitDialogOpenButton />
|
||||
</List>
|
||||
</DialogContent>
|
||||
|
||||
|
|
@ -8,8 +8,8 @@ import {
|
|||
import ArrowForwardIosIcon from "@material-ui/icons/ArrowForwardIos";
|
||||
import { useState } from "react";
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import ProviderInfo from "./ProviderInfo";
|
||||
import ProviderListDialog from "./ProviderListDialog";
|
||||
import MakerInfo from "./MakerInfo";
|
||||
import MakerListDialog from "./MakerListDialog";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
inner: {
|
||||
|
|
@ -17,23 +17,23 @@ const useStyles = makeStyles({
|
|||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
providerCard: {
|
||||
makerCard: {
|
||||
width: "100%",
|
||||
},
|
||||
providerCardContent: {
|
||||
makerCardContent: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
});
|
||||
|
||||
export default function ProviderSelect() {
|
||||
export default function MakerSelect() {
|
||||
const classes = useStyles();
|
||||
const [selectDialogOpen, setSelectDialogOpen] = useState(false);
|
||||
const selectedProvider = useAppSelector(
|
||||
(state) => state.providers.selectedProvider,
|
||||
const selectedMaker = useAppSelector(
|
||||
(state) => state.makers.selectedMaker,
|
||||
);
|
||||
|
||||
if (!selectedProvider) return <>No provider selected</>;
|
||||
if (!selectedMaker) return <>No maker selected</>;
|
||||
|
||||
function handleSelectDialogClose() {
|
||||
setSelectDialogOpen(false);
|
||||
|
|
@ -45,13 +45,13 @@ export default function ProviderSelect() {
|
|||
|
||||
return (
|
||||
<Box>
|
||||
<ProviderListDialog
|
||||
<MakerListDialog
|
||||
open={selectDialogOpen}
|
||||
onClose={handleSelectDialogClose}
|
||||
/>
|
||||
<Card variant="outlined" className={classes.providerCard}>
|
||||
<CardContent className={classes.providerCardContent}>
|
||||
<ProviderInfo provider={selectedProvider} />
|
||||
<Card variant="outlined" className={classes.makerCard}>
|
||||
<CardContent className={classes.makerCardContent}>
|
||||
<MakerInfo maker={selectedMaker} />
|
||||
<IconButton onClick={handleSelectDialogOpen} size="small">
|
||||
<ArrowForwardIosIcon />
|
||||
</IconButton>
|
||||
|
|
@ -10,19 +10,19 @@ import {
|
|||
import { Multiaddr } from "multiaddr";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
|
||||
type ProviderSubmitDialogProps = {
|
||||
type MakerSubmitDialogProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export default function ProviderSubmitDialog({
|
||||
export default function MakerSubmitDialog({
|
||||
open,
|
||||
onClose,
|
||||
}: ProviderSubmitDialogProps) {
|
||||
}: MakerSubmitDialogProps) {
|
||||
const [multiAddr, setMultiAddr] = useState("");
|
||||
const [peerId, setPeerId] = useState("");
|
||||
|
||||
async function handleProviderSubmit() {
|
||||
async function handleMakerSubmit() {
|
||||
if (multiAddr && peerId) {
|
||||
await fetch("https://api.unstoppableswap.net/api/submit-provider", {
|
||||
method: "post",
|
||||
|
|
@ -55,7 +55,7 @@ export default function ProviderSubmitDialog({
|
|||
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 "It is currently not possible to add a maker that is only reachable via Tor";
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
|
|
@ -65,10 +65,10 @@ export default function ProviderSubmitDialog({
|
|||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<DialogTitle>Submit a provider to the public registry</DialogTitle>
|
||||
<DialogTitle>Submit a maker to the public registry</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<DialogContentText>
|
||||
If the provider is valid and reachable, it will be displayed to all
|
||||
If the maker is valid and reachable, it will be displayed to all
|
||||
other users to trade with.
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
|
|
@ -78,7 +78,7 @@ export default function ProviderSubmitDialog({
|
|||
fullWidth
|
||||
helperText={
|
||||
getMultiAddressError() ||
|
||||
"Tells the swap client where the provider can be reached"
|
||||
"Tells the swap client where the maker can be reached"
|
||||
}
|
||||
value={multiAddr}
|
||||
onChange={handleMultiAddrChange}
|
||||
|
|
@ -89,7 +89,7 @@ export default function ProviderSubmitDialog({
|
|||
margin="dense"
|
||||
label="Peer ID"
|
||||
fullWidth
|
||||
helperText="Identifies the provider and allows for secure communication"
|
||||
helperText="Identifies the maker and allows for secure communication"
|
||||
value={peerId}
|
||||
onChange={handlePeerIdChange}
|
||||
placeholder="12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"
|
||||
|
|
@ -99,7 +99,7 @@ export default function ProviderSubmitDialog({
|
|||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleProviderSubmit}
|
||||
onClick={handleMakerSubmit}
|
||||
disabled={!(multiAddr && peerId && !getMultiAddressError())}
|
||||
color="primary"
|
||||
>
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
import { Box, Chip, makeStyles, Tooltip, Typography } from "@material-ui/core";
|
||||
import { VerifiedUser } from "@material-ui/icons";
|
||||
import { ExtendedProviderStatus } from "models/apiModel";
|
||||
import TruncatedText from "renderer/components/other/TruncatedText";
|
||||
import {
|
||||
MoneroBitcoinExchangeRate,
|
||||
SatsAmount,
|
||||
} from "renderer/components/other/Units";
|
||||
import { satsToBtc, secondsToDays } from "utils/conversionUtils";
|
||||
import { isProviderOutdated } from 'utils/multiAddrUtils';
|
||||
import WarningIcon from '@material-ui/icons/Warning';
|
||||
import { useAppSelector } from "store/hooks";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
content: {
|
||||
flex: 1,
|
||||
"& *": {
|
||||
lineBreak: "anywhere",
|
||||
},
|
||||
},
|
||||
chipsOuter: {
|
||||
display: "flex",
|
||||
marginTop: theme.spacing(1),
|
||||
gap: theme.spacing(0.5),
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* A chip that displays the markup of the provider's exchange rate compared to the market rate.
|
||||
*/
|
||||
function ProviderMarkupChip({ provider }: { provider: ExtendedProviderStatus }) {
|
||||
const marketExchangeRate = useAppSelector(s => s.rates?.xmrBtcRate);
|
||||
if (marketExchangeRate === null)
|
||||
return null;
|
||||
|
||||
const providerExchangeRate = satsToBtc(provider.price);
|
||||
/** The markup of the exchange rate compared to the market rate in percent */
|
||||
const markup = (providerExchangeRate - marketExchangeRate) / marketExchangeRate * 100;
|
||||
|
||||
return (
|
||||
<Tooltip title="The markup this provider charges compared to centralized markets. A lower markup means that you get more Monero for your Bitcoin.">
|
||||
<Chip label={`Markup ${markup.toFixed(2)}%`} />
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default function ProviderInfo({
|
||||
provider,
|
||||
}: {
|
||||
provider: ExtendedProviderStatus;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const isOutdated = isProviderOutdated(provider);
|
||||
|
||||
return (
|
||||
<Box className={classes.content}>
|
||||
<Typography color="textSecondary" gutterBottom>
|
||||
Swap Provider
|
||||
</Typography>
|
||||
<Typography variant="h5" component="h2">
|
||||
{provider.multiAddr}
|
||||
</Typography>
|
||||
<Typography color="textSecondary" gutterBottom>
|
||||
<TruncatedText limit={16} truncateMiddle>{provider.peerId}</TruncatedText>
|
||||
</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}>
|
||||
{provider.testnet && <Chip label="Testnet" />}
|
||||
{provider.uptime && (
|
||||
<Tooltip title="A high uptime (>90%) indicates reliability. Providers with very 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>
|
||||
)}
|
||||
{isOutdated && (
|
||||
<Tooltip title="This provider is running an older version of the software. Outdated providers may be unreliable and cause swaps to take longer to complete or fail entirely.">
|
||||
<Chip label="Outdated" icon={<WarningIcon />} color="primary" />
|
||||
</Tooltip>
|
||||
)}
|
||||
<ProviderMarkupChip provider={provider} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ export default function BitcoinLockTxInMempoolPage({
|
|||
loading
|
||||
additionalContent={
|
||||
<>
|
||||
Most swap providers require one confirmation before locking their
|
||||
Most makers require one confirmation before locking their
|
||||
Monero. After they lock their funds and the Monero transaction
|
||||
receives one confirmation, the swap will proceed to the next step.
|
||||
<br />
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default function SwapSetupInflightPage({
|
|||
<CircularProgressWithSubtitle
|
||||
description={
|
||||
<>
|
||||
Starting swap with provider to lock <SatsAmount amount={btc_lock_amount} />
|
||||
Starting swap with maker to lock <SatsAmount amount={btc_lock_amount} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@ export default function InitPage() {
|
|||
const [redeemAddressValid, setRedeemAddressValid] = useState(false);
|
||||
const [refundAddressValid, setRefundAddressValid] = useState(false);
|
||||
|
||||
const selectedProvider = useAppSelector(
|
||||
(state) => state.providers.selectedProvider,
|
||||
const selectedMaker = useAppSelector(
|
||||
(state) => state.makers.selectedMaker,
|
||||
);
|
||||
|
||||
async function init() {
|
||||
await buyXmr(
|
||||
selectedProvider,
|
||||
selectedMaker,
|
||||
useExternalRefundAddress ? refundAddress : null,
|
||||
redeemAddress,
|
||||
);
|
||||
|
|
@ -99,7 +99,7 @@ export default function InitPage() {
|
|||
disabled={
|
||||
(!refundAddressValid && useExternalRefundAddress) ||
|
||||
!redeemAddressValid ||
|
||||
!selectedProvider
|
||||
!selectedMaker
|
||||
}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default function TorInfoBox() {
|
|||
internet. It is a free and open network that is operated by
|
||||
volunteers. You can start and stop Tor by clicking the buttons
|
||||
below. If Tor is running, all traffic will be routed through it and
|
||||
the swap provider will not be able to see your IP address.
|
||||
the maker will not be able to see your IP address.
|
||||
</Typography>
|
||||
<CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split("\n")} />
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ export default function HistoryRowExpanded({
|
|||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Provider Address</TableCell>
|
||||
<TableCell>Maker Address</TableCell>
|
||||
<TableCell>
|
||||
<Box className={classes.outerAddressBox}>
|
||||
{swap.seller.addresses.map((addr) => (
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ import InputAdornment from "@material-ui/core/InputAdornment";
|
|||
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
|
||||
import SwapHorizIcon from "@material-ui/icons/SwapHoriz";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import { ExtendedProviderStatus } from "models/apiModel";
|
||||
import { ExtendedMakerStatus } from "models/apiModel";
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import { satsToBtc } from "utils/conversionUtils";
|
||||
import {
|
||||
ListSellersDialogOpenButton,
|
||||
ProviderSubmitDialogOpenButton,
|
||||
} from "../../modal/provider/ProviderListDialog";
|
||||
import ProviderSelect from "../../modal/provider/ProviderSelect";
|
||||
MakerSubmitDialogOpenButton,
|
||||
} from "../../modal/provider/MakerListDialog";
|
||||
import MakerSelect from "../../modal/provider/MakerSelect";
|
||||
import SwapDialog from "../../modal/swap/SwapDialog";
|
||||
|
||||
// After RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN failed reconnection attempts we can assume the public registry is down
|
||||
|
|
@ -43,7 +43,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
headerText: {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
providerInfo: {
|
||||
makerInfo: {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
swapIconOuter: {
|
||||
|
|
@ -53,12 +53,12 @@ const useStyles = makeStyles((theme) => ({
|
|||
swapIcon: {
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
noProvidersAlertOuter: {
|
||||
noMakersAlertOuter: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
noProvidersAlertButtonsOuter: {
|
||||
noMakersAlertButtonsOuter: {
|
||||
display: "flex",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
|
|
@ -76,17 +76,17 @@ function Title() {
|
|||
);
|
||||
}
|
||||
|
||||
function HasProviderSwapWidget({
|
||||
selectedProvider,
|
||||
function HasMakerSwapWidget({
|
||||
selectedMaker,
|
||||
}: {
|
||||
selectedProvider: ExtendedProviderStatus;
|
||||
selectedMaker: ExtendedMakerStatus;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const forceShowDialog = useAppSelector((state) => state.swap.state !== null);
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const [btcFieldValue, setBtcFieldValue] = useState<number | string>(
|
||||
satsToBtc(selectedProvider.minSwapAmount),
|
||||
satsToBtc(selectedMaker.minSwapAmount),
|
||||
);
|
||||
const [xmrFieldValue, setXmrFieldValue] = useState(1);
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ function HasProviderSwapWidget({
|
|||
setXmrFieldValue(0);
|
||||
} else {
|
||||
const convertedXmrAmount =
|
||||
parsedBtcAmount / satsToBtc(selectedProvider.price);
|
||||
parsedBtcAmount / satsToBtc(selectedMaker.price);
|
||||
setXmrFieldValue(convertedXmrAmount);
|
||||
}
|
||||
}
|
||||
|
|
@ -110,15 +110,15 @@ function HasProviderSwapWidget({
|
|||
if (Number.isNaN(parsedBtcAmount)) {
|
||||
return "This is not a valid number";
|
||||
}
|
||||
if (parsedBtcAmount < satsToBtc(selectedProvider.minSwapAmount)) {
|
||||
if (parsedBtcAmount < satsToBtc(selectedMaker.minSwapAmount)) {
|
||||
return `The minimum swap amount is ${satsToBtc(
|
||||
selectedProvider.minSwapAmount,
|
||||
)} BTC. Switch to a different provider if you want to swap less.`;
|
||||
selectedMaker.minSwapAmount,
|
||||
)} BTC. Switch to a different maker if you want to swap less.`;
|
||||
}
|
||||
if (parsedBtcAmount > satsToBtc(selectedProvider.maxSwapAmount)) {
|
||||
if (parsedBtcAmount > satsToBtc(selectedMaker.maxSwapAmount)) {
|
||||
return `The maximum swap amount is ${satsToBtc(
|
||||
selectedProvider.maxSwapAmount,
|
||||
)} BTC. Switch to a different provider if you want to swap more.`;
|
||||
selectedMaker.maxSwapAmount,
|
||||
)} BTC. Switch to a different maker if you want to swap more.`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ function HasProviderSwapWidget({
|
|||
setShowDialog(true);
|
||||
}
|
||||
|
||||
useEffect(updateXmrValue, [btcFieldValue, selectedProvider]);
|
||||
useEffect(updateXmrValue, [btcFieldValue, selectedMaker]);
|
||||
|
||||
return (
|
||||
// 'elevation' prop can't be passed down (type def issue)
|
||||
|
|
@ -160,7 +160,7 @@ function HasProviderSwapWidget({
|
|||
endAdornment: <InputAdornment position="end">XMR</InputAdornment>,
|
||||
}}
|
||||
/>
|
||||
<ProviderSelect />
|
||||
<MakerSelect />
|
||||
<Fab variant="extended" color="primary" onClick={handleGuideDialogOpen}>
|
||||
<SwapHorizIcon className={classes.swapIcon} />
|
||||
Swap
|
||||
|
|
@ -173,22 +173,22 @@ function HasProviderSwapWidget({
|
|||
);
|
||||
}
|
||||
|
||||
function HasNoProvidersSwapWidget() {
|
||||
function HasNoMakersSwapWidget() {
|
||||
const forceShowDialog = useAppSelector((state) => state.swap.state !== null);
|
||||
const isPublicRegistryDown = useAppSelector((state) =>
|
||||
isRegistryDown(state.providers.registry.connectionFailsCount),
|
||||
isRegistryDown(state.makers.registry.connectionFailsCount),
|
||||
);
|
||||
const classes = useStyles();
|
||||
|
||||
const alertBox = isPublicRegistryDown ? (
|
||||
<Alert severity="info">
|
||||
<Box className={classes.noProvidersAlertOuter}>
|
||||
<Box className={classes.noMakersAlertOuter}>
|
||||
<Typography>
|
||||
Currently, the public registry of providers seems to be unreachable.
|
||||
Currently, the public registry of makers seems to be unreachable.
|
||||
Here's what you can do:
|
||||
<ul>
|
||||
<li>
|
||||
Try discovering a provider by connecting to a rendezvous point
|
||||
Try discovering a maker by connecting to a rendezvous point
|
||||
</li>
|
||||
<li>
|
||||
Try again later when the public registry may be reachable again
|
||||
|
|
@ -202,20 +202,20 @@ function HasNoProvidersSwapWidget() {
|
|||
</Alert>
|
||||
) : (
|
||||
<Alert severity="info">
|
||||
<Box className={classes.noProvidersAlertOuter}>
|
||||
<Box className={classes.noMakersAlertOuter}>
|
||||
<Typography>
|
||||
Currently, there are no providers (trading partners) available in the
|
||||
Currently, there are no makers (trading partners) available in the
|
||||
official registry. Here's what you can do:
|
||||
<ul>
|
||||
<li>
|
||||
Try discovering a provider by connecting to a rendezvous point
|
||||
Try discovering a maker by connecting to a rendezvous point
|
||||
</li>
|
||||
<li>Add a new provider to the public registry</li>
|
||||
<li>Try again later when more providers may be available</li>
|
||||
<li>Add a new maker to the public registry</li>
|
||||
<li>Try again later when more makers may be available</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
<Box>
|
||||
<ProviderSubmitDialogOpenButton />
|
||||
<MakerSubmitDialogOpenButton />
|
||||
<ListSellersDialogOpenButton />
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
@ -225,12 +225,12 @@ function HasNoProvidersSwapWidget() {
|
|||
return (
|
||||
<Box>
|
||||
{alertBox}
|
||||
<SwapDialog open={forceShowDialog} onClose={() => {}} />
|
||||
<SwapDialog open={forceShowDialog} onClose={() => { }} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ProviderLoadingSwapWidget() {
|
||||
function MakerLoadingSwapWidget() {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
|
|
@ -245,21 +245,21 @@ function ProviderLoadingSwapWidget() {
|
|||
}
|
||||
|
||||
export default function SwapWidget() {
|
||||
const selectedProvider = useAppSelector(
|
||||
(state) => state.providers.selectedProvider,
|
||||
const selectedMaker = useAppSelector(
|
||||
(state) => state.makers.selectedMaker,
|
||||
);
|
||||
// If we fail more than RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN reconnect attempts, we'll show the "no providers" widget. We can assume the public registry is down.
|
||||
const providerLoading = useAppSelector(
|
||||
// If we fail more than RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN reconnect attempts, we'll show the "no makers" widget. We can assume the public registry is down.
|
||||
const makerLoading = useAppSelector(
|
||||
(state) =>
|
||||
state.providers.registry.providers === null &&
|
||||
!isRegistryDown(state.providers.registry.connectionFailsCount),
|
||||
state.makers.registry.makers === null &&
|
||||
!isRegistryDown(state.makers.registry.connectionFailsCount),
|
||||
);
|
||||
|
||||
if (providerLoading) {
|
||||
return <ProviderLoadingSwapWidget />;
|
||||
if (makerLoading) {
|
||||
return <MakerLoadingSwapWidget />;
|
||||
}
|
||||
if (selectedProvider) {
|
||||
return <HasProviderSwapWidget selectedProvider={selectedProvider} />;
|
||||
if (selectedMaker) {
|
||||
return <HasMakerSwapWidget selectedMaker={selectedMaker} />;
|
||||
}
|
||||
return <HasNoProvidersSwapWidget />;
|
||||
return <HasNoMakersSwapWidget />;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue