mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-07 01:05:05 -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]
|
## [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
|
## [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.
|
- 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 { useState, useEffect } from "react";
|
||||||
import { Table, Td, Th, Tr } from 'nextra/components'
|
import { Table, Td, Th, Tr } from 'nextra/components'
|
||||||
|
|
||||||
export default function SwapProviderTable() {
|
export default function SwapMakerTable() {
|
||||||
function satsToBtc(sats) {
|
function satsToBtc(sats) {
|
||||||
return sats / 100000000;
|
return sats / 100000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getProviders() {
|
async function getMakers() {
|
||||||
const response = await fetch("https://api.unstoppableswap.net/api/list");
|
const response = await fetch("https://api.unstoppableswap.net/api/list");
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [providers, setProviders] = useState([]);
|
const [makers, setMakers] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getProviders().then((data) => {
|
getMakers().then((data) => {
|
||||||
setProviders(data);
|
setMakers(data);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -38,16 +38,16 @@ export default function SwapProviderTable() {
|
||||||
</Tr>
|
</Tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{providers.map((provider) => (
|
{makers.map((maker) => (
|
||||||
<Tr key={provider.peerId}>
|
<Tr key={maker.peerId}>
|
||||||
<Td>
|
<Td>
|
||||||
{provider.testnet ? "Testnet" : "Mainnet"}
|
{maker.testnet ? "Testnet" : "Mainnet"}
|
||||||
</Td>
|
</Td>
|
||||||
<Td>{provider.multiAddr}</Td>
|
<Td>{maker.multiAddr}</Td>
|
||||||
<Td>{provider.peerId}</Td>
|
<Td>{maker.peerId}</Td>
|
||||||
<Td>{satsToBtc(provider.minSwapAmount)} BTC</Td>
|
<Td>{satsToBtc(maker.minSwapAmount)} BTC</Td>
|
||||||
<Td>{satsToBtc(provider.maxSwapAmount)} BTC</Td>
|
<Td>{satsToBtc(maker.maxSwapAmount)} BTC</Td>
|
||||||
<Td>{satsToBtc(provider.price)} XMR/BTC</Td>
|
<Td>{satsToBtc(maker.price)} XMR/BTC</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"first_swap": "Complete your first swap",
|
"first_swap": "Complete your first swap",
|
||||||
"market_maker_discovery": "Swap Provider discovery",
|
"market_maker_discovery": "Maker discovery",
|
||||||
"refund_punish": "Cancel, Refund and Punish explained"
|
"refund_punish": "Cancel, Refund and Punish explained"
|
||||||
}
|
}
|
|
@ -16,16 +16,16 @@ import { Steps } from 'nextra/components'
|
||||||
|
|
||||||
## Performing the swap
|
## Performing the swap
|
||||||
<Steps>
|
<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.
|
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'
|
import { Callout } from 'nextra/components'
|
||||||
|
|
||||||
<Callout type="info">
|
<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>
|
</Callout>
|
||||||
|
|
||||||
You can use the input field to calculate the approximate amount of Monero you'll receive for a given amount of Bitcoin.
|
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
|
### 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:
|
This will open a new window where you need to enter two addresses:
|
||||||
|
|
||||||
1. the Monero address you want to receive the Monero to
|
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",
|
display: "inline-block",
|
||||||
// center vertically
|
// center vertically
|
||||||
verticalAlign: "middle",
|
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 exchange rate (how much Bitcoin they demand for 1 Monero)
|
||||||
- the minimum and maximum amounts you can swap
|
- the minimum and maximum amounts you can swap
|
||||||
|
@ -88,10 +88,10 @@ The swap will go through four stages:
|
||||||
1. **Locking the Bitcoin**:
|
1. **Locking the Bitcoin**:
|
||||||
Your Bitcoin is locked in a 2-of-2 multisig address.
|
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.
|
The other party locks their Monero as well.
|
||||||
|
|
||||||
3. **_Swap Provider_ redeems _Bitcoin_**:
|
3. **_Maker_ redeems _Bitcoin_**:
|
||||||
The other party redeems the Bitcoin.
|
The other party redeems the Bitcoin.
|
||||||
|
|
||||||
4. **Redeeming the Monero**:
|
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_.
|
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 _Swap Providers_ are described below.
|
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.
|
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 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_.
|
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_
|
## _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";
|
import SwapProviderTable from "../../components/SwapProviderTable";
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ import SwapProviderTable from "../../components/SwapProviderTable";
|
||||||
<SwapProviderTable />
|
<SwapProviderTable />
|
||||||
</div>
|
</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" />
|
<img src="/rendezvous_1.png" />
|
||||||
|
|
||||||
|
@ -30,14 +30,14 @@ import SwapProviderTable from "../../components/SwapProviderTable";
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
// center vertically
|
// center vertically
|
||||||
verticalAlign: "middle",
|
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" />
|
<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" />
|
<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",
|
display: "inline-block",
|
||||||
// center vertically
|
// center vertically
|
||||||
verticalAlign: "middle",
|
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" />
|
<img src="/public_registry.png" />
|
||||||
|
|
|
@ -8,17 +8,17 @@ We have chosen to include a fairly technical explanation here. However, the GUI
|
||||||
|
|
||||||
## Cancel
|
## 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.
|
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 _Swap Provider_ publishing a special Bitcoin transaction called the `Bitcoin Cancel Transaction`.
|
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.
|
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
|
## 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.
|
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 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
|
## 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.
|
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 _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.
|
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",
|
"@tauri-apps/plugin-updater": "^2.0.0",
|
||||||
"@types/react-redux": "^7.1.34",
|
"@types/react-redux": "^7.1.34",
|
||||||
"humanize-duration": "^3.32.1",
|
"humanize-duration": "^3.32.1",
|
||||||
|
"jdenticon": "^3.3.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"multiaddr": "^10.0.1",
|
"multiaddr": "^10.0.1",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export interface ExtendedProviderStatus extends ProviderStatus {
|
export interface ExtendedMakerStatus extends MakerStatus {
|
||||||
uptime?: number;
|
uptime?: number;
|
||||||
age?: number;
|
age?: number;
|
||||||
relevancy?: number;
|
relevancy?: number;
|
||||||
|
@ -6,15 +6,15 @@ export interface ExtendedProviderStatus extends ProviderStatus {
|
||||||
recommended?: boolean;
|
recommended?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProviderStatus extends ProviderQuote, Provider {}
|
export interface MakerStatus extends MakerQuote, Maker { }
|
||||||
|
|
||||||
export interface ProviderQuote {
|
export interface MakerQuote {
|
||||||
price: number;
|
price: number;
|
||||||
minSwapAmount: number;
|
minSwapAmount: number;
|
||||||
maxSwapAmount: number;
|
maxSwapAmount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Provider {
|
export interface Maker {
|
||||||
multiAddr: string;
|
multiAddr: string;
|
||||||
testnet: boolean;
|
testnet: boolean;
|
||||||
peerId: string;
|
peerId: string;
|
||||||
|
|
|
@ -5,21 +5,21 @@
|
||||||
// - and to submit feedback
|
// - and to submit feedback
|
||||||
// - fetch currency rates from CoinGecko
|
// - fetch currency rates from CoinGecko
|
||||||
|
|
||||||
import { Alert, ExtendedProviderStatus } from "models/apiModel";
|
import { Alert, ExtendedMakerStatus } from "models/apiModel";
|
||||||
import { store } from "./store/storeRenderer";
|
import { store } from "./store/storeRenderer";
|
||||||
import { setBtcPrice, setXmrBtcRate, setXmrPrice } from "store/features/ratesSlice";
|
import { setBtcPrice, setXmrBtcRate, setXmrPrice } from "store/features/ratesSlice";
|
||||||
import { FiatCurrency } from "store/features/settingsSlice";
|
import { FiatCurrency } from "store/features/settingsSlice";
|
||||||
import { setAlerts } from "store/features/alertsSlice";
|
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";
|
import logger from "utils/logger";
|
||||||
|
|
||||||
const PUBLIC_REGISTRY_API_BASE_URL = "https://api.unstoppableswap.net";
|
const PUBLIC_REGISTRY_API_BASE_URL = "https://api.unstoppableswap.net";
|
||||||
|
|
||||||
async function fetchProvidersViaHttp(): Promise<
|
async function fetchMakersViaHttp(): Promise<
|
||||||
ExtendedProviderStatus[]
|
ExtendedMakerStatus[]
|
||||||
> {
|
> {
|
||||||
const response = await fetch(`${PUBLIC_REGISTRY_API_BASE_URL}/api/list`);
|
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[]> {
|
async function fetchAlertsViaHttp(): Promise<Alert[]> {
|
||||||
|
@ -114,8 +114,8 @@ export async function updateRates(): Promise<void> {
|
||||||
*/
|
*/
|
||||||
export async function updatePublicRegistry(): Promise<void> {
|
export async function updatePublicRegistry(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const providers = await fetchProvidersViaHttp();
|
const providers = await fetchMakersViaHttp();
|
||||||
store.dispatch(setRegistryProviders(providers));
|
store.dispatch(setRegistryMakers(providers));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
store.dispatch(registryConnectionFailed());
|
store.dispatch(registryConnectionFailed());
|
||||||
logger.error(error, "Error fetching providers");
|
logger.error(error, "Error fetching providers");
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default function RemainingFundsWillBeUsedAlert() {
|
||||||
>
|
>
|
||||||
The remaining funds of <SatsAmount amount={balance} /> in the wallet
|
The remaining funds of <SatsAmount amount={balance} /> in the wallet
|
||||||
will be used for the next swap. If the remaining funds exceed the
|
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.
|
instantaneously.
|
||||||
</Alert>
|
</Alert>
|
||||||
</Box>
|
</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 TruncatedText from "renderer/components/other/TruncatedText";
|
||||||
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
|
||||||
import { listSellersAtRendezvousPoint, PRESET_RENDEZVOUS_POINTS } from "renderer/rpc";
|
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 { useAppDispatch } from "store/hooks";
|
||||||
import { isValidMultiAddressWithPeerId } from "utils/parseUtils";
|
import { isValidMultiAddressWithPeerId } from "utils/parseUtils";
|
||||||
|
|
||||||
|
@ -54,20 +54,20 @@ export default function ListSellersDialog({
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSuccess({ sellers }: ListSellersResponse) {
|
function handleSuccess({ sellers }: ListSellersResponse) {
|
||||||
dispatch(discoveredProvidersByRendezvous(sellers));
|
dispatch(discoveredMakersByRendezvous(sellers));
|
||||||
|
|
||||||
const discoveredSellersCount = sellers.length;
|
const discoveredSellersCount = sellers.length;
|
||||||
let message: string;
|
let message: string;
|
||||||
|
|
||||||
switch (discoveredSellersCount) {
|
switch (discoveredSellersCount) {
|
||||||
case 0:
|
case 0:
|
||||||
message = `No providers were discovered at the rendezvous point`;
|
message = `No makers were discovered at the rendezvous point`;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
message = `Discovered one provider at the rendezvous point`;
|
message = `Discovered one maker at the rendezvous point`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
message = `Discovered ${discoveredSellersCount} providers at the rendezvous point`;
|
message = `Discovered ${discoveredSellersCount} makers at the rendezvous point`;
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueueSnackbar(message, {
|
enqueueSnackbar(message, {
|
||||||
|
@ -80,13 +80,13 @@ export default function ListSellersDialog({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={onClose} open={open}>
|
<Dialog onClose={onClose} open={open}>
|
||||||
<DialogTitle>Discover swap providers</DialogTitle>
|
<DialogTitle>Discover makers</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<DialogContentText>
|
<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
|
partners) without relying on one singular centralized institution. By
|
||||||
manually connecting to a rendezvous point run by a volunteer, you can
|
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>
|
</DialogContentText>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
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";
|
} from "@material-ui/core";
|
||||||
import AddIcon from "@material-ui/icons/Add";
|
import AddIcon from "@material-ui/icons/Add";
|
||||||
import SearchIcon from "@material-ui/icons/Search";
|
import SearchIcon from "@material-ui/icons/Search";
|
||||||
import { ExtendedProviderStatus } from "models/apiModel";
|
import { ExtendedMakerStatus } from "models/apiModel";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { setSelectedProvider } from "store/features/providersSlice";
|
import { setSelectedMaker } from "store/features/makersSlice";
|
||||||
import { useAllProviders, useAppDispatch } from "store/hooks";
|
import { useAllMakers, useAppDispatch } from "store/hooks";
|
||||||
import ListSellersDialog from "../listSellers/ListSellersDialog";
|
import ListSellersDialog from "../listSellers/ListSellersDialog";
|
||||||
import ProviderInfo from "./ProviderInfo";
|
import MakerInfo from "./MakerInfo";
|
||||||
import ProviderSubmitDialog from "./ProviderSubmitDialog";
|
import MakerSubmitDialog from "./MakerSubmitDialog";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
dialogContent: {
|
dialogContent: {
|
||||||
|
@ -27,12 +27,12 @@ const useStyles = makeStyles({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
type ProviderSelectDialogProps = {
|
type MakerSelectDialogProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ProviderSubmitDialogOpenButton() {
|
export function MakerSubmitDialogOpenButton() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -46,13 +46,13 @@ export function ProviderSubmitDialogOpenButton() {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ProviderSubmitDialog open={open} onClose={() => setOpen(false)} />
|
<MakerSubmitDialog open={open} onClose={() => setOpen(false)} />
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary="Add a new provider to public registry" />
|
<ListItemText primary="Add a new maker to public registry" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -77,41 +77,41 @@ export function ListSellersDialogOpenButton() {
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary="Discover providers by connecting to a rendezvous point" />
|
<ListItemText primary="Discover makers by connecting to a rendezvous point" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProviderListDialog({
|
export default function MakerListDialog({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
}: ProviderSelectDialogProps) {
|
}: MakerSelectDialogProps) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const providers = useAllProviders();
|
const makers = useAllMakers();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
function handleProviderChange(provider: ExtendedProviderStatus) {
|
function handleMakerChange(maker: ExtendedMakerStatus) {
|
||||||
dispatch(setSelectedProvider(provider));
|
dispatch(setSelectedMaker(maker));
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={onClose} open={open}>
|
<Dialog onClose={onClose} open={open}>
|
||||||
<DialogTitle>Select a swap provider</DialogTitle>
|
<DialogTitle>Select a maker</DialogTitle>
|
||||||
|
|
||||||
<DialogContent className={classes.dialogContent} dividers>
|
<DialogContent className={classes.dialogContent} dividers>
|
||||||
<List>
|
<List>
|
||||||
{providers.map((provider) => (
|
{makers.map((maker) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
button
|
button
|
||||||
onClick={() => handleProviderChange(provider)}
|
onClick={() => handleMakerChange(maker)}
|
||||||
key={provider.peerId}
|
key={maker.peerId}
|
||||||
>
|
>
|
||||||
<ProviderInfo provider={provider} key={provider.peerId} />
|
<MakerInfo maker={maker} key={maker.peerId} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
<ListSellersDialogOpenButton />
|
<ListSellersDialogOpenButton />
|
||||||
<ProviderSubmitDialogOpenButton />
|
<MakerSubmitDialogOpenButton />
|
||||||
</List>
|
</List>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
@ -8,8 +8,8 @@ import {
|
||||||
import ArrowForwardIosIcon from "@material-ui/icons/ArrowForwardIos";
|
import ArrowForwardIosIcon from "@material-ui/icons/ArrowForwardIos";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import ProviderInfo from "./ProviderInfo";
|
import MakerInfo from "./MakerInfo";
|
||||||
import ProviderListDialog from "./ProviderListDialog";
|
import MakerListDialog from "./MakerListDialog";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
inner: {
|
inner: {
|
||||||
|
@ -17,23 +17,23 @@ const useStyles = makeStyles({
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
},
|
},
|
||||||
providerCard: {
|
makerCard: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
providerCardContent: {
|
makerCardContent: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function ProviderSelect() {
|
export default function MakerSelect() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [selectDialogOpen, setSelectDialogOpen] = useState(false);
|
const [selectDialogOpen, setSelectDialogOpen] = useState(false);
|
||||||
const selectedProvider = useAppSelector(
|
const selectedMaker = useAppSelector(
|
||||||
(state) => state.providers.selectedProvider,
|
(state) => state.makers.selectedMaker,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!selectedProvider) return <>No provider selected</>;
|
if (!selectedMaker) return <>No maker selected</>;
|
||||||
|
|
||||||
function handleSelectDialogClose() {
|
function handleSelectDialogClose() {
|
||||||
setSelectDialogOpen(false);
|
setSelectDialogOpen(false);
|
||||||
|
@ -45,13 +45,13 @@ export default function ProviderSelect() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<ProviderListDialog
|
<MakerListDialog
|
||||||
open={selectDialogOpen}
|
open={selectDialogOpen}
|
||||||
onClose={handleSelectDialogClose}
|
onClose={handleSelectDialogClose}
|
||||||
/>
|
/>
|
||||||
<Card variant="outlined" className={classes.providerCard}>
|
<Card variant="outlined" className={classes.makerCard}>
|
||||||
<CardContent className={classes.providerCardContent}>
|
<CardContent className={classes.makerCardContent}>
|
||||||
<ProviderInfo provider={selectedProvider} />
|
<MakerInfo maker={selectedMaker} />
|
||||||
<IconButton onClick={handleSelectDialogOpen} size="small">
|
<IconButton onClick={handleSelectDialogOpen} size="small">
|
||||||
<ArrowForwardIosIcon />
|
<ArrowForwardIosIcon />
|
||||||
</IconButton>
|
</IconButton>
|
|
@ -10,19 +10,19 @@ import {
|
||||||
import { Multiaddr } from "multiaddr";
|
import { Multiaddr } from "multiaddr";
|
||||||
import { ChangeEvent, useState } from "react";
|
import { ChangeEvent, useState } from "react";
|
||||||
|
|
||||||
type ProviderSubmitDialogProps = {
|
type MakerSubmitDialogProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ProviderSubmitDialog({
|
export default function MakerSubmitDialog({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
}: ProviderSubmitDialogProps) {
|
}: MakerSubmitDialogProps) {
|
||||||
const [multiAddr, setMultiAddr] = useState("");
|
const [multiAddr, setMultiAddr] = useState("");
|
||||||
const [peerId, setPeerId] = useState("");
|
const [peerId, setPeerId] = useState("");
|
||||||
|
|
||||||
async function handleProviderSubmit() {
|
async function handleMakerSubmit() {
|
||||||
if (multiAddr && peerId) {
|
if (multiAddr && peerId) {
|
||||||
await fetch("https://api.unstoppableswap.net/api/submit-provider", {
|
await fetch("https://api.unstoppableswap.net/api/submit-provider", {
|
||||||
method: "post",
|
method: "post",
|
||||||
|
@ -55,7 +55,7 @@ export default function ProviderSubmitDialog({
|
||||||
return "The multi address should not contain the peer id (/p2p/)";
|
return "The multi address should not contain the peer id (/p2p/)";
|
||||||
}
|
}
|
||||||
if (multiAddress.protoNames().find((name) => name.includes("onion"))) {
|
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;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -65,10 +65,10 @@ export default function ProviderSubmitDialog({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={onClose} open={open}>
|
<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>
|
<DialogContent dividers>
|
||||||
<DialogContentText>
|
<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.
|
other users to trade with.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -78,7 +78,7 @@ export default function ProviderSubmitDialog({
|
||||||
fullWidth
|
fullWidth
|
||||||
helperText={
|
helperText={
|
||||||
getMultiAddressError() ||
|
getMultiAddressError() ||
|
||||||
"Tells the swap client where the provider can be reached"
|
"Tells the swap client where the maker can be reached"
|
||||||
}
|
}
|
||||||
value={multiAddr}
|
value={multiAddr}
|
||||||
onChange={handleMultiAddrChange}
|
onChange={handleMultiAddrChange}
|
||||||
|
@ -89,7 +89,7 @@ export default function ProviderSubmitDialog({
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label="Peer ID"
|
label="Peer ID"
|
||||||
fullWidth
|
fullWidth
|
||||||
helperText="Identifies the provider and allows for secure communication"
|
helperText="Identifies the maker and allows for secure communication"
|
||||||
value={peerId}
|
value={peerId}
|
||||||
onChange={handlePeerIdChange}
|
onChange={handlePeerIdChange}
|
||||||
placeholder="12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"
|
placeholder="12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"
|
||||||
|
@ -99,7 +99,7 @@ export default function ProviderSubmitDialog({
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleProviderSubmit}
|
onClick={handleMakerSubmit}
|
||||||
disabled={!(multiAddr && peerId && !getMultiAddressError())}
|
disabled={!(multiAddr && peerId && !getMultiAddressError())}
|
||||||
color="primary"
|
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
|
loading
|
||||||
additionalContent={
|
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
|
Monero. After they lock their funds and the Monero transaction
|
||||||
receives one confirmation, the swap will proceed to the next step.
|
receives one confirmation, the swap will proceed to the next step.
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default function SwapSetupInflightPage({
|
||||||
<CircularProgressWithSubtitle
|
<CircularProgressWithSubtitle
|
||||||
description={
|
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 [redeemAddressValid, setRedeemAddressValid] = useState(false);
|
||||||
const [refundAddressValid, setRefundAddressValid] = useState(false);
|
const [refundAddressValid, setRefundAddressValid] = useState(false);
|
||||||
|
|
||||||
const selectedProvider = useAppSelector(
|
const selectedMaker = useAppSelector(
|
||||||
(state) => state.providers.selectedProvider,
|
(state) => state.makers.selectedMaker,
|
||||||
);
|
);
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await buyXmr(
|
await buyXmr(
|
||||||
selectedProvider,
|
selectedMaker,
|
||||||
useExternalRefundAddress ? refundAddress : null,
|
useExternalRefundAddress ? refundAddress : null,
|
||||||
redeemAddress,
|
redeemAddress,
|
||||||
);
|
);
|
||||||
|
@ -99,7 +99,7 @@ export default function InitPage() {
|
||||||
disabled={
|
disabled={
|
||||||
(!refundAddressValid && useExternalRefundAddress) ||
|
(!refundAddressValid && useExternalRefundAddress) ||
|
||||||
!redeemAddressValid ||
|
!redeemAddressValid ||
|
||||||
!selectedProvider
|
!selectedMaker
|
||||||
}
|
}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
|
@ -35,7 +35,7 @@ export default function TorInfoBox() {
|
||||||
internet. It is a free and open network that is operated by
|
internet. It is a free and open network that is operated by
|
||||||
volunteers. You can start and stop Tor by clicking the buttons
|
volunteers. You can start and stop Tor by clicking the buttons
|
||||||
below. If Tor is running, all traffic will be routed through it and
|
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>
|
</Typography>
|
||||||
<CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split("\n")} />
|
<CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split("\n")} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -91,7 +91,7 @@ export default function HistoryRowExpanded({
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Provider Address</TableCell>
|
<TableCell>Maker Address</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Box className={classes.outerAddressBox}>
|
<Box className={classes.outerAddressBox}>
|
||||||
{swap.seller.addresses.map((addr) => (
|
{swap.seller.addresses.map((addr) => (
|
||||||
|
|
|
@ -11,15 +11,15 @@ import InputAdornment from "@material-ui/core/InputAdornment";
|
||||||
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
|
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
|
||||||
import SwapHorizIcon from "@material-ui/icons/SwapHoriz";
|
import SwapHorizIcon from "@material-ui/icons/SwapHoriz";
|
||||||
import { Alert } from "@material-ui/lab";
|
import { Alert } from "@material-ui/lab";
|
||||||
import { ExtendedProviderStatus } from "models/apiModel";
|
import { ExtendedMakerStatus } from "models/apiModel";
|
||||||
import { ChangeEvent, useEffect, useState } from "react";
|
import { ChangeEvent, useEffect, useState } from "react";
|
||||||
import { useAppSelector } from "store/hooks";
|
import { useAppSelector } from "store/hooks";
|
||||||
import { satsToBtc } from "utils/conversionUtils";
|
import { satsToBtc } from "utils/conversionUtils";
|
||||||
import {
|
import {
|
||||||
ListSellersDialogOpenButton,
|
ListSellersDialogOpenButton,
|
||||||
ProviderSubmitDialogOpenButton,
|
MakerSubmitDialogOpenButton,
|
||||||
} from "../../modal/provider/ProviderListDialog";
|
} from "../../modal/provider/MakerListDialog";
|
||||||
import ProviderSelect from "../../modal/provider/ProviderSelect";
|
import MakerSelect from "../../modal/provider/MakerSelect";
|
||||||
import SwapDialog from "../../modal/swap/SwapDialog";
|
import SwapDialog from "../../modal/swap/SwapDialog";
|
||||||
|
|
||||||
// After RECONNECTION_ATTEMPTS_UNTIL_ASSUME_DOWN failed reconnection attempts we can assume the public registry is down
|
// 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: {
|
headerText: {
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
},
|
},
|
||||||
providerInfo: {
|
makerInfo: {
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
},
|
},
|
||||||
swapIconOuter: {
|
swapIconOuter: {
|
||||||
|
@ -53,12 +53,12 @@ const useStyles = makeStyles((theme) => ({
|
||||||
swapIcon: {
|
swapIcon: {
|
||||||
marginRight: theme.spacing(1),
|
marginRight: theme.spacing(1),
|
||||||
},
|
},
|
||||||
noProvidersAlertOuter: {
|
noMakersAlertOuter: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
noProvidersAlertButtonsOuter: {
|
noMakersAlertButtonsOuter: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
},
|
||||||
|
@ -76,17 +76,17 @@ function Title() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HasProviderSwapWidget({
|
function HasMakerSwapWidget({
|
||||||
selectedProvider,
|
selectedMaker,
|
||||||
}: {
|
}: {
|
||||||
selectedProvider: ExtendedProviderStatus;
|
selectedMaker: ExtendedMakerStatus;
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const forceShowDialog = useAppSelector((state) => state.swap.state !== null);
|
const forceShowDialog = useAppSelector((state) => state.swap.state !== null);
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
const [btcFieldValue, setBtcFieldValue] = useState<number | string>(
|
const [btcFieldValue, setBtcFieldValue] = useState<number | string>(
|
||||||
satsToBtc(selectedProvider.minSwapAmount),
|
satsToBtc(selectedMaker.minSwapAmount),
|
||||||
);
|
);
|
||||||
const [xmrFieldValue, setXmrFieldValue] = useState(1);
|
const [xmrFieldValue, setXmrFieldValue] = useState(1);
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ function HasProviderSwapWidget({
|
||||||
setXmrFieldValue(0);
|
setXmrFieldValue(0);
|
||||||
} else {
|
} else {
|
||||||
const convertedXmrAmount =
|
const convertedXmrAmount =
|
||||||
parsedBtcAmount / satsToBtc(selectedProvider.price);
|
parsedBtcAmount / satsToBtc(selectedMaker.price);
|
||||||
setXmrFieldValue(convertedXmrAmount);
|
setXmrFieldValue(convertedXmrAmount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,15 +110,15 @@ function HasProviderSwapWidget({
|
||||||
if (Number.isNaN(parsedBtcAmount)) {
|
if (Number.isNaN(parsedBtcAmount)) {
|
||||||
return "This is not a valid number";
|
return "This is not a valid number";
|
||||||
}
|
}
|
||||||
if (parsedBtcAmount < satsToBtc(selectedProvider.minSwapAmount)) {
|
if (parsedBtcAmount < satsToBtc(selectedMaker.minSwapAmount)) {
|
||||||
return `The minimum swap amount is ${satsToBtc(
|
return `The minimum swap amount is ${satsToBtc(
|
||||||
selectedProvider.minSwapAmount,
|
selectedMaker.minSwapAmount,
|
||||||
)} BTC. Switch to a different provider if you want to swap less.`;
|
)} 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(
|
return `The maximum swap amount is ${satsToBtc(
|
||||||
selectedProvider.maxSwapAmount,
|
selectedMaker.maxSwapAmount,
|
||||||
)} BTC. Switch to a different provider if you want to swap more.`;
|
)} BTC. Switch to a different maker if you want to swap more.`;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ function HasProviderSwapWidget({
|
||||||
setShowDialog(true);
|
setShowDialog(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(updateXmrValue, [btcFieldValue, selectedProvider]);
|
useEffect(updateXmrValue, [btcFieldValue, selectedMaker]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// 'elevation' prop can't be passed down (type def issue)
|
// 'elevation' prop can't be passed down (type def issue)
|
||||||
|
@ -160,7 +160,7 @@ function HasProviderSwapWidget({
|
||||||
endAdornment: <InputAdornment position="end">XMR</InputAdornment>,
|
endAdornment: <InputAdornment position="end">XMR</InputAdornment>,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ProviderSelect />
|
<MakerSelect />
|
||||||
<Fab variant="extended" color="primary" onClick={handleGuideDialogOpen}>
|
<Fab variant="extended" color="primary" onClick={handleGuideDialogOpen}>
|
||||||
<SwapHorizIcon className={classes.swapIcon} />
|
<SwapHorizIcon className={classes.swapIcon} />
|
||||||
Swap
|
Swap
|
||||||
|
@ -173,22 +173,22 @@ function HasProviderSwapWidget({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HasNoProvidersSwapWidget() {
|
function HasNoMakersSwapWidget() {
|
||||||
const forceShowDialog = useAppSelector((state) => state.swap.state !== null);
|
const forceShowDialog = useAppSelector((state) => state.swap.state !== null);
|
||||||
const isPublicRegistryDown = useAppSelector((state) =>
|
const isPublicRegistryDown = useAppSelector((state) =>
|
||||||
isRegistryDown(state.providers.registry.connectionFailsCount),
|
isRegistryDown(state.makers.registry.connectionFailsCount),
|
||||||
);
|
);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const alertBox = isPublicRegistryDown ? (
|
const alertBox = isPublicRegistryDown ? (
|
||||||
<Alert severity="info">
|
<Alert severity="info">
|
||||||
<Box className={classes.noProvidersAlertOuter}>
|
<Box className={classes.noMakersAlertOuter}>
|
||||||
<Typography>
|
<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:
|
Here's what you can do:
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Try discovering a provider by connecting to a rendezvous point
|
Try discovering a maker by connecting to a rendezvous point
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Try again later when the public registry may be reachable again
|
Try again later when the public registry may be reachable again
|
||||||
|
@ -202,20 +202,20 @@ function HasNoProvidersSwapWidget() {
|
||||||
</Alert>
|
</Alert>
|
||||||
) : (
|
) : (
|
||||||
<Alert severity="info">
|
<Alert severity="info">
|
||||||
<Box className={classes.noProvidersAlertOuter}>
|
<Box className={classes.noMakersAlertOuter}>
|
||||||
<Typography>
|
<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:
|
official registry. Here's what you can do:
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Try discovering a provider by connecting to a rendezvous point
|
Try discovering a maker by connecting to a rendezvous point
|
||||||
</li>
|
</li>
|
||||||
<li>Add a new provider to the public registry</li>
|
<li>Add a new maker to the public registry</li>
|
||||||
<li>Try again later when more providers may be available</li>
|
<li>Try again later when more makers may be available</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box>
|
<Box>
|
||||||
<ProviderSubmitDialogOpenButton />
|
<MakerSubmitDialogOpenButton />
|
||||||
<ListSellersDialogOpenButton />
|
<ListSellersDialogOpenButton />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -230,7 +230,7 @@ function HasNoProvidersSwapWidget() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProviderLoadingSwapWidget() {
|
function MakerLoadingSwapWidget() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -245,21 +245,21 @@ function ProviderLoadingSwapWidget() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwapWidget() {
|
export default function SwapWidget() {
|
||||||
const selectedProvider = useAppSelector(
|
const selectedMaker = useAppSelector(
|
||||||
(state) => state.providers.selectedProvider,
|
(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.
|
// 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 providerLoading = useAppSelector(
|
const makerLoading = useAppSelector(
|
||||||
(state) =>
|
(state) =>
|
||||||
state.providers.registry.providers === null &&
|
state.makers.registry.makers === null &&
|
||||||
!isRegistryDown(state.providers.registry.connectionFailsCount),
|
!isRegistryDown(state.makers.registry.connectionFailsCount),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (providerLoading) {
|
if (makerLoading) {
|
||||||
return <ProviderLoadingSwapWidget />;
|
return <MakerLoadingSwapWidget />;
|
||||||
}
|
}
|
||||||
if (selectedProvider) {
|
if (selectedMaker) {
|
||||||
return <HasProviderSwapWidget selectedProvider={selectedProvider} />;
|
return <HasMakerSwapWidget selectedMaker={selectedMaker} />;
|
||||||
}
|
}
|
||||||
return <HasNoProvidersSwapWidget />;
|
return <HasNoMakersSwapWidget />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ import {
|
||||||
} from "store/features/rpcSlice";
|
} from "store/features/rpcSlice";
|
||||||
import { swapProgressEventReceived } from "store/features/swapSlice";
|
import { swapProgressEventReceived } from "store/features/swapSlice";
|
||||||
import { store } from "./store/storeRenderer";
|
import { store } from "./store/storeRenderer";
|
||||||
import { Provider } from "models/apiModel";
|
import { Maker } from "models/apiModel";
|
||||||
import { providerToConcatenatedMultiAddr } from "utils/multiAddrUtils";
|
import { providerToConcatenatedMultiAddr } from "utils/multiAddrUtils";
|
||||||
import { MoneroRecoveryResponse } from "models/rpcModel";
|
import { MoneroRecoveryResponse } from "models/rpcModel";
|
||||||
import { ListSellersResponse } from "../models/tauriModel";
|
import { ListSellersResponse } from "../models/tauriModel";
|
||||||
|
@ -48,7 +48,7 @@ import logger from "utils/logger";
|
||||||
import { getNetwork, getNetworkName, isTestnet } from "store/config";
|
import { getNetwork, getNetworkName, isTestnet } from "store/config";
|
||||||
import { Blockchain, Network } from "store/features/settingsSlice";
|
import { Blockchain, Network } from "store/features/settingsSlice";
|
||||||
import { setStatus } from "store/features/nodesSlice";
|
import { setStatus } from "store/features/nodesSlice";
|
||||||
import { discoveredProvidersByRendezvous } from "store/features/providersSlice";
|
import { discoveredMakersByRendezvous } from "store/features/makersSlice";
|
||||||
|
|
||||||
export const PRESET_RENDEZVOUS_POINTS = [
|
export const PRESET_RENDEZVOUS_POINTS = [
|
||||||
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
||||||
|
@ -57,7 +57,7 @@ export const PRESET_RENDEZVOUS_POINTS = [
|
||||||
export async function fetchSellersAtPresetRendezvousPoints() {
|
export async function fetchSellersAtPresetRendezvousPoints() {
|
||||||
await Promise.all(PRESET_RENDEZVOUS_POINTS.map(async (rendezvousPoint) => {
|
await Promise.all(PRESET_RENDEZVOUS_POINTS.map(async (rendezvousPoint) => {
|
||||||
const response = await listSellersAtRendezvousPoint(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`);
|
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(
|
export async function buyXmr(
|
||||||
seller: Provider,
|
seller: Maker,
|
||||||
bitcoin_change_address: string | null,
|
bitcoin_change_address: string | null,
|
||||||
monero_receive_address: string,
|
monero_receive_address: string,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import alertsSlice from "./features/alertsSlice";
|
import alertsSlice from "./features/alertsSlice";
|
||||||
import providersSlice from "./features/providersSlice";
|
import makersSlice from "./features/makersSlice";
|
||||||
import ratesSlice from "./features/ratesSlice";
|
import ratesSlice from "./features/ratesSlice";
|
||||||
import rpcSlice from "./features/rpcSlice";
|
import rpcSlice from "./features/rpcSlice";
|
||||||
import swapReducer from "./features/swapSlice";
|
import swapReducer from "./features/swapSlice";
|
||||||
import torSlice from "./features/torSlice";
|
import torSlice from "./features/torSlice";
|
||||||
import settingsSlice from "./features/settingsSlice";
|
import settingsSlice from "./features/settingsSlice";
|
||||||
import nodesSlice from "./features/nodesSlice";
|
import nodesSlice from "./features/nodesSlice";
|
||||||
|
|
||||||
export const reducers = {
|
export const reducers = {
|
||||||
swap: swapReducer,
|
swap: swapReducer,
|
||||||
providers: providersSlice,
|
makers: makersSlice,
|
||||||
tor: torSlice,
|
tor: torSlice,
|
||||||
rpc: rpcSlice,
|
rpc: rpcSlice,
|
||||||
alerts: alertsSlice,
|
alerts: alertsSlice,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ExtendedProviderStatus } from "models/apiModel";
|
import { ExtendedMakerStatus } from "models/apiModel";
|
||||||
import { splitPeerIdFromMultiAddress } from "utils/parseUtils";
|
import { splitPeerIdFromMultiAddress } from "utils/parseUtils";
|
||||||
import { getMatches } from '@tauri-apps/plugin-cli';
|
import { getMatches } from '@tauri-apps/plugin-cli';
|
||||||
import { Network } from "./features/settingsSlice";
|
import { Network } from "./features/settingsSlice";
|
||||||
|
@ -19,14 +19,14 @@ export function isTestnet() {
|
||||||
|
|
||||||
export const isDevelopment = true;
|
export const isDevelopment = true;
|
||||||
|
|
||||||
export function getStubTestnetProvider(): ExtendedProviderStatus | null {
|
export function getStubTestnetMaker(): ExtendedMakerStatus | null {
|
||||||
const stubProviderAddress = import.meta.env
|
const stubMakerAddress = import.meta.env
|
||||||
.VITE_TESTNET_STUB_PROVIDER_ADDRESS;
|
.VITE_TESTNET_STUB_PROVIDER_ADDRESS;
|
||||||
|
|
||||||
if (stubProviderAddress != null) {
|
if (stubMakerAddress != null) {
|
||||||
try {
|
try {
|
||||||
const [multiAddr, peerId] =
|
const [multiAddr, peerId] =
|
||||||
splitPeerIdFromMultiAddress(stubProviderAddress);
|
splitPeerIdFromMultiAddress(stubMakerAddress);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
multiAddr,
|
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 { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { ExtendedProviderStatus, ProviderStatus } from "models/apiModel";
|
import { ExtendedMakerStatus, MakerStatus } from "models/apiModel";
|
||||||
import {
|
import {
|
||||||
TauriLogEvent,
|
TauriLogEvent,
|
||||||
GetSwapInfoResponse,
|
GetSwapInfoResponse,
|
||||||
|
@ -16,7 +16,7 @@ import logger from "utils/logger";
|
||||||
interface State {
|
interface State {
|
||||||
balance: number | null;
|
balance: number | null;
|
||||||
withdrawTxId: string | null;
|
withdrawTxId: string | null;
|
||||||
rendezvous_discovered_sellers: (ExtendedProviderStatus | ProviderStatus)[];
|
rendezvous_discovered_sellers: (ExtendedMakerStatus | MakerStatus)[];
|
||||||
swapInfos: {
|
swapInfos: {
|
||||||
[swapId: string]: GetSwapInfoResponseExt;
|
[swapId: string]: GetSwapInfoResponseExt;
|
||||||
};
|
};
|
||||||
|
@ -103,9 +103,9 @@ export const rpcSlice = createSlice({
|
||||||
rpcSetWithdrawTxId(slice, action: PayloadAction<string>) {
|
rpcSetWithdrawTxId(slice, action: PayloadAction<string>) {
|
||||||
slice.state.withdrawTxId = action.payload;
|
slice.state.withdrawTxId = action.payload;
|
||||||
},
|
},
|
||||||
rpcSetRendezvousDiscoveredProviders(
|
rpcSetRendezvousDiscoveredMakers(
|
||||||
slice,
|
slice,
|
||||||
action: PayloadAction<(ExtendedProviderStatus | ProviderStatus)[]>,
|
action: PayloadAction<(ExtendedMakerStatus | MakerStatus)[]>,
|
||||||
) {
|
) {
|
||||||
slice.state.rendezvous_discovered_sellers = action.payload;
|
slice.state.rendezvous_discovered_sellers = action.payload;
|
||||||
},
|
},
|
||||||
|
@ -146,7 +146,7 @@ export const {
|
||||||
rpcSetBalance,
|
rpcSetBalance,
|
||||||
rpcSetWithdrawTxId,
|
rpcSetWithdrawTxId,
|
||||||
rpcResetWithdrawTxId,
|
rpcResetWithdrawTxId,
|
||||||
rpcSetRendezvousDiscoveredProviders,
|
rpcSetRendezvousDiscoveredMakers,
|
||||||
rpcSetSwapInfo,
|
rpcSetSwapInfo,
|
||||||
rpcSetMoneroRecoveryKeys,
|
rpcSetMoneroRecoveryKeys,
|
||||||
rpcResetMoneroRecoveryKeys,
|
rpcResetMoneroRecoveryKeys,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { isCliLogRelatedToSwap } from "models/cliModel";
|
||||||
import { SettingsState } from "./features/settingsSlice";
|
import { SettingsState } from "./features/settingsSlice";
|
||||||
import { NodesSlice } from "./features/nodesSlice";
|
import { NodesSlice } from "./features/nodesSlice";
|
||||||
import { RatesState } from "./features/ratesSlice";
|
import { RatesState } from "./features/ratesSlice";
|
||||||
import { sortProviderList } from "utils/sortUtils";
|
import { sortMakerList } from "utils/sortUtils";
|
||||||
|
|
||||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||||
|
@ -70,13 +70,13 @@ export function useActiveSwapLogs() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAllProviders() {
|
export function useAllMakers() {
|
||||||
return useAppSelector((state) => {
|
return useAppSelector((state) => {
|
||||||
const registryProviders = state.providers.registry.providers || [];
|
const registryMakers = state.makers.registry.makers || [];
|
||||||
const listSellersProviders = state.providers.rendezvous.providers || [];
|
const listSellersMakers = state.makers.rendezvous.makers || [];
|
||||||
const all = [...registryProviders, ...listSellersProviders];
|
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 { Seller } from "models/tauriModel";
|
||||||
import { isTestnet } from "store/config";
|
import { isTestnet } from "store/config";
|
||||||
import { splitPeerIdFromMultiAddress } from "./parseUtils";
|
import { splitPeerIdFromMultiAddress } from "./parseUtils";
|
||||||
|
@ -45,12 +45,12 @@ export function secondsToDays(seconds: number): number {
|
||||||
return seconds / 86400;
|
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
|
// 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.
|
// the models returned by the public registry and the models used internally.
|
||||||
export function rendezvousSellerToProviderStatus(
|
export function rendezvousSellerToMakerStatus(
|
||||||
seller: Seller,
|
seller: Seller,
|
||||||
): ProviderStatus | null {
|
): MakerStatus | null {
|
||||||
if (seller.status.type === "Unreachable") {
|
if (seller.status.type === "Unreachable") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
import { ExtendedProviderStatus, Provider } from "models/apiModel";
|
import { ExtendedMakerStatus, Maker } from "models/apiModel";
|
||||||
import { Multiaddr } from "multiaddr";
|
import { Multiaddr } from "multiaddr";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import { isTestnet } from "store/config";
|
import { isTestnet } from "store/config";
|
||||||
|
|
||||||
const MIN_ASB_VERSION = "1.0.0-alpha.1"
|
const MIN_ASB_VERSION = "1.0.0-alpha.1"
|
||||||
|
|
||||||
export function providerToConcatenatedMultiAddr(provider: Provider) {
|
export function providerToConcatenatedMultiAddr(provider: Maker) {
|
||||||
return new Multiaddr(provider.multiAddr)
|
return new Multiaddr(provider.multiAddr)
|
||||||
.encapsulate(`/p2p/${provider.peerId}`)
|
.encapsulate(`/p2p/${provider.peerId}`)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isProviderOnCorrectNetwork(
|
export function isMakerOnCorrectNetwork(
|
||||||
provider: ExtendedProviderStatus,
|
provider: ExtendedMakerStatus,
|
||||||
): boolean {
|
): boolean {
|
||||||
return provider.testnet === isTestnet();
|
return provider.testnet === isTestnet();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isProviderOutdated(provider: ExtendedProviderStatus): boolean {
|
export function isMakerOutdated(provider: ExtendedMakerStatus): boolean {
|
||||||
if (provider.version != null) {
|
if (provider.version != null) {
|
||||||
if (semver.satisfies(provider.version, `>=${MIN_ASB_VERSION}`))
|
if (semver.satisfies(provider.version, `>=${MIN_ASB_VERSION}`))
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { ExtendedProviderStatus } from "models/apiModel";
|
import { ExtendedMakerStatus } from "models/apiModel";
|
||||||
import { isProviderOnCorrectNetwork, isProviderOutdated } from "./multiAddrUtils";
|
import { isMakerOnCorrectNetwork, isMakerOutdated } from "./multiAddrUtils";
|
||||||
|
|
||||||
export function sortProviderList(list: ExtendedProviderStatus[]) {
|
export function sortMakerList(list: ExtendedMakerStatus[]) {
|
||||||
return list
|
return list
|
||||||
// Filter out providers that are on the wrong network (testnet / mainnet)
|
// Filter out makers that are on the wrong network (testnet / mainnet)
|
||||||
.filter(isProviderOnCorrectNetwork)
|
.filter(isMakerOnCorrectNetwork)
|
||||||
.concat()
|
.concat()
|
||||||
// Sort by criteria
|
// Sort by criteria
|
||||||
.sort((firstEl, secondEl) => {
|
.sort((firstEl, secondEl) => {
|
||||||
// If either provider is outdated, prioritize the one that isn't
|
// If either provider is outdated, prioritize the one that isn't
|
||||||
if (isProviderOutdated(firstEl) && !isProviderOutdated(secondEl)) return 1;
|
if (isMakerOutdated(firstEl) && !isMakerOutdated(secondEl)) return 1;
|
||||||
if (!isProviderOutdated(firstEl) && isProviderOutdated(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 neither of them have a relevancy score or they are the same, sort by price
|
||||||
if (firstEl.relevancy == secondEl.relevancy) {
|
if (firstEl.relevancy == secondEl.relevancy) {
|
||||||
|
@ -24,7 +24,7 @@ export function sortProviderList(list: ExtendedProviderStatus[]) {
|
||||||
// Otherwise, sort by relevancy score
|
// Otherwise, sort by relevancy score
|
||||||
return secondEl.relevancy - firstEl.relevancy;
|
return secondEl.relevancy - firstEl.relevancy;
|
||||||
})
|
})
|
||||||
// Remove duplicate providers
|
// Remove duplicate makers
|
||||||
.filter((provider, index, self) =>
|
.filter((provider, index, self) =>
|
||||||
index === self.findIndex((p) => p.peerId === provider.peerId)
|
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"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.6.tgz#193ced6a40c8006cfc1ca3f4553444fb38f0e543"
|
||||||
integrity sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==
|
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":
|
"@types/node@^20.14.10":
|
||||||
version "20.14.10"
|
version "20.14.10"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a"
|
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"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz#32c467d4bf1f1a0faa63fc793c2ba81169e7652f"
|
||||||
integrity sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==
|
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:
|
chai@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c"
|
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"
|
reflect.getprototypeof "^1.0.4"
|
||||||
set-function-name "^2.0.1"
|
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:
|
joycon@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
|
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"
|
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
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:
|
update-browserslist-db@^1.0.16:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e"
|
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).
|
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.
|
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)
|
.estimate_fee(TxCancel::weight(), btc_amount)
|
||||||
.await?;
|
.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(
|
event_emitter.emit_swap_progress_event(
|
||||||
swap_id,
|
swap_id,
|
||||||
TauriSwapProgressEvent::SwapSetupInflight {
|
TauriSwapProgressEvent::SwapSetupInflight {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue