Skip to content

Commit

Permalink
feat: Maker avatar (#205)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
binarybaron authored Nov 25, 2024
1 parent 23d22b5 commit b2e74df
Show file tree
Hide file tree
Showing 36 changed files with 511 additions and 429 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- GUI: Changed terminology from "swap providers" to "makers"
- GUI: For each maker, we now display a unique deterministically generated avatar derived from the maker's public key

## [1.0.0-rc.6] - 2024-11-21

- CLI + GUI: Tor is now bundled with the application. All libp2p connections between peers are routed through Tor, if the `--enable-tor` flag is set. The `--tor-socks5-port` argument has been removed. This feature is powered by [arti](https://tpo.pages.torproject.net/core/arti/), an implementation of the Tor protocol in Rust by the Tor Project.
Expand Down
26 changes: 13 additions & 13 deletions docs/components/SwapProviderTable.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { useState, useEffect } from "react";
import { Table, Td, Th, Tr } from 'nextra/components'

export default function SwapProviderTable() {
export default function SwapMakerTable() {
function satsToBtc(sats) {
return sats / 100000000;
}

async function getProviders() {
async function getMakers() {
const response = await fetch("https://api.unstoppableswap.net/api/list");
const data = await response.json();
return data;
}

const [providers, setProviders] = useState([]);
const [makers, setMakers] = useState([]);

useEffect(() => {
getProviders().then((data) => {
setProviders(data);
getMakers().then((data) => {
setMakers(data);
});
}, []);

Expand All @@ -38,16 +38,16 @@ export default function SwapProviderTable() {
</Tr>
</thead>
<tbody>
{providers.map((provider) => (
<Tr key={provider.peerId}>
{makers.map((maker) => (
<Tr key={maker.peerId}>
<Td>
{provider.testnet ? "Testnet" : "Mainnet"}
{maker.testnet ? "Testnet" : "Mainnet"}
</Td>
<Td>{provider.multiAddr}</Td>
<Td>{provider.peerId}</Td>
<Td>{satsToBtc(provider.minSwapAmount)} BTC</Td>
<Td>{satsToBtc(provider.maxSwapAmount)} BTC</Td>
<Td>{satsToBtc(provider.price)} XMR/BTC</Td>
<Td>{maker.multiAddr}</Td>
<Td>{maker.peerId}</Td>
<Td>{satsToBtc(maker.minSwapAmount)} BTC</Td>
<Td>{satsToBtc(maker.maxSwapAmount)} BTC</Td>
<Td>{satsToBtc(maker.price)} XMR/BTC</Td>
</Tr>
))}
</tbody>
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/usage/_meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"first_swap": "Complete your first swap",
"market_maker_discovery": "Swap Provider discovery",
"market_maker_discovery": "Maker discovery",
"refund_punish": "Cancel, Refund and Punish explained"
}
16 changes: 8 additions & 8 deletions docs/pages/usage/first_swap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ import { Steps } from 'nextra/components'

## Performing the swap
<Steps>
### Choose a _Swap Provider_ to swap with
### Choose a _maker_ to swap with

In the bottom of the screen you can see the currently selected _Swap Provider_.
In the bottom of the screen you can see the currently selected _maker_.
This is who you'll send your Bitcoin to and who you'll receive the Monero from.
You can change the _Swap Provider_ by clicking on the arrow and selecting a different _Swap Provider_ from the list.
You can change the _maker_ by clicking on the arrow and selecting a different _maker_ from the list.

import { Callout } from 'nextra/components'

<Callout type="info">
Different _Swap Providers_ offer different exchange rates and differing amounts of liquidity. You may want to choose the _Swap Provider_ that best suits your needs.
Different _makers_ offer different exchange rates and differing amounts of liquidity. You may want to choose the _maker_ that best suits your needs.
</Callout>

You can use the input field to calculate the approximate amount of Monero you'll receive for a given amount of Bitcoin.
Expand All @@ -39,7 +39,7 @@ This is only used as a reference for you to get a rough idea of how much Monero

### Start the Swap

Once you've selected a _Swap Provider_, you can start the swap by clicking the `Swap` button.
Once you've selected a _maker_, you can start the swap by clicking the `Swap` button.
This will open a new window where you need to enter two addresses:

1. the Monero address you want to receive the Monero to
Expand All @@ -54,7 +54,7 @@ After pressing the <img src="/start_swap_button.png" style={{
display: "inline-block",
// center vertically
verticalAlign: "middle",
}}/> button, you'll be shown an offer by the _Swap Provider_. This includes:
}}/> button, you'll be shown an offer by the _maker_. This includes:

- the exchange rate (how much Bitcoin they demand for 1 Monero)
- the minimum and maximum amounts you can swap
Expand Down Expand Up @@ -88,10 +88,10 @@ The swap will go through four stages:
1. **Locking the Bitcoin**:
Your Bitcoin is locked in a 2-of-2 multisig address.

2. **_Swap Provider_ locks the Monero**:
2. **_Maker_ locks the Monero**:
The other party locks their Monero as well.

3. **_Swap Provider_ redeems _Bitcoin_**:
3. **_Maker_ redeems _Bitcoin_**:
The other party redeems the Bitcoin.

4. **Redeeming the Monero**:
Expand Down
28 changes: 14 additions & 14 deletions docs/pages/usage/market_maker_discovery.mdx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# _Swap Provider_ discovery
# _Maker_ discovery

A _Swap Provider_ is a service run by a pseudonymous entity that offers to sell Monero in exchange for Bitcoin. To swap your Bitcoin for Monero you need to connect to a one of these _Swap Providers_.
The different ways to discover _Swap Providers_ are described below.
A _maker_ is a service run by a pseudonymous entity that offers to sell Monero in exchange for Bitcoin. To swap your Bitcoin for Monero you need to connect to a one of these _makers_.
The different ways to discover _makers_ are described below.

There are two ways to discover _Swap Providers_:
There are two ways to discover _makers_:

1. **Public Registry**: Community volunteers maintain a list of _Swap Providers_ that is provided to the GUI and is kept up to date automatically. This list is displayed in the GUI by default. The _Public Registry_ also stores additional information about the _Swap Providers_ such as their uptime and age, and makes it available to the GUI.
2. **Rendezvous**: The GUI can discover Swap Providers using the [_Rendezvous_ protocol](https://docs.libp2p.io/concepts/discovery-routing/rendezvous/). This protocol enables the GUI to find providers that register themselves at a _Rendezvous Point_. The GUI can query these points to get a list of registered providers. _Rendezvous Points_ are operated by community volunteers, and anyone can run one. The GUI can connect to various _Rendezvous Points_ to discover different _Swap Providers_.
1. **Public Registry**: Community volunteers maintain a list of _makers_ that is provided to the GUI and is kept up to date automatically. This list is displayed in the GUI by default. The _Public Registry_ also stores additional information about the _makers_ such as their uptime and age, and makes it available to the GUI.
2. **Rendezvous**: The GUI can discover makers using the [_Rendezvous_ protocol](https://docs.libp2p.io/concepts/discovery-routing/rendezvous/). This protocol enables the GUI to find makers that register themselves at a _Rendezvous Point_. The GUI can query these points to get a list of registered makers. _Rendezvous Points_ are operated by community volunteers, and anyone can run one. The GUI can connect to various _Rendezvous Points_ to discover different _makers_.

## _Public Registry_

The providers from the registry are displayed in the GUI. If you want to connect to them directly without the GUI choose one from the table below.
The makers from the registry are displayed in the GUI. If you want to connect to them directly without the GUI choose one from the table below.

import SwapProviderTable from "../../components/SwapProviderTable";

Expand All @@ -19,9 +19,9 @@ import SwapProviderTable from "../../components/SwapProviderTable";
<SwapProviderTable />
</div>

## How to discover _Swap Providers_ via _Rendezvous_
## How to discover _makers_ via _Rendezvous_

1. Open the _Swap Provider_ list by clicking the right-facing arrow in the widget on the _Swap_ tab.
1. Open the _maker_ list by clicking the right-facing arrow in the widget on the _Swap_ tab.

<img src="/rendezvous_1.png" />

Expand All @@ -30,14 +30,14 @@ import SwapProviderTable from "../../components/SwapProviderTable";
display: "inline-block",
// center vertically
verticalAlign: "middle",
}}/> button to open the _Discover swap providers_ dialog. Enter the _Multiaddress_ of the _Rendezvous Point_ you want to connect to. You can also choose one of the predined ones from the list below the Textfield. Click the _Connect_ button to connect to the rendezvous point.
}}/> button to open the _Discover makers_ dialog. Enter the _Multiaddress_ of the _Rendezvous Point_ you want to connect to. You can also choose one of the predined ones from the list below the Textfield. Click the _Connect_ button to connect to the rendezvous point.
<img src="/rendezvous_2.png" />

## How to add a _Swap Provider_ to the _Public Registry_
## How to add a _maker_ to the _Public Registry_

If you know of a _Swap Provider_ that is not yet in the _Public Registry_, you can submit it manually. Here's how you can do it:
If you know of a _maker_ that is not yet in the _Public Registry_, you can submit it manually. Here's how you can do it:

1. Open the _Swap Provider_ list by clicking the right-facing arrow in the widget on the _Swap_ tab.
1. Open the _maker_ list by clicking the right-facing arrow in the widget on the _Swap_ tab.

<img src="/rendezvous_1.png" />

Expand All @@ -46,5 +46,5 @@ If you know of a _Swap Provider_ that is not yet in the _Public Registry_, you c
display: "inline-block",
// center vertically
verticalAlign: "middle",
}}/> button. Enter the _Multiaddress_ of the _Swap Provider_ as well as the _Peer ID_ of the provider. Click the _Submit_ button to submit the provider to the _Public Registry_.
}}/> button. Enter the _Multiaddress_ of the _maker_ as well as the _Peer ID_ of the provider. Click the _Submit_ button to submit the provider to the _Public Registry_.
<img src="/public_registry.png" />
10 changes: 5 additions & 5 deletions docs/pages/usage/refund_punish.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ We have chosen to include a fairly technical explanation here. However, the GUI

## Cancel

If the _Swap Provider_ has not been able to redeem the Bitcoin within 12 hours (72 Bitcoin blocks) from the start of the swap, the swap will be cancelled.
This is done by either you or the _Swap Provider_ publishing a special Bitcoin transaction called the `Bitcoin Cancel Transaction`.
If the _maker_ has not been able to redeem the Bitcoin within 12 hours (72 Bitcoin blocks) from the start of the swap, the swap will be cancelled.
This is done by either you or the _maker_ publishing a special Bitcoin transaction called the `Bitcoin Cancel Transaction`.
As soon as this transaction is included in the Bitcoin blockchain, the swap is locked in a state where only the [_Refund_](#refund) and [_Punish_](#punish) paths can be activated. The _Happy Path_ path where you redeem the Monero is no longer possible.

## Refund

As soon as the swap is cancelled, you can refund your Bitcoin. This is done by publishing the `Bitcoin Refund Transaction` on the Bitcoin blockchain.
If this is done within 24 hours (144 Bitcoin blocks) from the inclusion of the `Bitcoin Cancel Transaction`, you will get your Bitcoin back.
If you do not refund your Bitcoin within this time frame, the _Swap Provider_ can punish you. This is a security measure to ensure that you do not cancel the swap and then refuse to refund your Bitcoin which would result in the _Swap Provider_ losing their Monero.
If you do not refund your Bitcoin within this time frame, the _maker_ can punish you. This is a security measure to ensure that you do not cancel the swap and then refuse to refund your Bitcoin which would result in the _maker_ losing their Monero.

## Punish

If you do not refund your Bitcoin within 24 hours (144 Bitcoin blocks) from the inclusion of the `Bitcoin Cancel Transaction`, the _Swap Provider_ will _punish_ you. This will result in the _Swap Provider_ taking your Bitcoin as a penalty for not refunding it in time.
Even if this state is reached and the _Swap Provider_ has punished you, there's still hope to redeem the Monero. The _Swap Provider_ can choose to allow you to redeem the Monero by transmitting a secret key to you. This however is at the discretion of the _Swap Provider_ and they are not obligated to do so.
If you do not refund your Bitcoin within 24 hours (144 Bitcoin blocks) from the inclusion of the `Bitcoin Cancel Transaction`, the _maker_ will _punish_ you. This will result in the _maker_ taking your Bitcoin as a penalty for not refunding it in time.
Even if this state is reached and the _maker_ has punished you, there's still hope to redeem the Monero. The _maker_ can choose to allow you to redeem the Monero by transmitting a secret key to you. This however is at the discretion of the _maker_ and they are not obligated to do so.
1 change: 1 addition & 0 deletions src-gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@tauri-apps/plugin-updater": "^2.0.0",
"@types/react-redux": "^7.1.34",
"humanize-duration": "^3.32.1",
"jdenticon": "^3.3.0",
"lodash": "^4.17.21",
"multiaddr": "^10.0.1",
"notistack": "^3.0.1",
Expand Down
8 changes: 4 additions & 4 deletions src-gui/src/models/apiModel.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
export interface ExtendedProviderStatus extends ProviderStatus {
export interface ExtendedMakerStatus extends MakerStatus {
uptime?: number;
age?: number;
relevancy?: number;
version?: string;
recommended?: boolean;
}

export interface ProviderStatus extends ProviderQuote, Provider {}
export interface MakerStatus extends MakerQuote, Maker { }

export interface ProviderQuote {
export interface MakerQuote {
price: number;
minSwapAmount: number;
maxSwapAmount: number;
}

export interface Provider {
export interface Maker {
multiAddr: string;
testnet: boolean;
peerId: string;
Expand Down
14 changes: 7 additions & 7 deletions src-gui/src/renderer/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@
// - and to submit feedback
// - fetch currency rates from CoinGecko

import { Alert, ExtendedProviderStatus } from "models/apiModel";
import { Alert, ExtendedMakerStatus } from "models/apiModel";
import { store } from "./store/storeRenderer";
import { setBtcPrice, setXmrBtcRate, setXmrPrice } from "store/features/ratesSlice";
import { FiatCurrency } from "store/features/settingsSlice";
import { setAlerts } from "store/features/alertsSlice";
import { registryConnectionFailed, setRegistryProviders } from "store/features/providersSlice";
import { registryConnectionFailed, setRegistryMakers } from "store/features/makersSlice";
import logger from "utils/logger";

const PUBLIC_REGISTRY_API_BASE_URL = "https://api.unstoppableswap.net";

async function fetchProvidersViaHttp(): Promise<
ExtendedProviderStatus[]
async function fetchMakersViaHttp(): Promise<
ExtendedMakerStatus[]
> {
const response = await fetch(`${PUBLIC_REGISTRY_API_BASE_URL}/api/list`);
return (await response.json()) as ExtendedProviderStatus[];
return (await response.json()) as ExtendedMakerStatus[];
}

async function fetchAlertsViaHttp(): Promise<Alert[]> {
Expand Down Expand Up @@ -114,8 +114,8 @@ export async function updateRates(): Promise<void> {
*/
export async function updatePublicRegistry(): Promise<void> {
try {
const providers = await fetchProvidersViaHttp();
store.dispatch(setRegistryProviders(providers));
const providers = await fetchMakersViaHttp();
store.dispatch(setRegistryMakers(providers));
} catch (error) {
store.dispatch(registryConnectionFailed());
logger.error(error, "Error fetching providers");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function RemainingFundsWillBeUsedAlert() {
>
The remaining funds of <SatsAmount amount={balance} /> in the wallet
will be used for the next swap. If the remaining funds exceed the
minimum swap amount of the provider, a swap will be initiated
minimum swap amount of the maker, a swap will be initiated
instantaneously.
</Alert>
</Box>
Expand Down
29 changes: 29 additions & 0 deletions src-gui/src/renderer/components/icons/IdentIcon.tsx
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { ChangeEvent, useState } from "react";
import TruncatedText from "renderer/components/other/TruncatedText";
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
import { listSellersAtRendezvousPoint, PRESET_RENDEZVOUS_POINTS } from "renderer/rpc";
import { discoveredProvidersByRendezvous } from "store/features/providersSlice";
import { discoveredMakersByRendezvous } from "store/features/makersSlice";
import { useAppDispatch } from "store/hooks";
import { isValidMultiAddressWithPeerId } from "utils/parseUtils";

Expand Down Expand Up @@ -54,20 +54,20 @@ export default function ListSellersDialog({
}

function handleSuccess({ sellers }: ListSellersResponse) {
dispatch(discoveredProvidersByRendezvous(sellers));
dispatch(discoveredMakersByRendezvous(sellers));

const discoveredSellersCount = sellers.length;
let message: string;

switch (discoveredSellersCount) {
case 0:
message = `No providers were discovered at the rendezvous point`;
message = `No makers were discovered at the rendezvous point`;
break;
case 1:
message = `Discovered one provider at the rendezvous point`;
message = `Discovered one maker at the rendezvous point`;
break;
default:
message = `Discovered ${discoveredSellersCount} providers at the rendezvous point`;
message = `Discovered ${discoveredSellersCount} makers at the rendezvous point`;
}

enqueueSnackbar(message, {
Expand All @@ -80,13 +80,13 @@ export default function ListSellersDialog({

return (
<Dialog onClose={onClose} open={open}>
<DialogTitle>Discover swap providers</DialogTitle>
<DialogTitle>Discover makers</DialogTitle>
<DialogContent dividers>
<DialogContentText>
The rendezvous protocol provides a way to discover providers (trading
The rendezvous protocol provides a way to discover makers (trading
partners) without relying on one singular centralized institution. By
manually connecting to a rendezvous point run by a volunteer, you can
discover providers and then connect and swap with them.
discover makers and then connect and swap with them.
</DialogContentText>
<TextField
autoFocus
Expand Down
Loading

0 comments on commit b2e74df

Please sign in to comment.