feat(gui): Allow discovery of sellers via connecting to rendezvous point (#83)

We,
- add a new list_sellers Tauri IPC command
- we rename the Seller struct to AliceAddress to name clash
This commit is contained in:
binarybaron 2024-09-19 00:40:51 +02:00 committed by GitHub
parent beccd23280
commit 167e031172
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 164 additions and 57 deletions

View file

@ -1,3 +1,9 @@
// This file is responsible for making HTTP requests to the Unstoppable API and to the CoinGecko API.
// The APIs are used to:
// - fetch provider status from the public registry
// - fetch alerts to be displayed to the user
// - and to submit feedback
// - fetch currency rates from CoinGecko
import { Alert, ExtendedProviderStatus } from "models/apiModel";
const API_BASE_URL = "https://api.unstoppableswap.net";

View file

@ -11,11 +11,15 @@ import {
TextField,
Theme,
} from "@material-ui/core";
import { Multiaddr } from "multiaddr";
import { ListSellersResponse } from "models/tauriModel";
import { useSnackbar } from "notistack";
import { ChangeEvent, useState } from "react";
import TruncatedText from "renderer/components/other/TruncatedText";
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
import { listSellersAtRendezvousPoint } from "renderer/rpc";
import { discoveredProvidersByRendezvous } from "store/features/providersSlice";
import { useAppDispatch } from "store/hooks";
import { isValidMultiAddressWithPeerId } from "utils/parseUtils";
const PRESET_RENDEZVOUS_POINTS = [
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
@ -42,27 +46,23 @@ export default function ListSellersDialog({
const classes = useStyles();
const [rendezvousAddress, setRendezvousAddress] = useState("");
const { enqueueSnackbar } = useSnackbar();
const dispatch = useAppDispatch();
function handleMultiAddrChange(event: ChangeEvent<HTMLInputElement>) {
setRendezvousAddress(event.target.value);
}
function getMultiAddressError(): string | null {
try {
const multiAddress = new Multiaddr(rendezvousAddress);
if (!multiAddress.protoNames().includes("p2p")) {
return "The multi address must contain the peer id (/p2p/)";
}
return null;
} catch {
return "Not a valid multi address";
}
return isValidMultiAddressWithPeerId(rendezvousAddress) ? null : "Address is invalid or missing peer ID";
}
function handleSuccess(amountOfSellers: number) {
function handleSuccess({ sellers }: ListSellersResponse) {
dispatch(discoveredProvidersByRendezvous(sellers));
const discoveredSellersCount = sellers.length;
let message: string;
switch (amountOfSellers) {
switch (discoveredSellersCount) {
case 0:
message = `No providers were discovered at the rendezvous point`;
break;
@ -70,7 +70,7 @@ export default function ListSellersDialog({
message = `Discovered one provider at the rendezvous point`;
break;
default:
message = `Discovered ${amountOfSellers} providers at the rendezvous point`;
message = `Discovered ${discoveredSellersCount} providers at the rendezvous point`;
}
enqueueSnackbar(message, {
@ -119,12 +119,13 @@ export default function ListSellersDialog({
<Button onClick={onClose}>Cancel</Button>
<PromiseInvokeButton
variant="contained"
disabled={!(rendezvousAddress && !getMultiAddressError())}
disabled={
// We disable the button if the multiaddress is invalid
getMultiAddressError() !== null
}
color="primary"
onSuccess={handleSuccess}
onInvoke={() => {
throw new Error("Not implemented");
}}
onInvoke={() => listSellersAtRendezvousPoint(rendezvousAddress)}
>
Connect
</PromiseInvokeButton>

View file

@ -39,7 +39,7 @@ export default function ProviderInfo({
{provider.multiAddr}
</Typography>
<Typography color="textSecondary" gutterBottom>
<TruncatedText limit={12}>{provider.peerId}</TruncatedText>
<TruncatedText>{provider.peerId}</TruncatedText>
</Typography>
<Typography variant="caption">
Exchange rate:{" "}

View file

@ -177,7 +177,7 @@ function HasNoProvidersSwapWidget() {
const forceShowDialog = useAppSelector((state) => state.swap.state !== null);
const isPublicRegistryDown = useAppSelector((state) =>
isRegistryDown(
state.providers.registry.failedReconnectAttemptsSinceLastSuccess,
state.providers.registry.connectionFailsCount,
),
);
const classes = useStyles();
@ -255,7 +255,7 @@ export default function SwapWidget() {
(state) =>
state.providers.registry.providers === null &&
!isRegistryDown(
state.providers.registry.failedReconnectAttemptsSinceLastSuccess,
state.providers.registry.connectionFailsCount,
),
);

View file

@ -2,7 +2,10 @@ import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { setAlerts } from "store/features/alertsSlice";
import { setRegistryProviders } from "store/features/providersSlice";
import {
registryConnectionFailed,
setRegistryProviders,
} from "store/features/providersSlice";
import { setBtcPrice, setXmrPrice } from "store/features/ratesSlice";
import logger from "../utils/logger";
import {
@ -12,18 +15,9 @@ import {
fetchXmrPrice,
} from "./api";
import App from "./components/App";
import {
checkBitcoinBalance,
getAllSwapInfos,
initEventListeners,
} from "./rpc";
import { initEventListeners } from "./rpc";
import { persistor, store } from "./store/storeRenderer";
setInterval(() => {
checkBitcoinBalance();
getAllSwapInfos();
}, 30 * 1000);
const container = document.getElementById("root");
const root = createRoot(container!);
root.render(
@ -44,6 +38,7 @@ async function fetchInitialData() {
"Fetched providers via UnstoppableSwap HTTP API",
);
} catch (e) {
store.dispatch(registryConnectionFailed());
logger.error(e, "Failed to fetch providers via UnstoppableSwap HTTP API");
}

View file

@ -8,6 +8,7 @@ import {
GetLogsArgs,
GetLogsResponse,
GetSwapInfoResponse,
ListSellersArgs,
MoneroRecoveryArgs,
ResumeSwapArgs,
ResumeSwapResponse,
@ -27,6 +28,7 @@ import { store } from "./store/storeRenderer";
import { Provider } from "models/apiModel";
import { providerToConcatenatedMultiAddr } from "utils/multiAddrUtils";
import { MoneroRecoveryResponse } from "models/rpcModel";
import { ListSellersResponse } from "../models/tauriModel";
export async function initEventListeners() {
// This operation is in-expensive
@ -144,3 +146,11 @@ export async function getLogsOfSwap(
redact,
});
}
export async function listSellersAtRendezvousPoint(
rendezvousPointAddress: string,
): Promise<ListSellersResponse> {
return await invoke<ListSellersArgs, ListSellersResponse>("list_sellers", {
rendezvous_point: rendezvousPointAddress,
});
}