mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-25 06:39:53 -04:00
feat: Reliable Peer Discovery (#408)
* feat(swap): Allow discovery at multiple rendezvous points, request quotes from locally stored peers - Ensure uniqueness of the peer_addresses table (no duplicate entries) - Add peer to local database even if we just request a quote, and no swap (call to list_sellers) - Refactor list_sellers to take multiple rendezvous points - Allow db to be passed into list_sellers, if so request quote from all locally stored peers * feat: editable list of rendezvous points in settings, new maker box on help page * Recover old commits * fix small compile errors due to rebase * amend * fixes * fix(gui): Do not display "Core components are loading..." spinner * fix(gui): Prefer makers with m.minSwapAmount > 0 BTC * feat(cli, gui): Fetch version of maker * feat: display progress bar
This commit is contained in:
parent
686947e8dc
commit
4702bd5bf2
31 changed files with 1869 additions and 512 deletions
|
@ -20,6 +20,7 @@ import {
|
|||
checkContextAvailability,
|
||||
getSwapInfo,
|
||||
initializeContext,
|
||||
listSellersAtRendezvousPoint,
|
||||
updateAllNodeStatuses,
|
||||
} from "./rpc";
|
||||
import { store } from "./store/storeRenderer";
|
||||
|
@ -30,6 +31,9 @@ const TAURI_UNIFIED_EVENT_CHANNEL_NAME = "tauri-unified-event";
|
|||
// Update the public registry every 5 minutes
|
||||
const PROVIDER_UPDATE_INTERVAL = 5 * 60 * 1_000;
|
||||
|
||||
// Discover peers every 5 minutes
|
||||
const DISCOVER_PEERS_INTERVAL = 5 * 60 * 1_000;
|
||||
|
||||
// Update node statuses every 2 minutes
|
||||
const STATUS_UPDATE_INTERVAL = 2 * 60 * 1_000;
|
||||
|
||||
|
@ -50,6 +54,11 @@ export async function setupBackgroundTasks(): Promise<void> {
|
|||
setIntervalImmediate(updateAllNodeStatuses, STATUS_UPDATE_INTERVAL);
|
||||
setIntervalImmediate(updateRates, UPDATE_RATE_INTERVAL);
|
||||
setIntervalImmediate(fetchAllConversations, FETCH_CONVERSATIONS_INTERVAL);
|
||||
setIntervalImmediate(
|
||||
() =>
|
||||
listSellersAtRendezvousPoint(store.getState().settings.rendezvousPoints),
|
||||
DISCOVER_PEERS_INTERVAL,
|
||||
);
|
||||
|
||||
// Fetch all alerts
|
||||
updateAlerts();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Box, Button, LinearProgress, Badge } from "@mui/material";
|
||||
import { Box, Button, LinearProgress, Badge, Typography } from "@mui/material";
|
||||
import { Alert } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAppSelector, usePendingBackgroundProcesses } from "store/hooks";
|
||||
|
@ -159,6 +159,35 @@ function PartialInitStatus({
|
|||
</>
|
||||
</LoadingSpinnerAlert>
|
||||
);
|
||||
case "ListSellers": {
|
||||
const progress = status.progress.content;
|
||||
const totalExpected =
|
||||
progress.rendezvous_points_total + progress.peers_discovered;
|
||||
const totalCompleted =
|
||||
progress.rendezvous_points_connected +
|
||||
progress.quotes_received +
|
||||
progress.quotes_failed;
|
||||
const progressValue =
|
||||
totalExpected > 0 ? (totalCompleted / totalExpected) * 100 : 0;
|
||||
|
||||
return (
|
||||
<AlertWithLinearProgress
|
||||
title={
|
||||
<>
|
||||
Discovering peers
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box color="success.main">
|
||||
{progress.quotes_received} online
|
||||
</Box>
|
||||
<Box color="error.main">{progress.quotes_failed} offline</Box>
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
progress={progressValue}
|
||||
count={totalOfType}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
return exhaustiveGuard(status);
|
||||
}
|
||||
|
@ -181,11 +210,7 @@ export default function DaemonStatusAlert() {
|
|||
|
||||
switch (contextStatus) {
|
||||
case TauriContextStatusEvent.Initializing:
|
||||
return (
|
||||
<LoadingSpinnerAlert severity="warning">
|
||||
Core components are loading
|
||||
</LoadingSpinnerAlert>
|
||||
);
|
||||
return null;
|
||||
case TauriContextStatusEvent.Available:
|
||||
return <Alert severity="success">The daemon is running</Alert>;
|
||||
case TauriContextStatusEvent.Failed:
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { ListSellersResponse } from "models/tauriModel";
|
||||
import { useSnackbar } from "notistack";
|
||||
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 { discoveredMakersByRendezvous } from "store/features/makersSlice";
|
||||
import { useAppDispatch } from "store/hooks";
|
||||
import { isValidMultiAddressWithPeerId } from "utils/parseUtils";
|
||||
|
||||
type ListSellersDialogProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export default function ListSellersDialog({
|
||||
open,
|
||||
onClose,
|
||||
}: ListSellersDialogProps) {
|
||||
const [rendezvousAddress, setRendezvousAddress] = useState("");
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
function handleMultiAddrChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
setRendezvousAddress(event.target.value);
|
||||
}
|
||||
|
||||
function getMultiAddressError(): string | null {
|
||||
return isValidMultiAddressWithPeerId(rendezvousAddress)
|
||||
? null
|
||||
: "Address is invalid or missing peer ID";
|
||||
}
|
||||
|
||||
function handleSuccess({ sellers }: ListSellersResponse) {
|
||||
dispatch(discoveredMakersByRendezvous(sellers));
|
||||
|
||||
const discoveredSellersCount = sellers.length;
|
||||
let message: string;
|
||||
|
||||
switch (discoveredSellersCount) {
|
||||
case 0:
|
||||
message = `No makers were discovered at the rendezvous point`;
|
||||
break;
|
||||
case 1:
|
||||
message = `Discovered one maker at the rendezvous point`;
|
||||
break;
|
||||
default:
|
||||
message = `Discovered ${discoveredSellersCount} makers at the rendezvous point`;
|
||||
}
|
||||
|
||||
enqueueSnackbar(message, {
|
||||
variant: "success",
|
||||
autoHideDuration: 5000,
|
||||
});
|
||||
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<DialogTitle>Discover makers</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<DialogContentText>
|
||||
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 makers and then connect and swap with them.
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="Rendezvous point"
|
||||
fullWidth
|
||||
helperText={
|
||||
getMultiAddressError() || "Multiaddress of the rendezvous point"
|
||||
}
|
||||
value={rendezvousAddress}
|
||||
onChange={handleMultiAddrChange}
|
||||
placeholder="/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE"
|
||||
error={!!getMultiAddressError()}
|
||||
/>
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 1 }}>
|
||||
{PRESET_RENDEZVOUS_POINTS.map((rAddress) => (
|
||||
<Chip
|
||||
key={rAddress}
|
||||
clickable
|
||||
label={<TruncatedText limit={30}>{rAddress}</TruncatedText>}
|
||||
onClick={() => setRendezvousAddress(rAddress)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<PromiseInvokeButton
|
||||
variant="contained"
|
||||
disabled={
|
||||
// We disable the button if the multiaddress is invalid
|
||||
getMultiAddressError() !== null
|
||||
}
|
||||
color="primary"
|
||||
onSuccess={handleSuccess}
|
||||
onInvoke={() => listSellersAtRendezvousPoint(rendezvousAddress)}
|
||||
>
|
||||
Connect
|
||||
</PromiseInvokeButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -86,14 +86,12 @@ export default function MakerInfo({ maker }: { maker: ExtendedMakerStatus }) {
|
|||
<Chip label={`${Math.round(maker.uptime * 100)}% uptime`} />
|
||||
</Tooltip>
|
||||
)}
|
||||
{maker.age ? (
|
||||
{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">
|
||||
|
@ -105,6 +103,11 @@ export default function MakerInfo({ maker }: { maker: ExtendedMakerStatus }) {
|
|||
<Chip label="Outdated" icon={<WarningIcon />} color="primary" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{maker.version && (
|
||||
<Tooltip title="The version of the maker's software">
|
||||
<Chip label={`v${maker.version}`} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<MakerMarkupChip maker={maker} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
@ -15,7 +15,6 @@ import { ExtendedMakerStatus } from "models/apiModel";
|
|||
import { useState } from "react";
|
||||
import { setSelectedMaker } from "store/features/makersSlice";
|
||||
import { useAllMakers, useAppDispatch } from "store/hooks";
|
||||
import ListSellersDialog from "../listSellers/ListSellersDialog";
|
||||
import MakerInfo from "./MakerInfo";
|
||||
import MakerSubmitDialog from "./MakerSubmitDialog";
|
||||
|
||||
|
@ -50,30 +49,6 @@ export function MakerSubmitDialogOpenButton() {
|
|||
);
|
||||
}
|
||||
|
||||
export function ListSellersDialogOpenButton() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<ListItemButton
|
||||
autoFocus
|
||||
onClick={() => {
|
||||
// Prevents background from being clicked and reopening dialog
|
||||
if (!open) {
|
||||
setOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListSellersDialog open={open} onClose={() => setOpen(false)} />
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<SearchIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="Discover makers by connecting to a rendezvous point" />
|
||||
</ListItemButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MakerListDialog({
|
||||
open,
|
||||
onClose,
|
||||
|
@ -99,7 +74,6 @@ export default function MakerListDialog({
|
|||
<MakerInfo maker={maker} key={maker.peerId} />
|
||||
</ListItemButton>
|
||||
))}
|
||||
<ListSellersDialogOpenButton />
|
||||
<MakerSubmitDialogOpenButton />
|
||||
</List>
|
||||
</DialogContent>
|
||||
|
|
58
src-gui/src/renderer/components/pages/help/DiscoveryBox.tsx
Normal file
58
src-gui/src/renderer/components/pages/help/DiscoveryBox.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { Box, Typography, styled } from "@mui/material";
|
||||
import InfoBox from "renderer/components/modal/swap/InfoBox";
|
||||
import { useSettings } from "store/hooks";
|
||||
import { Search } from "@mui/icons-material";
|
||||
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||
import { listSellersAtRendezvousPoint } from "renderer/rpc";
|
||||
import { useAppDispatch } from "store/hooks";
|
||||
import { discoveredMakersByRendezvous } from "store/features/makersSlice";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
const StyledPromiseButton = styled(PromiseInvokeButton)(({ theme }) => ({
|
||||
marginTop: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export default function DiscoveryBox() {
|
||||
const rendezvousPoints = useSettings((s) => s.rendezvousPoints);
|
||||
const dispatch = useAppDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const handleDiscovery = async () => {
|
||||
const { sellers } = await listSellersAtRendezvousPoint(rendezvousPoints);
|
||||
dispatch(discoveredMakersByRendezvous(sellers));
|
||||
};
|
||||
|
||||
return (
|
||||
<InfoBox
|
||||
title={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
Discover Makers
|
||||
</Box>
|
||||
}
|
||||
mainContent={
|
||||
<Typography variant="subtitle2">
|
||||
By connecting to rendezvous points run by volunteers, you can discover
|
||||
makers and then connect and swap with them in a decentralized manner.
|
||||
You have {rendezvousPoints.length} stored rendezvous{" "}
|
||||
{rendezvousPoints.length === 1 ? "point" : "points"} which we will
|
||||
connect to. We will also attempt to connect to peers which you have
|
||||
previously connected to.
|
||||
</Typography>
|
||||
}
|
||||
additionalContent={
|
||||
<StyledPromiseButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onInvoke={handleDiscovery}
|
||||
disabled={rendezvousPoints.length === 0}
|
||||
startIcon={<Search />}
|
||||
displayErrorSnackbar
|
||||
>
|
||||
Discover Makers
|
||||
</StyledPromiseButton>
|
||||
}
|
||||
icon={null}
|
||||
loading={false}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -21,19 +21,20 @@ import {
|
|||
Switch,
|
||||
SelectChangeEvent,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
removeNode,
|
||||
resetSettings,
|
||||
setFetchFiatPrices,
|
||||
setFiatCurrency,
|
||||
} from "store/features/settingsSlice";
|
||||
import {
|
||||
addNode,
|
||||
addRendezvousPoint,
|
||||
Blockchain,
|
||||
FiatCurrency,
|
||||
moveUpNode,
|
||||
Network,
|
||||
removeNode,
|
||||
removeRendezvousPoint,
|
||||
resetSettings,
|
||||
setFetchFiatPrices,
|
||||
setFiatCurrency,
|
||||
setTheme,
|
||||
setTorEnabled,
|
||||
} from "store/features/settingsSlice";
|
||||
import { useAppDispatch, useNodes, useSettings } from "store/hooks";
|
||||
import ValidatedTextField from "renderer/components/other/ValidatedTextField";
|
||||
|
@ -49,8 +50,8 @@ import {
|
|||
} from "@mui/icons-material";
|
||||
import { getNetwork } from "store/config";
|
||||
import { currencySymbol } from "utils/formatUtils";
|
||||
import { setTorEnabled } from "store/features/settingsSlice";
|
||||
import InfoBox from "renderer/components/modal/swap/InfoBox";
|
||||
import { isValidMultiAddressWithPeerId } from "utils/parseUtils";
|
||||
|
||||
const PLACEHOLDER_ELECTRUM_RPC_URL = "ssl://blockstream.info:700";
|
||||
const PLACEHOLDER_MONERO_NODE_URL = "http://xmr-node.cakewallet.com:18081";
|
||||
|
@ -85,6 +86,7 @@ export default function SettingsBox() {
|
|||
<MoneroNodeUrlSetting />
|
||||
<FetchFiatPricesSetting />
|
||||
<ThemeSetting />
|
||||
<RendezvousPointsSetting />
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
@ -590,3 +592,121 @@ export function TorSettings() {
|
|||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A setting that allows you to manage rendezvous points for maker discovery
|
||||
*/
|
||||
function RendezvousPointsSetting() {
|
||||
const [tableVisible, setTableVisible] = useState(false);
|
||||
const rendezvousPoints = useSettings((s) => s.rendezvousPoints);
|
||||
const dispatch = useAppDispatch();
|
||||
const [newPoint, setNewPoint] = useState("");
|
||||
|
||||
const onAddNewPoint = () => {
|
||||
dispatch(addRendezvousPoint(newPoint));
|
||||
setNewPoint("");
|
||||
};
|
||||
|
||||
const onRemovePoint = (point: string) => {
|
||||
dispatch(removeRendezvousPoint(point));
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<SettingLabel
|
||||
label="Rendezvous Points"
|
||||
tooltip="These are the points where makers can be discovered. Add custom rendezvous points here to expand your maker discovery options."
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton onClick={() => setTableVisible(true)}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
{tableVisible && (
|
||||
<Dialog
|
||||
open={true}
|
||||
onClose={() => setTableVisible(false)}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>Rendezvous Points</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="subtitle2">
|
||||
Add or remove rendezvous points where makers can be discovered.
|
||||
These points help you find trading partners in a decentralized
|
||||
way.
|
||||
</Typography>
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
style={{ marginTop: "1rem" }}
|
||||
elevation={0}
|
||||
>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{ width: "85%" }}>
|
||||
Rendezvous Point
|
||||
</TableCell>
|
||||
<TableCell style={{ width: "15%" }} align="right">
|
||||
Actions
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rendezvousPoints.map((point, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell style={{ wordBreak: "break-all" }}>
|
||||
<Typography variant="overline">{point}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Tooltip title="Remove this rendezvous point">
|
||||
<IconButton onClick={() => onRemovePoint(point)}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<ValidatedTextField
|
||||
label="Add new rendezvous point"
|
||||
value={newPoint}
|
||||
onValidatedChange={setNewPoint}
|
||||
placeholder="/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE"
|
||||
fullWidth
|
||||
isValid={isValidMultiAddressWithPeerId}
|
||||
variant="outlined"
|
||||
noErrorWhenEmpty
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Tooltip title="Add this rendezvous point">
|
||||
<IconButton
|
||||
onClick={onAddNewPoint}
|
||||
disabled={
|
||||
!isValidMultiAddressWithPeerId(newPoint) ||
|
||||
newPoint.length === 0
|
||||
}
|
||||
>
|
||||
<Add />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setTableVisible(false)} size="large">
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import DonateInfoBox from "./DonateInfoBox";
|
|||
import DaemonControlBox from "./DaemonControlBox";
|
||||
import SettingsBox from "./SettingsBox";
|
||||
import ExportDataBox from "./ExportDataBox";
|
||||
import DiscoveryBox from "./DiscoveryBox";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
|
||||
|
@ -27,6 +28,7 @@ export default function SettingsPage() {
|
|||
}}
|
||||
>
|
||||
<SettingsBox />
|
||||
<DiscoveryBox />
|
||||
<ExportDataBox />
|
||||
<DaemonControlBox />
|
||||
<DonateInfoBox />
|
||||
|
|
|
@ -14,10 +14,7 @@ import { ExtendedMakerStatus } from "models/apiModel";
|
|||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import { useAppSelector } from "store/hooks";
|
||||
import { satsToBtc } from "utils/conversionUtils";
|
||||
import {
|
||||
ListSellersDialogOpenButton,
|
||||
MakerSubmitDialogOpenButton,
|
||||
} from "../../modal/provider/MakerListDialog";
|
||||
import { MakerSubmitDialogOpenButton } from "../../modal/provider/MakerListDialog";
|
||||
import MakerSelect from "../../modal/provider/MakerSelect";
|
||||
import SwapDialog from "../../modal/swap/SwapDialog";
|
||||
|
||||
|
@ -162,7 +159,7 @@ function HasNoMakersSwapWidget() {
|
|||
</ul>
|
||||
</Typography>
|
||||
<Box>
|
||||
<ListSellersDialogOpenButton />
|
||||
<MakerSubmitDialogOpenButton />
|
||||
</Box>
|
||||
</Box>
|
||||
</Alert>
|
||||
|
@ -180,7 +177,6 @@ function HasNoMakersSwapWidget() {
|
|||
</Typography>
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<MakerSubmitDialogOpenButton />
|
||||
<ListSellersDialogOpenButton />
|
||||
</Box>
|
||||
</Box>
|
||||
</Alert>
|
||||
|
|
|
@ -43,13 +43,13 @@ import { CliLog } from "models/cliModel";
|
|||
import { logsToRawString, parseLogsFromString } from "utils/parseUtils";
|
||||
|
||||
export const PRESET_RENDEZVOUS_POINTS = [
|
||||
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
||||
"/dnsaddr/xxmr.cheap/p2p/12D3KooWMk3QyPS8D1d1vpHZoY7y2MnXdPE5yV6iyPvyuj4zcdxT",
|
||||
];
|
||||
|
||||
export async function fetchSellersAtPresetRendezvousPoints() {
|
||||
await Promise.all(
|
||||
PRESET_RENDEZVOUS_POINTS.map(async (rendezvousPoint) => {
|
||||
const response = await listSellersAtRendezvousPoint(rendezvousPoint);
|
||||
const response = await listSellersAtRendezvousPoint([rendezvousPoint]);
|
||||
store.dispatch(discoveredMakersByRendezvous(response.sellers));
|
||||
|
||||
logger.info(
|
||||
|
@ -206,10 +206,10 @@ export async function redactLogs(
|
|||
}
|
||||
|
||||
export async function listSellersAtRendezvousPoint(
|
||||
rendezvousPointAddress: string,
|
||||
rendezvousPointAddresses: string[],
|
||||
): Promise<ListSellersResponse> {
|
||||
return await invoke<ListSellersArgs, ListSellersResponse>("list_sellers", {
|
||||
rendezvous_point: rendezvousPointAddress,
|
||||
rendezvous_points: rendezvousPointAddresses,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { ExtendedMakerStatus, MakerStatus } from "models/apiModel";
|
||||
import { Seller } from "models/tauriModel";
|
||||
import { SellerStatus } from "models/tauriModel";
|
||||
import { getStubTestnetMaker } from "store/config";
|
||||
import { rendezvousSellerToMakerStatus } from "utils/conversionUtils";
|
||||
import { isMakerOutdated } from "utils/multiAddrUtils";
|
||||
|
@ -60,7 +60,7 @@ export const makersSlice = createSlice({
|
|||
name: "providers",
|
||||
initialState,
|
||||
reducers: {
|
||||
discoveredMakersByRendezvous(slice, action: PayloadAction<Seller[]>) {
|
||||
discoveredMakersByRendezvous(slice, action: PayloadAction<SellerStatus[]>) {
|
||||
action.payload.forEach((discoveredSeller) => {
|
||||
const discoveredMakerStatus =
|
||||
rendezvousSellerToMakerStatus(discoveredSeller);
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { Theme } from "renderer/components/theme";
|
||||
|
||||
const DEFAULT_RENDEZVOUS_POINTS = [
|
||||
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
||||
"/dns4/discover2.unstoppableswap.net/tcp/8888/p2p/12D3KooWGRvf7qVQDrNR5nfYD6rKrbgeTi9x8RrbdxbmsPvxL4mw",
|
||||
"/dns4/darkness.su/tcp/8888/p2p/12D3KooWFQAgVVS9t9UgL6v1sLprJVM7am5hFK7vy9iBCCoCBYmU",
|
||||
];
|
||||
|
||||
export interface SettingsState {
|
||||
/// This is an ordered list of node urls for each network and blockchain
|
||||
nodes: Record<Network, Record<Blockchain, string[]>>;
|
||||
|
@ -12,6 +18,8 @@ export interface SettingsState {
|
|||
/// Whether to enable Tor for p2p connections
|
||||
enableTor: boolean;
|
||||
userHasSeenIntroduction: boolean;
|
||||
/// List of rendezvous points
|
||||
rendezvousPoints: string[];
|
||||
}
|
||||
|
||||
export enum FiatCurrency {
|
||||
|
@ -112,6 +120,7 @@ const initialState: SettingsState = {
|
|||
fiatCurrency: FiatCurrency.Usd,
|
||||
enableTor: true,
|
||||
userHasSeenIntroduction: false,
|
||||
rendezvousPoints: DEFAULT_RENDEZVOUS_POINTS,
|
||||
};
|
||||
|
||||
const alertsSlice = createSlice({
|
||||
|
@ -147,6 +156,14 @@ const alertsSlice = createSlice({
|
|||
setFiatCurrency(slice, action: PayloadAction<FiatCurrency>) {
|
||||
slice.fiatCurrency = action.payload;
|
||||
},
|
||||
addRendezvousPoint(slice, action: PayloadAction<string>) {
|
||||
slice.rendezvousPoints.push(action.payload);
|
||||
},
|
||||
removeRendezvousPoint(slice, action: PayloadAction<string>) {
|
||||
slice.rendezvousPoints = slice.rendezvousPoints.filter(
|
||||
(point) => point !== action.payload,
|
||||
);
|
||||
},
|
||||
addNode(
|
||||
slice,
|
||||
action: PayloadAction<{
|
||||
|
@ -202,6 +219,8 @@ export const {
|
|||
setFiatCurrency,
|
||||
setTorEnabled,
|
||||
setUserHasSeenIntroduction,
|
||||
addRendezvousPoint,
|
||||
removeRendezvousPoint,
|
||||
} = alertsSlice.actions;
|
||||
|
||||
export default alertsSlice.reducer;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { MakerStatus } from "models/apiModel";
|
||||
import { Seller } from "models/tauriModel";
|
||||
import { MakerStatus, ExtendedMakerStatus } from "models/apiModel";
|
||||
import { SellerStatus } from "models/tauriModel";
|
||||
import { isTestnet } from "store/config";
|
||||
import { splitPeerIdFromMultiAddress } from "./parseUtils";
|
||||
|
||||
|
@ -48,21 +48,20 @@ export function secondsToDays(seconds: number): number {
|
|||
// which we use internally to represent the status of a provider. This provides consistency between
|
||||
// the models returned by the public registry and the models used internally.
|
||||
export function rendezvousSellerToMakerStatus(
|
||||
seller: Seller,
|
||||
): MakerStatus | null {
|
||||
if (seller.status.type === "Unreachable") {
|
||||
seller: SellerStatus,
|
||||
): ExtendedMakerStatus | null {
|
||||
if (seller.type === "Unreachable") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [multiAddr, peerId] = splitPeerIdFromMultiAddress(seller.multiaddr);
|
||||
|
||||
return {
|
||||
maxSwapAmount: seller.status.content.max_quantity,
|
||||
minSwapAmount: seller.status.content.min_quantity,
|
||||
price: seller.status.content.price,
|
||||
peerId,
|
||||
multiAddr,
|
||||
maxSwapAmount: seller.content.quote.max_quantity,
|
||||
minSwapAmount: seller.content.quote.min_quantity,
|
||||
price: seller.content.quote.price,
|
||||
peerId: seller.content.peer_id,
|
||||
multiAddr: seller.content.multiaddr,
|
||||
testnet: isTestnet(),
|
||||
version: seller.content.version,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ export function sortMakerList(list: ExtendedMakerStatus[]) {
|
|||
(m) => (m.relevancy == null ? 1 : 0),
|
||||
// Prefer makers with a higher relevancy score
|
||||
(m) => -(m.relevancy ?? 0),
|
||||
// Prefer makers with a minimum quantity > 0
|
||||
(m) => ((m.minSwapAmount ?? 0) > 0 ? 0 : 1),
|
||||
// Prefer makers with a lower price
|
||||
(m) => m.price,
|
||||
],
|
||||
|
|
|
@ -1033,7 +1033,7 @@
|
|||
dependencies:
|
||||
"@tauri-apps/api" "^2.0.0"
|
||||
|
||||
"@tauri-apps/plugin-updater@>=2.7.1":
|
||||
"@tauri-apps/plugin-updater@2.7.1":
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-updater/-/plugin-updater-2.7.1.tgz#82adcfd06cdd4bc6d64343c8934b700c9174913a"
|
||||
integrity sha512-1OPqEY/z7NDVSeTEMIhD2ss/vXWdpfZ5Th2Mk0KtPR/RA6FKuOTDGZQhxoyYBk0pcZJ+nNZUbl/IujDCLBApjA==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue