mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-03 15:24:53 -04: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
|
@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
- 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
|
||||
|
||||
## [1.0.0-rc.6] - 2024-11-21
|
||||
|
||||
- CLI + GUI: Tor is now bundled with the application. All libp2p connections between peers are routed through Tor, if the `--enable-tor` flag is set. The `--tor-socks5-port` argument has been removed. This feature is powered by [arti](https://tpo.pages.torproject.net/core/arti/), an implementation of the Tor protocol in Rust by the Tor Project.
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Table, Td, Th, Tr } from 'nextra/components'
|
||||
|
||||
export default function SwapProviderTable() {
|
||||
export default function SwapMakerTable() {
|
||||
function satsToBtc(sats) {
|
||||
return sats / 100000000;
|
||||
}
|
||||
|
||||
async function getProviders() {
|
||||
async function getMakers() {
|
||||
const response = await fetch("https://api.unstoppableswap.net/api/list");
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
const [providers, setProviders] = useState([]);
|
||||
const [makers, setMakers] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
getProviders().then((data) => {
|
||||
setProviders(data);
|
||||
getMakers().then((data) => {
|
||||
setMakers(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
@ -38,16 +38,16 @@ export default function SwapProviderTable() {
|
|||
</Tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{providers.map((provider) => (
|
||||
<Tr key={provider.peerId}>
|
||||
{makers.map((maker) => (
|
||||
<Tr key={maker.peerId}>
|
||||
<Td>
|
||||
{provider.testnet ? "Testnet" : "Mainnet"}
|
||||
{maker.testnet ? "Testnet" : "Mainnet"}
|
||||
</Td>
|
||||
<Td>{provider.multiAddr}</Td>
|
||||
<Td>{provider.peerId}</Td>
|
||||
<Td>{satsToBtc(provider.minSwapAmount)} BTC</Td>
|
||||
<Td>{satsToBtc(provider.maxSwapAmount)} BTC</Td>
|
||||
<Td>{satsToBtc(provider.price)} XMR/BTC</Td>
|
||||
<Td>{maker.multiAddr}</Td>
|
||||
<Td>{maker.peerId}</Td>
|
||||
<Td>{satsToBtc(maker.minSwapAmount)} BTC</Td>
|
||||
<Td>{satsToBtc(maker.maxSwapAmount)} BTC</Td>
|
||||
<Td>{satsToBtc(maker.price)} XMR/BTC</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"first_swap": "Complete your first swap",
|
||||
"market_maker_discovery": "Swap Provider discovery",
|
||||
"market_maker_discovery": "Maker discovery",
|
||||
"refund_punish": "Cancel, Refund and Punish explained"
|
||||
}
|
|
@ -16,16 +16,16 @@ import { Steps } from 'nextra/components'
|
|||
|
||||
## Performing the swap
|
||||
<Steps>
|
||||
### Choose a _Swap Provider_ to swap with
|
||||
### Choose a _maker_ to swap with
|
||||
|
||||
In the bottom of the screen you can see the currently selected _Swap Provider_.
|
||||
In the bottom of the screen you can see the currently selected _maker_.
|
||||
This is who you'll send your Bitcoin to and who you'll receive the Monero from.
|
||||
You can change the _Swap Provider_ by clicking on the arrow and selecting a different _Swap Provider_ from the list.
|
||||
You can change the _maker_ by clicking on the arrow and selecting a different _maker_ from the list.
|
||||
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="info">
|
||||
Different _Swap Providers_ offer different exchange rates and differing amounts of liquidity. You may want to choose the _Swap Provider_ that best suits your needs.
|
||||
Different _makers_ offer different exchange rates and differing amounts of liquidity. You may want to choose the _maker_ that best suits your needs.
|
||||
</Callout>
|
||||
|
||||
You can use the input field to calculate the approximate amount of Monero you'll receive for a given amount of Bitcoin.
|
||||
|
@ -39,7 +39,7 @@ This is only used as a reference for you to get a rough idea of how much Monero
|
|||
|
||||
### Start the Swap
|
||||
|
||||
Once you've selected a _Swap Provider_, you can start the swap by clicking the `Swap` button.
|
||||
Once you've selected a _maker_, you can start the swap by clicking the `Swap` button.
|
||||
This will open a new window where you need to enter two addresses:
|
||||
|
||||
1. the Monero address you want to receive the Monero to
|
||||
|
@ -54,7 +54,7 @@ After pressing the <img src="/start_swap_button.png" style={{
|
|||
display: "inline-block",
|
||||
// center vertically
|
||||
verticalAlign: "middle",
|
||||
}}/> button, you'll be shown an offer by the _Swap Provider_. This includes:
|
||||
}}/> button, you'll be shown an offer by the _maker_. This includes:
|
||||
|
||||
- the exchange rate (how much Bitcoin they demand for 1 Monero)
|
||||
- the minimum and maximum amounts you can swap
|
||||
|
@ -88,10 +88,10 @@ The swap will go through four stages:
|
|||
1. **Locking the Bitcoin**:
|
||||
Your Bitcoin is locked in a 2-of-2 multisig address.
|
||||
|
||||
2. **_Swap Provider_ locks the Monero**:
|
||||
2. **_Maker_ locks the Monero**:
|
||||
The other party locks their Monero as well.
|
||||
|
||||
3. **_Swap Provider_ redeems _Bitcoin_**:
|
||||
3. **_Maker_ redeems _Bitcoin_**:
|
||||
The other party redeems the Bitcoin.
|
||||
|
||||
4. **Redeeming the Monero**:
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# _Swap Provider_ discovery
|
||||
# _Maker_ discovery
|
||||
|
||||
A _Swap Provider_ is a service run by a pseudonymous entity that offers to sell Monero in exchange for Bitcoin. To swap your Bitcoin for Monero you need to connect to a one of these _Swap Providers_.
|
||||
The different ways to discover _Swap Providers_ are described below.
|
||||
A _maker_ is a service run by a pseudonymous entity that offers to sell Monero in exchange for Bitcoin. To swap your Bitcoin for Monero you need to connect to a one of these _makers_.
|
||||
The different ways to discover _makers_ are described below.
|
||||
|
||||
There are two ways to discover _Swap Providers_:
|
||||
There are two ways to discover _makers_:
|
||||
|
||||
1. **Public Registry**: Community volunteers maintain a list of _Swap Providers_ that is provided to the GUI and is kept up to date automatically. This list is displayed in the GUI by default. The _Public Registry_ also stores additional information about the _Swap Providers_ such as their uptime and age, and makes it available to the GUI.
|
||||
2. **Rendezvous**: The GUI can discover Swap Providers using the [_Rendezvous_ protocol](https://docs.libp2p.io/concepts/discovery-routing/rendezvous/). This protocol enables the GUI to find providers that register themselves at a _Rendezvous Point_. The GUI can query these points to get a list of registered providers. _Rendezvous Points_ are operated by community volunteers, and anyone can run one. The GUI can connect to various _Rendezvous Points_ to discover different _Swap Providers_.
|
||||
1. **Public Registry**: Community volunteers maintain a list of _makers_ that is provided to the GUI and is kept up to date automatically. This list is displayed in the GUI by default. The _Public Registry_ also stores additional information about the _makers_ such as their uptime and age, and makes it available to the GUI.
|
||||
2. **Rendezvous**: The GUI can discover makers using the [_Rendezvous_ protocol](https://docs.libp2p.io/concepts/discovery-routing/rendezvous/). This protocol enables the GUI to find makers that register themselves at a _Rendezvous Point_. The GUI can query these points to get a list of registered makers. _Rendezvous Points_ are operated by community volunteers, and anyone can run one. The GUI can connect to various _Rendezvous Points_ to discover different _makers_.
|
||||
|
||||
## _Public Registry_
|
||||
|
||||
The providers from the registry are displayed in the GUI. If you want to connect to them directly without the GUI choose one from the table below.
|
||||
The makers from the registry are displayed in the GUI. If you want to connect to them directly without the GUI choose one from the table below.
|
||||
|
||||
import SwapProviderTable from "../../components/SwapProviderTable";
|
||||
|
||||
|
@ -19,9 +19,9 @@ import SwapProviderTable from "../../components/SwapProviderTable";
|
|||
<SwapProviderTable />
|
||||
</div>
|
||||
|
||||
## How to discover _Swap Providers_ via _Rendezvous_
|
||||
## How to discover _makers_ via _Rendezvous_
|
||||
|
||||
1. Open the _Swap Provider_ list by clicking the right-facing arrow in the widget on the _Swap_ tab.
|
||||
1. Open the _maker_ list by clicking the right-facing arrow in the widget on the _Swap_ tab.
|
||||
|
||||
<img src="/rendezvous_1.png" />
|
||||
|
||||
|
@ -30,14 +30,14 @@ import SwapProviderTable from "../../components/SwapProviderTable";
|
|||
display: "inline-block",
|
||||
// center vertically
|
||||
verticalAlign: "middle",
|
||||
}}/> button to open the _Discover swap providers_ dialog. Enter the _Multiaddress_ of the _Rendezvous Point_ you want to connect to. You can also choose one of the predined ones from the list below the Textfield. Click the _Connect_ button to connect to the rendezvous point.
|
||||
}}/> button to open the _Discover makers_ dialog. Enter the _Multiaddress_ of the _Rendezvous Point_ you want to connect to. You can also choose one of the predined ones from the list below the Textfield. Click the _Connect_ button to connect to the rendezvous point.
|
||||
<img src="/rendezvous_2.png" />
|
||||
|
||||
## How to add a _Swap Provider_ to the _Public Registry_
|
||||
## How to add a _maker_ to the _Public Registry_
|
||||
|
||||
If you know of a _Swap Provider_ that is not yet in the _Public Registry_, you can submit it manually. Here's how you can do it:
|
||||
If you know of a _maker_ that is not yet in the _Public Registry_, you can submit it manually. Here's how you can do it:
|
||||
|
||||
1. Open the _Swap Provider_ list by clicking the right-facing arrow in the widget on the _Swap_ tab.
|
||||
1. Open the _maker_ list by clicking the right-facing arrow in the widget on the _Swap_ tab.
|
||||
|
||||
<img src="/rendezvous_1.png" />
|
||||
|
||||
|
@ -46,5 +46,5 @@ If you know of a _Swap Provider_ that is not yet in the _Public Registry_, you c
|
|||
display: "inline-block",
|
||||
// center vertically
|
||||
verticalAlign: "middle",
|
||||
}}/> button. Enter the _Multiaddress_ of the _Swap Provider_ as well as the _Peer ID_ of the provider. Click the _Submit_ button to submit the provider to the _Public Registry_.
|
||||
}}/> button. Enter the _Multiaddress_ of the _maker_ as well as the _Peer ID_ of the provider. Click the _Submit_ button to submit the provider to the _Public Registry_.
|
||||
<img src="/public_registry.png" />
|
||||
|
|
|
@ -8,17 +8,17 @@ We have chosen to include a fairly technical explanation here. However, the GUI
|
|||
|
||||
## Cancel
|
||||
|
||||
If the _Swap Provider_ has not been able to redeem the Bitcoin within 12 hours (72 Bitcoin blocks) from the start of the swap, the swap will be cancelled.
|
||||
This is done by either you or the _Swap Provider_ publishing a special Bitcoin transaction called the `Bitcoin Cancel Transaction`.
|
||||
If the _maker_ has not been able to redeem the Bitcoin within 12 hours (72 Bitcoin blocks) from the start of the swap, the swap will be cancelled.
|
||||
This is done by either you or the _maker_ publishing a special Bitcoin transaction called the `Bitcoin Cancel Transaction`.
|
||||
As soon as this transaction is included in the Bitcoin blockchain, the swap is locked in a state where only the [_Refund_](#refund) and [_Punish_](#punish) paths can be activated. The _Happy Path_ path where you redeem the Monero is no longer possible.
|
||||
|
||||
## Refund
|
||||
|
||||
As soon as the swap is cancelled, you can refund your Bitcoin. This is done by publishing the `Bitcoin Refund Transaction` on the Bitcoin blockchain.
|
||||
If this is done within 24 hours (144 Bitcoin blocks) from the inclusion of the `Bitcoin Cancel Transaction`, you will get your Bitcoin back.
|
||||
If you do not refund your Bitcoin within this time frame, the _Swap Provider_ can punish you. This is a security measure to ensure that you do not cancel the swap and then refuse to refund your Bitcoin which would result in the _Swap Provider_ losing their Monero.
|
||||
If you do not refund your Bitcoin within this time frame, the _maker_ can punish you. This is a security measure to ensure that you do not cancel the swap and then refuse to refund your Bitcoin which would result in the _maker_ losing their Monero.
|
||||
|
||||
## Punish
|
||||
|
||||
If you do not refund your Bitcoin within 24 hours (144 Bitcoin blocks) from the inclusion of the `Bitcoin Cancel Transaction`, the _Swap Provider_ will _punish_ you. This will result in the _Swap Provider_ taking your Bitcoin as a penalty for not refunding it in time.
|
||||
Even if this state is reached and the _Swap Provider_ has punished you, there's still hope to redeem the Monero. The _Swap Provider_ can choose to allow you to redeem the Monero by transmitting a secret key to you. This however is at the discretion of the _Swap Provider_ and they are not obligated to do so.
|
||||
If you do not refund your Bitcoin within 24 hours (144 Bitcoin blocks) from the inclusion of the `Bitcoin Cancel Transaction`, the _maker_ will _punish_ you. This will result in the _maker_ taking your Bitcoin as a penalty for not refunding it in time.
|
||||
Even if this state is reached and the _maker_ has punished you, there's still hope to redeem the Monero. The _maker_ can choose to allow you to redeem the Monero by transmitting a secret key to you. This however is at the discretion of the _maker_ and they are not obligated to do so.
|
|
@ -29,6 +29,7 @@
|
|||
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||
"@types/react-redux": "^7.1.34",
|
||||
"humanize-duration": "^3.32.1",
|
||||
"jdenticon": "^3.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"multiaddr": "^10.0.1",
|
||||
"notistack": "^3.0.1",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export interface ExtendedProviderStatus extends ProviderStatus {
|
||||
export interface ExtendedMakerStatus extends MakerStatus {
|
||||
uptime?: number;
|
||||
age?: number;
|
||||
relevancy?: number;
|
||||
|
@ -6,15 +6,15 @@ export interface ExtendedProviderStatus extends ProviderStatus {
|
|||
recommended?: boolean;
|
||||
}
|
||||
|
||||
export interface ProviderStatus extends ProviderQuote, Provider {}
|
||||
export interface MakerStatus extends MakerQuote, Maker { }
|
||||
|
||||
export interface ProviderQuote {
|
||||
export interface MakerQuote {
|
||||
price: number;
|
||||
minSwapAmount: number;
|
||||
maxSwapAmount: number;
|
||||
}
|
||||
|
||||
export interface Provider {
|
||||
export interface Maker {
|
||||
multiAddr: string;
|
||||
testnet: boolean;
|
||||
peerId: string;
|
||||
|
|
|
@ -5,21 +5,21 @@
|
|||
// - and to submit feedback
|
||||
// - fetch currency rates from CoinGecko
|
||||
|
||||
import { Alert, ExtendedProviderStatus } from "models/apiModel";
|
||||
import { Alert, ExtendedMakerStatus } from "models/apiModel";
|
||||
import { store } from "./store/storeRenderer";
|
||||
import { setBtcPrice, setXmrBtcRate, setXmrPrice } from "store/features/ratesSlice";
|
||||
import { FiatCurrency } from "store/features/settingsSlice";
|
||||
import { setAlerts } from "store/features/alertsSlice";
|
||||
import { registryConnectionFailed, setRegistryProviders } from "store/features/providersSlice";
|
||||
import { registryConnectionFailed, setRegistryMakers } from "store/features/makersSlice";
|
||||
import logger from "utils/logger";
|
||||
|
||||
const PUBLIC_REGISTRY_API_BASE_URL = "https://api.unstoppableswap.net";
|
||||
|
||||
async function fetchProvidersViaHttp(): Promise<
|
||||
ExtendedProviderStatus[]
|
||||
async function fetchMakersViaHttp(): Promise<
|
||||
ExtendedMakerStatus[]
|
||||
> {
|
||||
const response = await fetch(`${PUBLIC_REGISTRY_API_BASE_URL}/api/list`);
|
||||
return (await response.json()) as ExtendedProviderStatus[];
|
||||
return (await response.json()) as ExtendedMakerStatus[];
|
||||
}
|
||||
|
||||
async function fetchAlertsViaHttp(): Promise<Alert[]> {
|
||||
|
@ -114,8 +114,8 @@ export async function updateRates(): Promise<void> {
|
|||
*/
|
||||
export async function updatePublicRegistry(): Promise<void> {
|
||||
try {
|
||||
const providers = await fetchProvidersViaHttp();
|
||||
store.dispatch(setRegistryProviders(providers));
|
||||
const providers = await fetchMakersViaHttp();
|
||||
store.dispatch(setRegistryMakers(providers));
|
||||
} catch (error) {
|
||||
store.dispatch(registryConnectionFailed());
|
||||
logger.error(error, "Error fetching providers");
|
||||
|
|
|
@ -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>
|
||||
|
@ -230,7 +230,7 @@ function HasNoProvidersSwapWidget() {
|
|||
);
|
||||
}
|
||||
|
||||
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 />;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ import {
|
|||
} from "store/features/rpcSlice";
|
||||
import { swapProgressEventReceived } from "store/features/swapSlice";
|
||||
import { store } from "./store/storeRenderer";
|
||||
import { Provider } from "models/apiModel";
|
||||
import { Maker } from "models/apiModel";
|
||||
import { providerToConcatenatedMultiAddr } from "utils/multiAddrUtils";
|
||||
import { MoneroRecoveryResponse } from "models/rpcModel";
|
||||
import { ListSellersResponse } from "../models/tauriModel";
|
||||
|
@ -48,7 +48,7 @@ import logger from "utils/logger";
|
|||
import { getNetwork, getNetworkName, isTestnet } from "store/config";
|
||||
import { Blockchain, Network } from "store/features/settingsSlice";
|
||||
import { setStatus } from "store/features/nodesSlice";
|
||||
import { discoveredProvidersByRendezvous } from "store/features/providersSlice";
|
||||
import { discoveredMakersByRendezvous } from "store/features/makersSlice";
|
||||
|
||||
export const PRESET_RENDEZVOUS_POINTS = [
|
||||
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
||||
|
@ -57,7 +57,7 @@ export const PRESET_RENDEZVOUS_POINTS = [
|
|||
export async function fetchSellersAtPresetRendezvousPoints() {
|
||||
await Promise.all(PRESET_RENDEZVOUS_POINTS.map(async (rendezvousPoint) => {
|
||||
const response = await listSellersAtRendezvousPoint(rendezvousPoint);
|
||||
store.dispatch(discoveredProvidersByRendezvous(response.sellers));
|
||||
store.dispatch(discoveredMakersByRendezvous(response.sellers));
|
||||
|
||||
logger.log(`Discovered ${response.sellers.length} sellers at rendezvous point ${rendezvousPoint} during startup fetch`);
|
||||
}),
|
||||
|
@ -118,7 +118,7 @@ export async function withdrawBtc(address: string): Promise<string> {
|
|||
}
|
||||
|
||||
export async function buyXmr(
|
||||
seller: Provider,
|
||||
seller: Maker,
|
||||
bitcoin_change_address: string | null,
|
||||
monero_receive_address: string,
|
||||
) {
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import alertsSlice from "./features/alertsSlice";
|
||||
import providersSlice from "./features/providersSlice";
|
||||
import makersSlice from "./features/makersSlice";
|
||||
import ratesSlice from "./features/ratesSlice";
|
||||
import rpcSlice from "./features/rpcSlice";
|
||||
import swapReducer from "./features/swapSlice";
|
||||
import torSlice from "./features/torSlice";
|
||||
import settingsSlice from "./features/settingsSlice";
|
||||
import nodesSlice from "./features/nodesSlice";
|
||||
|
||||
export const reducers = {
|
||||
swap: swapReducer,
|
||||
providers: providersSlice,
|
||||
makers: makersSlice,
|
||||
tor: torSlice,
|
||||
rpc: rpcSlice,
|
||||
alerts: alertsSlice,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ExtendedProviderStatus } from "models/apiModel";
|
||||
import { ExtendedMakerStatus } from "models/apiModel";
|
||||
import { splitPeerIdFromMultiAddress } from "utils/parseUtils";
|
||||
import { getMatches } from '@tauri-apps/plugin-cli';
|
||||
import { Network } from "./features/settingsSlice";
|
||||
|
@ -19,14 +19,14 @@ export function isTestnet() {
|
|||
|
||||
export const isDevelopment = true;
|
||||
|
||||
export function getStubTestnetProvider(): ExtendedProviderStatus | null {
|
||||
const stubProviderAddress = import.meta.env
|
||||
export function getStubTestnetMaker(): ExtendedMakerStatus | null {
|
||||
const stubMakerAddress = import.meta.env
|
||||
.VITE_TESTNET_STUB_PROVIDER_ADDRESS;
|
||||
|
||||
if (stubProviderAddress != null) {
|
||||
if (stubMakerAddress != null) {
|
||||
try {
|
||||
const [multiAddr, peerId] =
|
||||
splitPeerIdFromMultiAddress(stubProviderAddress);
|
||||
splitPeerIdFromMultiAddress(stubMakerAddress);
|
||||
|
||||
return {
|
||||
multiAddr,
|
||||
|
|
127
src-gui/src/store/features/makersSlice.ts
Normal file
127
src-gui/src/store/features/makersSlice.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { ExtendedMakerStatus, MakerStatus } from "models/apiModel";
|
||||
import { Seller } from "models/tauriModel";
|
||||
import { getStubTestnetMaker } from "store/config";
|
||||
import { rendezvousSellerToMakerStatus } from "utils/conversionUtils";
|
||||
import { isMakerOutdated } from "utils/multiAddrUtils";
|
||||
import { sortMakerList } from "utils/sortUtils";
|
||||
|
||||
const stubTestnetMaker = getStubTestnetMaker();
|
||||
|
||||
export interface MakersSlice {
|
||||
rendezvous: {
|
||||
makers: (ExtendedMakerStatus | MakerStatus)[];
|
||||
};
|
||||
registry: {
|
||||
makers: ExtendedMakerStatus[] | null;
|
||||
// This counts how many failed connections attempts we have counted since the last successful connection
|
||||
connectionFailsCount: number;
|
||||
};
|
||||
selectedMaker: ExtendedMakerStatus | null;
|
||||
}
|
||||
|
||||
const initialState: MakersSlice = {
|
||||
rendezvous: {
|
||||
makers: [],
|
||||
},
|
||||
registry: {
|
||||
makers: stubTestnetMaker ? [stubTestnetMaker] : null,
|
||||
connectionFailsCount: 0,
|
||||
},
|
||||
selectedMaker: null,
|
||||
};
|
||||
|
||||
function selectNewSelectedMaker(
|
||||
slice: MakersSlice,
|
||||
peerId?: string,
|
||||
): MakerStatus {
|
||||
const selectedPeerId = peerId || slice.selectedMaker?.peerId;
|
||||
|
||||
// Check if we still have a record of the currently selected provider
|
||||
const currentMaker = slice.registry.makers?.find((prov) => prov.peerId === selectedPeerId) || slice.rendezvous.makers.find((prov) => prov.peerId === selectedPeerId);
|
||||
|
||||
// If the currently selected provider is not outdated, keep it
|
||||
if (currentMaker != null && !isMakerOutdated(currentMaker)) {
|
||||
return currentMaker;
|
||||
}
|
||||
|
||||
// Otherwise we'd prefer to switch to a provider that has the newest version
|
||||
const providers = sortMakerList([
|
||||
...(slice.registry.makers ?? []),
|
||||
...(slice.rendezvous.makers ?? []),
|
||||
]);
|
||||
|
||||
return providers.at(0) || null;
|
||||
}
|
||||
|
||||
export const makersSlice = createSlice({
|
||||
name: "providers",
|
||||
initialState,
|
||||
reducers: {
|
||||
discoveredMakersByRendezvous(slice, action: PayloadAction<Seller[]>) {
|
||||
action.payload.forEach((discoveredSeller) => {
|
||||
const discoveredMakerStatus =
|
||||
rendezvousSellerToMakerStatus(discoveredSeller);
|
||||
|
||||
// If the seller has a status of "Unreachable" the provider is not added to the list
|
||||
if (discoveredMakerStatus === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the provider was already discovered via the public registry, don't add it again
|
||||
const indexOfExistingMaker = slice.rendezvous.makers.findIndex(
|
||||
(prov) =>
|
||||
prov.peerId === discoveredMakerStatus.peerId &&
|
||||
prov.multiAddr === discoveredMakerStatus.multiAddr,
|
||||
);
|
||||
|
||||
// Avoid duplicate entries, replace them instead
|
||||
if (indexOfExistingMaker !== -1) {
|
||||
slice.rendezvous.makers[indexOfExistingMaker] =
|
||||
discoveredMakerStatus;
|
||||
} else {
|
||||
slice.rendezvous.makers.push(discoveredMakerStatus);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort the provider list and select a new provider if needed
|
||||
slice.rendezvous.makers = sortMakerList(slice.rendezvous.makers);
|
||||
slice.selectedMaker = selectNewSelectedMaker(slice);
|
||||
},
|
||||
setRegistryMakers(
|
||||
slice,
|
||||
action: PayloadAction<ExtendedMakerStatus[]>,
|
||||
) {
|
||||
if (stubTestnetMaker) {
|
||||
action.payload.push(stubTestnetMaker);
|
||||
}
|
||||
|
||||
// Sort the provider list and select a new provider if needed
|
||||
slice.registry.makers = sortMakerList(action.payload);
|
||||
slice.selectedMaker = selectNewSelectedMaker(slice);
|
||||
},
|
||||
registryConnectionFailed(slice) {
|
||||
slice.registry.connectionFailsCount += 1;
|
||||
},
|
||||
setSelectedMaker(
|
||||
slice,
|
||||
action: PayloadAction<{
|
||||
peerId: string;
|
||||
}>,
|
||||
) {
|
||||
slice.selectedMaker = selectNewSelectedMaker(
|
||||
slice,
|
||||
action.payload.peerId,
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
discoveredMakersByRendezvous,
|
||||
setRegistryMakers,
|
||||
registryConnectionFailed,
|
||||
setSelectedMaker,
|
||||
} = makersSlice.actions;
|
||||
|
||||
export default makersSlice.reducer;
|
|
@ -1,127 +0,0 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { ExtendedProviderStatus, ProviderStatus } from "models/apiModel";
|
||||
import { Seller } from "models/tauriModel";
|
||||
import { getStubTestnetProvider } from "store/config";
|
||||
import { rendezvousSellerToProviderStatus } from "utils/conversionUtils";
|
||||
import { isProviderOutdated } from "utils/multiAddrUtils";
|
||||
import { sortProviderList } from "utils/sortUtils";
|
||||
|
||||
const stubTestnetProvider = getStubTestnetProvider();
|
||||
|
||||
export interface ProvidersSlice {
|
||||
rendezvous: {
|
||||
providers: (ExtendedProviderStatus | ProviderStatus)[];
|
||||
};
|
||||
registry: {
|
||||
providers: ExtendedProviderStatus[] | null;
|
||||
// This counts how many failed connections attempts we have counted since the last successful connection
|
||||
connectionFailsCount: number;
|
||||
};
|
||||
selectedProvider: ExtendedProviderStatus | null;
|
||||
}
|
||||
|
||||
const initialState: ProvidersSlice = {
|
||||
rendezvous: {
|
||||
providers: [],
|
||||
},
|
||||
registry: {
|
||||
providers: stubTestnetProvider ? [stubTestnetProvider] : null,
|
||||
connectionFailsCount: 0,
|
||||
},
|
||||
selectedProvider: null,
|
||||
};
|
||||
|
||||
function selectNewSelectedProvider(
|
||||
slice: ProvidersSlice,
|
||||
peerId?: string,
|
||||
): ProviderStatus {
|
||||
const selectedPeerId = peerId || slice.selectedProvider?.peerId;
|
||||
|
||||
// Check if we still have a record of the currently selected provider
|
||||
const currentProvider = slice.registry.providers?.find((prov) => prov.peerId === selectedPeerId) || slice.rendezvous.providers.find((prov) => prov.peerId === selectedPeerId);
|
||||
|
||||
// If the currently selected provider is not outdated, keep it
|
||||
if (currentProvider != null && !isProviderOutdated(currentProvider)) {
|
||||
return currentProvider;
|
||||
}
|
||||
|
||||
// Otherwise we'd prefer to switch to a provider that has the newest version
|
||||
const providers = sortProviderList([
|
||||
...(slice.registry.providers ?? []),
|
||||
...(slice.rendezvous.providers ?? []),
|
||||
]);
|
||||
|
||||
return providers.at(0) || null;
|
||||
}
|
||||
|
||||
export const providersSlice = createSlice({
|
||||
name: "providers",
|
||||
initialState,
|
||||
reducers: {
|
||||
discoveredProvidersByRendezvous(slice, action: PayloadAction<Seller[]>) {
|
||||
action.payload.forEach((discoveredSeller) => {
|
||||
const discoveredProviderStatus =
|
||||
rendezvousSellerToProviderStatus(discoveredSeller);
|
||||
|
||||
// If the seller has a status of "Unreachable" the provider is not added to the list
|
||||
if (discoveredProviderStatus === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the provider was already discovered via the public registry, don't add it again
|
||||
const indexOfExistingProvider = slice.rendezvous.providers.findIndex(
|
||||
(prov) =>
|
||||
prov.peerId === discoveredProviderStatus.peerId &&
|
||||
prov.multiAddr === discoveredProviderStatus.multiAddr,
|
||||
);
|
||||
|
||||
// Avoid duplicate entries, replace them instead
|
||||
if (indexOfExistingProvider !== -1) {
|
||||
slice.rendezvous.providers[indexOfExistingProvider] =
|
||||
discoveredProviderStatus;
|
||||
} else {
|
||||
slice.rendezvous.providers.push(discoveredProviderStatus);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort the provider list and select a new provider if needed
|
||||
slice.rendezvous.providers = sortProviderList(slice.rendezvous.providers);
|
||||
slice.selectedProvider = selectNewSelectedProvider(slice);
|
||||
},
|
||||
setRegistryProviders(
|
||||
slice,
|
||||
action: PayloadAction<ExtendedProviderStatus[]>,
|
||||
) {
|
||||
if (stubTestnetProvider) {
|
||||
action.payload.push(stubTestnetProvider);
|
||||
}
|
||||
|
||||
// Sort the provider list and select a new provider if needed
|
||||
slice.registry.providers = sortProviderList(action.payload);
|
||||
slice.selectedProvider = selectNewSelectedProvider(slice);
|
||||
},
|
||||
registryConnectionFailed(slice) {
|
||||
slice.registry.connectionFailsCount += 1;
|
||||
},
|
||||
setSelectedProvider(
|
||||
slice,
|
||||
action: PayloadAction<{
|
||||
peerId: string;
|
||||
}>,
|
||||
) {
|
||||
slice.selectedProvider = selectNewSelectedProvider(
|
||||
slice,
|
||||
action.payload.peerId,
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
discoveredProvidersByRendezvous,
|
||||
setRegistryProviders,
|
||||
registryConnectionFailed,
|
||||
setSelectedProvider,
|
||||
} = providersSlice.actions;
|
||||
|
||||
export default providersSlice.reducer;
|
|
@ -1,5 +1,5 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { ExtendedProviderStatus, ProviderStatus } from "models/apiModel";
|
||||
import { ExtendedMakerStatus, MakerStatus } from "models/apiModel";
|
||||
import {
|
||||
TauriLogEvent,
|
||||
GetSwapInfoResponse,
|
||||
|
@ -16,7 +16,7 @@ import logger from "utils/logger";
|
|||
interface State {
|
||||
balance: number | null;
|
||||
withdrawTxId: string | null;
|
||||
rendezvous_discovered_sellers: (ExtendedProviderStatus | ProviderStatus)[];
|
||||
rendezvous_discovered_sellers: (ExtendedMakerStatus | MakerStatus)[];
|
||||
swapInfos: {
|
||||
[swapId: string]: GetSwapInfoResponseExt;
|
||||
};
|
||||
|
@ -103,9 +103,9 @@ export const rpcSlice = createSlice({
|
|||
rpcSetWithdrawTxId(slice, action: PayloadAction<string>) {
|
||||
slice.state.withdrawTxId = action.payload;
|
||||
},
|
||||
rpcSetRendezvousDiscoveredProviders(
|
||||
rpcSetRendezvousDiscoveredMakers(
|
||||
slice,
|
||||
action: PayloadAction<(ExtendedProviderStatus | ProviderStatus)[]>,
|
||||
action: PayloadAction<(ExtendedMakerStatus | MakerStatus)[]>,
|
||||
) {
|
||||
slice.state.rendezvous_discovered_sellers = action.payload;
|
||||
},
|
||||
|
@ -146,7 +146,7 @@ export const {
|
|||
rpcSetBalance,
|
||||
rpcSetWithdrawTxId,
|
||||
rpcResetWithdrawTxId,
|
||||
rpcSetRendezvousDiscoveredProviders,
|
||||
rpcSetRendezvousDiscoveredMakers,
|
||||
rpcSetSwapInfo,
|
||||
rpcSetMoneroRecoveryKeys,
|
||||
rpcResetMoneroRecoveryKeys,
|
||||
|
|
|
@ -8,7 +8,7 @@ import { isCliLogRelatedToSwap } from "models/cliModel";
|
|||
import { SettingsState } from "./features/settingsSlice";
|
||||
import { NodesSlice } from "./features/nodesSlice";
|
||||
import { RatesState } from "./features/ratesSlice";
|
||||
import { sortProviderList } from "utils/sortUtils";
|
||||
import { sortMakerList } from "utils/sortUtils";
|
||||
|
||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
|
@ -70,13 +70,13 @@ export function useActiveSwapLogs() {
|
|||
);
|
||||
}
|
||||
|
||||
export function useAllProviders() {
|
||||
export function useAllMakers() {
|
||||
return useAppSelector((state) => {
|
||||
const registryProviders = state.providers.registry.providers || [];
|
||||
const listSellersProviders = state.providers.rendezvous.providers || [];
|
||||
const all = [...registryProviders, ...listSellersProviders];
|
||||
const registryMakers = state.makers.registry.makers || [];
|
||||
const listSellersMakers = state.makers.rendezvous.makers || [];
|
||||
const all = [...registryMakers, ...listSellersMakers];
|
||||
|
||||
return sortProviderList(all);
|
||||
return sortMakerList(all);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ProviderStatus } from "models/apiModel";
|
||||
import { MakerStatus } from "models/apiModel";
|
||||
import { Seller } from "models/tauriModel";
|
||||
import { isTestnet } from "store/config";
|
||||
import { splitPeerIdFromMultiAddress } from "./parseUtils";
|
||||
|
@ -45,12 +45,12 @@ export function secondsToDays(seconds: number): number {
|
|||
return seconds / 86400;
|
||||
}
|
||||
|
||||
// Convert the "Seller" object returned by the list sellers tauri endpoint to a "ProviderStatus" object
|
||||
// Convert the "Seller" object returned by the list sellers tauri endpoint to a "MakerStatus" object
|
||||
// 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 rendezvousSellerToProviderStatus(
|
||||
export function rendezvousSellerToMakerStatus(
|
||||
seller: Seller,
|
||||
): ProviderStatus | null {
|
||||
): MakerStatus | null {
|
||||
if (seller.status.type === "Unreachable") {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import { ExtendedProviderStatus, Provider } from "models/apiModel";
|
||||
import { ExtendedMakerStatus, Maker } from "models/apiModel";
|
||||
import { Multiaddr } from "multiaddr";
|
||||
import semver from "semver";
|
||||
import { isTestnet } from "store/config";
|
||||
|
||||
const MIN_ASB_VERSION = "1.0.0-alpha.1"
|
||||
|
||||
export function providerToConcatenatedMultiAddr(provider: Provider) {
|
||||
export function providerToConcatenatedMultiAddr(provider: Maker) {
|
||||
return new Multiaddr(provider.multiAddr)
|
||||
.encapsulate(`/p2p/${provider.peerId}`)
|
||||
.toString();
|
||||
}
|
||||
|
||||
export function isProviderOnCorrectNetwork(
|
||||
provider: ExtendedProviderStatus,
|
||||
export function isMakerOnCorrectNetwork(
|
||||
provider: ExtendedMakerStatus,
|
||||
): boolean {
|
||||
return provider.testnet === isTestnet();
|
||||
}
|
||||
|
||||
export function isProviderOutdated(provider: ExtendedProviderStatus): boolean {
|
||||
export function isMakerOutdated(provider: ExtendedMakerStatus): boolean {
|
||||
if (provider.version != null) {
|
||||
if (semver.satisfies(provider.version, `>=${MIN_ASB_VERSION}`))
|
||||
return false;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { ExtendedProviderStatus } from "models/apiModel";
|
||||
import { isProviderOnCorrectNetwork, isProviderOutdated } from "./multiAddrUtils";
|
||||
import { ExtendedMakerStatus } from "models/apiModel";
|
||||
import { isMakerOnCorrectNetwork, isMakerOutdated } from "./multiAddrUtils";
|
||||
|
||||
export function sortProviderList(list: ExtendedProviderStatus[]) {
|
||||
export function sortMakerList(list: ExtendedMakerStatus[]) {
|
||||
return list
|
||||
// Filter out providers that are on the wrong network (testnet / mainnet)
|
||||
.filter(isProviderOnCorrectNetwork)
|
||||
// Filter out makers that are on the wrong network (testnet / mainnet)
|
||||
.filter(isMakerOnCorrectNetwork)
|
||||
.concat()
|
||||
// Sort by criteria
|
||||
.sort((firstEl, secondEl) => {
|
||||
// If either provider is outdated, prioritize the one that isn't
|
||||
if (isProviderOutdated(firstEl) && !isProviderOutdated(secondEl)) return 1;
|
||||
if (!isProviderOutdated(firstEl) && isProviderOutdated(secondEl)) return -1;
|
||||
if (isMakerOutdated(firstEl) && !isMakerOutdated(secondEl)) return 1;
|
||||
if (!isMakerOutdated(firstEl) && isMakerOutdated(secondEl)) return -1;
|
||||
|
||||
// If neither of them have a relevancy score or they are the same, sort by price
|
||||
if (firstEl.relevancy == secondEl.relevancy) {
|
||||
|
@ -24,7 +24,7 @@ export function sortProviderList(list: ExtendedProviderStatus[]) {
|
|||
// Otherwise, sort by relevancy score
|
||||
return secondEl.relevancy - firstEl.relevancy;
|
||||
})
|
||||
// Remove duplicate providers
|
||||
// Remove duplicate makers
|
||||
.filter((provider, index, self) =>
|
||||
index === self.findIndex((p) => p.peerId === provider.peerId)
|
||||
)
|
||||
|
|
|
@ -1001,6 +1001,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.6.tgz#193ced6a40c8006cfc1ca3f4553444fb38f0e543"
|
||||
integrity sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==
|
||||
|
||||
"@types/node@*":
|
||||
version "22.9.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.9.3.tgz#08f3d64b3bc6d74b162d36f60213e8a6704ef2b4"
|
||||
integrity sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==
|
||||
dependencies:
|
||||
undici-types "~6.19.8"
|
||||
|
||||
"@types/node@^20.14.10":
|
||||
version "20.14.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a"
|
||||
|
@ -1432,6 +1439,13 @@ caniuse-lite@^1.0.30001629:
|
|||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz#32c467d4bf1f1a0faa63fc793c2ba81169e7652f"
|
||||
integrity sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==
|
||||
|
||||
canvas-renderer@~2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/canvas-renderer/-/canvas-renderer-2.2.1.tgz#c1d131f78a9799aca8af9679ad0a005052b65550"
|
||||
integrity sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
chai@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c"
|
||||
|
@ -2538,6 +2552,13 @@ iterator.prototype@^1.1.2:
|
|||
reflect.getprototypeof "^1.0.4"
|
||||
set-function-name "^2.0.1"
|
||||
|
||||
jdenticon@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/jdenticon/-/jdenticon-3.3.0.tgz#64bae9f9b3cf5c2a210e183648117afe3a89b367"
|
||||
integrity sha512-DhuBRNRIybGPeAjMjdHbkIfiwZCCmf8ggu7C49jhp6aJ7DYsZfudnvnTY5/1vgUhrGA7JaDAx1WevnpjCPvaGg==
|
||||
dependencies:
|
||||
canvas-renderer "~2.2.0"
|
||||
|
||||
joycon@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
|
||||
|
@ -3750,6 +3771,11 @@ undici-types@~5.26.4:
|
|||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
|
||||
undici-types@~6.19.8:
|
||||
version "6.19.8"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
|
||||
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
|
||||
|
||||
update-browserslist-db@^1.0.16:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e"
|
||||
|
|
|
@ -26,7 +26,7 @@ You can read [this blogpost](https://comit.network/blog/2021/07/02/transaction-p
|
|||
|
||||
For more detailed documentation on the CLI, see [this README](./dev-docs/cli/README.md).
|
||||
|
||||
## Becoming a Market Maker
|
||||
## Becoming a market maker
|
||||
|
||||
Swapping of course needs two parties - and the CLI is only one of them: The taker that occasionally starts a swap with a market maker.
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ async fn next_state(
|
|||
.estimate_fee(TxCancel::weight(), btc_amount)
|
||||
.await?;
|
||||
|
||||
// Emit an event to tauri that we are negotiating with the swap provider to lock the Bitcoin
|
||||
// Emit an event to tauri that we are negotiating with the maker to lock the Bitcoin
|
||||
event_emitter.emit_swap_progress_event(
|
||||
swap_id,
|
||||
TauriSwapProgressEvent::SwapSetupInflight {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue