mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-04-28 03:36:16 -04:00
Merge changes from legacy GUI, allow daemon logs to be attached to feedback (#115)
This PR applies all remaining changes from https://github.com/UnstoppableSwap/unstoppableswap-gui/pull/210 - Added checkbox option to attach daemon logs when submitting feedback - Added "Outdated" chip with warning icon for providers running outdated asb versions - Updated `BitcoinPunishedPage` to display different messages for BtcPunished and CooperativeRedeemRejected states (including reason for failed cooperative redeem) - Added "Attempt recovery" button for swaps in BtcPunished state - Modified `getBitcoinTxExplorerUrl` to use mempool.space instead of blockchair.com - Added `useResumeableSwapsCountExcludingPunished` hook to count resumable swaps excluding punished ones, use it for the badge and alert - Updated `sortProviderList` function to filter out incompatible providers before sorting - Added `TauriSwapProgressEventExt` type to extract specific event types from TauriSwapProgressEvent
This commit is contained in:
parent
639f540876
commit
2bffe40a37
@ -10,6 +10,8 @@ export type TauriSwapProgressEventContent<
|
|||||||
T extends TauriSwapProgressEventType,
|
T extends TauriSwapProgressEventType,
|
||||||
> = Extract<TauriSwapProgressEvent, { type: T }>["content"];
|
> = Extract<TauriSwapProgressEvent, { type: T }>["content"];
|
||||||
|
|
||||||
|
export type TauriSwapProgressEventExt<T extends TauriSwapProgressEventType> = Extract<TauriSwapProgressEvent, { type: T }>;
|
||||||
|
|
||||||
// See /swap/src/protocol/bob/state.rs#L57
|
// See /swap/src/protocol/bob/state.rs#L57
|
||||||
// TODO: Replace this with a typeshare definition
|
// TODO: Replace this with a typeshare definition
|
||||||
export enum BobStateName {
|
export enum BobStateName {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Button } from "@material-ui/core";
|
import { Button } from "@material-ui/core";
|
||||||
import Alert from "@material-ui/lab/Alert";
|
import Alert from "@material-ui/lab/Alert";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useResumeableSwapsCount } from "store/hooks";
|
import { useResumeableSwapsCountExcludingPunished } from "store/hooks";
|
||||||
|
|
||||||
export default function UnfinishedSwapsAlert() {
|
export default function UnfinishedSwapsAlert() {
|
||||||
const resumableSwapsCount = useResumeableSwapsCount();
|
const resumableSwapsCount = useResumeableSwapsCountExcludingPunished();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
if (resumableSwapsCount > 0) {
|
if (resumableSwapsCount > 0) {
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
Paper,
|
||||||
Select,
|
Select,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
@ -21,7 +25,7 @@ import LoadingButton from "../../other/LoadingButton";
|
|||||||
import { PiconeroAmount } from "../../other/Units";
|
import { PiconeroAmount } from "../../other/Units";
|
||||||
import { getLogsOfSwap } from "renderer/rpc";
|
import { getLogsOfSwap } from "renderer/rpc";
|
||||||
|
|
||||||
async function submitFeedback(body: string, swapId: string | number) {
|
async function submitFeedback(body: string, swapId: string | number, submitDaemonLogs: boolean) {
|
||||||
let attachedBody = "";
|
let attachedBody = "";
|
||||||
|
|
||||||
if (swapId !== 0 && typeof swapId === "string") {
|
if (swapId !== 0 && typeof swapId === "string") {
|
||||||
@ -39,6 +43,13 @@ async function submitFeedback(body: string, swapId: string | number) {
|
|||||||
.join("\n====\n")}`;
|
.join("\n====\n")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (submitDaemonLogs) {
|
||||||
|
const logs = store.getState().rpc?.logs ?? [];
|
||||||
|
attachedBody += `\n\nDaemon Logs: ${logs
|
||||||
|
.map((l) => JSON.stringify(l))
|
||||||
|
.join("\n====\n")}`;
|
||||||
|
}
|
||||||
|
|
||||||
await submitFeedbackViaHttp(body, attachedBody);
|
await submitFeedbackViaHttp(body, attachedBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +77,7 @@ function SwapSelectDropDown({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={(e) => setSelectedSwap(e.target.value as string)}
|
onChange={(e) => setSelectedSwap(e.target.value as string)}
|
||||||
>
|
>
|
||||||
<MenuItem value={0}>Do not attach logs</MenuItem>
|
<MenuItem value={0}>Do not attach a swap</MenuItem>
|
||||||
{swaps.map((swap) => (
|
{swaps.map((swap) => (
|
||||||
<MenuItem value={swap.swap_id} key={swap.swap_id}>
|
<MenuItem value={swap.swap_id} key={swap.swap_id}>
|
||||||
Swap <TruncatedText>{swap.swap_id}</TruncatedText> from{" "}
|
Swap <TruncatedText>{swap.swap_id}</TruncatedText> from{" "}
|
||||||
@ -96,6 +107,7 @@ export default function FeedbackDialog({
|
|||||||
const [selectedAttachedSwap, setSelectedAttachedSwap] = useState<
|
const [selectedAttachedSwap, setSelectedAttachedSwap] = useState<
|
||||||
string | number
|
string | number
|
||||||
>(currentSwapId?.swap_id || 0);
|
>(currentSwapId?.swap_id || 0);
|
||||||
|
const [attachDaemonLogs, setAttachDaemonLogs] = useState(true);
|
||||||
|
|
||||||
const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH;
|
const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH;
|
||||||
|
|
||||||
@ -106,9 +118,9 @@ export default function FeedbackDialog({
|
|||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
Got something to say? Drop us a message below. If you had an issue
|
Got something to say? Drop us a message below. If you had an issue
|
||||||
with a specific swap, select it from the dropdown to attach the logs.
|
with a specific swap, select it from the dropdown to attach the logs.
|
||||||
It will help us figure out what went wrong. Hit that submit button
|
It will help us figure out what went wrong.
|
||||||
when you are ready. We appreciate you taking the time to share your
|
<br />
|
||||||
thoughts!
|
We appreciate you taking the time to share your thoughts! Every feedback is read by a core developer!
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
@ -124,7 +136,7 @@ export default function FeedbackDialog({
|
|||||||
label={
|
label={
|
||||||
bodyTooLong
|
bodyTooLong
|
||||||
? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})`
|
? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})`
|
||||||
: "Feedback"
|
: "Message"
|
||||||
}
|
}
|
||||||
multiline
|
multiline
|
||||||
minRows={4}
|
minRows={4}
|
||||||
@ -136,6 +148,18 @@ export default function FeedbackDialog({
|
|||||||
selectedSwap={selectedAttachedSwap}
|
selectedSwap={selectedAttachedSwap}
|
||||||
setSelectedSwap={setSelectedAttachedSwap}
|
setSelectedSwap={setSelectedAttachedSwap}
|
||||||
/>
|
/>
|
||||||
|
<Paper variant="outlined" style={{ padding: "0.5rem" }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
color="primary"
|
||||||
|
checked={attachDaemonLogs}
|
||||||
|
onChange={(e) => setAttachDaemonLogs(e.target.checked)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Attach daemon logs"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
@ -150,7 +174,7 @@ export default function FeedbackDialog({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setPending(true);
|
setPending(true);
|
||||||
await submitFeedback(bodyText, selectedAttachedSwap);
|
await submitFeedback(bodyText, selectedAttachedSwap, attachDaemonLogs);
|
||||||
enqueueSnackbar("Feedback submitted successfully!", {
|
enqueueSnackbar("Feedback submitted successfully!", {
|
||||||
variant: "success",
|
variant: "success",
|
||||||
});
|
});
|
||||||
|
@ -7,6 +7,8 @@ import {
|
|||||||
SatsAmount,
|
SatsAmount,
|
||||||
} from "renderer/components/other/Units";
|
} from "renderer/components/other/Units";
|
||||||
import { satsToBtc, secondsToDays } from "utils/conversionUtils";
|
import { satsToBtc, secondsToDays } from "utils/conversionUtils";
|
||||||
|
import { isProviderOutdated } from 'utils/multiAddrUtils';
|
||||||
|
import WarningIcon from '@material-ui/icons/Warning';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
content: {
|
content: {
|
||||||
@ -29,6 +31,7 @@ export default function ProviderInfo({
|
|||||||
provider: ExtendedProviderStatus;
|
provider: ExtendedProviderStatus;
|
||||||
}) {
|
}) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const isOutdated = isProviderOutdated(provider);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={classes.content}>
|
<Box className={classes.content}>
|
||||||
@ -70,6 +73,11 @@ export default function ProviderInfo({
|
|||||||
<Chip label="Recommended" icon={<VerifiedUser />} color="primary" />
|
<Chip label="Recommended" icon={<VerifiedUser />} color="primary" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{isOutdated && (
|
||||||
|
<Tooltip title="This provider is running an outdated 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>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box } from "@material-ui/core";
|
import { Box } from "@material-ui/core";
|
||||||
import { SwapSlice, SwapState } from "models/storeModel";
|
import { SwapState } from "models/storeModel";
|
||||||
import CircularProgressWithSubtitle from "../CircularProgressWithSubtitle";
|
import CircularProgressWithSubtitle from "../CircularProgressWithSubtitle";
|
||||||
import BitcoinPunishedPage from "./done/BitcoinPunishedPage";
|
import BitcoinPunishedPage from "./done/BitcoinPunishedPage";
|
||||||
import BitcoinRefundedPage from "./done/BitcoinRefundedPage";
|
import BitcoinRefundedPage from "./done/BitcoinRefundedPage";
|
||||||
@ -52,7 +52,7 @@ export default function SwapStatePage({ state }: { state: SwapState | null }) {
|
|||||||
case "BtcRefunded":
|
case "BtcRefunded":
|
||||||
return <BitcoinRefundedPage {...state.curr.content} />;
|
return <BitcoinRefundedPage {...state.curr.content} />;
|
||||||
case "BtcPunished":
|
case "BtcPunished":
|
||||||
return <BitcoinPunishedPage />;
|
return <BitcoinPunishedPage state={state.curr} />;
|
||||||
case "AttemptingCooperativeRedeem":
|
case "AttemptingCooperativeRedeem":
|
||||||
return (
|
return (
|
||||||
<CircularProgressWithSubtitle description="Attempting to redeem the Monero with the help of the other party" />
|
<CircularProgressWithSubtitle description="Attempting to redeem the Monero with the help of the other party" />
|
||||||
@ -62,7 +62,7 @@ export default function SwapStatePage({ state }: { state: SwapState | null }) {
|
|||||||
<CircularProgressWithSubtitle description="The other party is cooperating with us to redeem the Monero..." />
|
<CircularProgressWithSubtitle description="The other party is cooperating with us to redeem the Monero..." />
|
||||||
);
|
);
|
||||||
case "CooperativeRedeemRejected":
|
case "CooperativeRedeemRejected":
|
||||||
return <BitcoinPunishedPage />;
|
return <BitcoinPunishedPage state={state.curr} />;
|
||||||
case "Released":
|
case "Released":
|
||||||
return <ProcessExitedPage prevState={state.prev} swapId={state.swapId} />;
|
return <ProcessExitedPage prevState={state.prev} swapId={state.swapId} />;
|
||||||
default:
|
default:
|
||||||
|
@ -1,13 +1,27 @@
|
|||||||
import { Box, DialogContentText } from "@material-ui/core";
|
import { Box, DialogContentText } from '@material-ui/core';
|
||||||
import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
|
import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox';
|
||||||
|
import { TauriSwapProgressEventExt } from 'models/tauriModelExt';
|
||||||
|
|
||||||
export default function BitcoinPunishedPage() {
|
export default function BitcoinPunishedPage({
|
||||||
|
state,
|
||||||
|
}: {
|
||||||
|
state: TauriSwapProgressEventExt<"BtcPunished"> | TauriSwapProgressEventExt<"CooperativeRedeemRejected">
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
Unfortunately, the swap was not successful, and you've incurred a
|
Unfortunately, the swap was unsuccessful. Since you did not refund in
|
||||||
penalty because the swap was not refunded in time. Both the Bitcoin and
|
time, the Bitcoin has been lost. However, with the cooperation of the
|
||||||
Monero are irretrievable.
|
other party, you might still be able to redeem the Monero, although this
|
||||||
|
is not guaranteed.{' '}
|
||||||
|
{state.type === "CooperativeRedeemRejected" && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
We tried to redeem the Monero with the other party's help, but it
|
||||||
|
was unsuccessful (reason: {state.content.reason}). Attempting again at a
|
||||||
|
later time might yield success. <br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<FeedbackInfoBox />
|
<FeedbackInfoBox />
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Badge } from "@material-ui/core";
|
import { Badge } from "@material-ui/core";
|
||||||
import { useResumeableSwapsCount } from "store/hooks";
|
import { useResumeableSwapsCountExcludingPunished } from "store/hooks";
|
||||||
|
|
||||||
export default function UnfinishedSwapsBadge({
|
export default function UnfinishedSwapsBadge({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
}) {
|
}) {
|
||||||
const resumableSwapsCount = useResumeableSwapsCount();
|
const resumableSwapsCount = useResumeableSwapsCountExcludingPunished();
|
||||||
|
|
||||||
if (resumableSwapsCount > 0) {
|
if (resumableSwapsCount > 0) {
|
||||||
return (
|
return (
|
||||||
|
@ -16,6 +16,7 @@ import { resumeSwap } from "renderer/rpc";
|
|||||||
|
|
||||||
export function SwapResumeButton({
|
export function SwapResumeButton({
|
||||||
swap,
|
swap,
|
||||||
|
children,
|
||||||
...props
|
...props
|
||||||
}: ButtonProps & { swap: GetSwapInfoResponse }) {
|
}: ButtonProps & { swap: GetSwapInfoResponse }) {
|
||||||
return (
|
return (
|
||||||
@ -27,7 +28,7 @@ export function SwapResumeButton({
|
|||||||
onInvoke={() => resumeSwap(swap.swap_id)}
|
onInvoke={() => resumeSwap(swap.swap_id)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
Resume
|
{ children }
|
||||||
</PromiseInvokeButton>
|
</PromiseInvokeButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -75,15 +76,13 @@ export default function HistoryRowActions(swap: GetSwapInfoResponse) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Display a button here to attempt a cooperative redeem
|
|
||||||
// See this PR: https://github.com/UnstoppableSwap/unstoppableswap-gui/pull/212
|
|
||||||
if (swap.state_name === BobStateName.BtcPunished) {
|
if (swap.state_name === BobStateName.BtcPunished) {
|
||||||
return (
|
return (
|
||||||
<Tooltip title="This swap is completed. You have been punished.">
|
<Tooltip title="You have been punished. You can attempt to recover the Monero with the help of the other party but that is not guaranteed to work">
|
||||||
<ErrorIcon style={{ color: red[500] }} />
|
<SwapResumeButton swap={swap} size="small">Attempt recovery</SwapResumeButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SwapResumeButton swap={swap} />;
|
return <SwapResumeButton swap={swap}>Resume</SwapResumeButton>;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { sortBy } from "lodash";
|
import { sortBy } from "lodash";
|
||||||
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
import { BobStateName, GetSwapInfoResponseExt } from "models/tauriModelExt";
|
||||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||||
import type { AppDispatch, RootState } from "renderer/store/storeRenderer";
|
import type { AppDispatch, RootState } from "renderer/store/storeRenderer";
|
||||||
import { parseDateString } from "utils/parseUtils";
|
import { parseDateString } from "utils/parseUtils";
|
||||||
@ -10,15 +10,25 @@ import { TauriSettings } from "models/tauriModel";
|
|||||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||||
|
|
||||||
export function useResumeableSwapsCount() {
|
export function useResumeableSwapsCount(
|
||||||
|
additionalFilter?: (s: GetSwapInfoResponseExt) => boolean,
|
||||||
|
) {
|
||||||
return useAppSelector(
|
return useAppSelector(
|
||||||
(state) =>
|
(state) =>
|
||||||
Object.values(state.rpc.state.swapInfos).filter(
|
Object.values(state.rpc.state.swapInfos).filter(
|
||||||
(swapInfo) => !swapInfo.completed,
|
(swapInfo: GetSwapInfoResponseExt) =>
|
||||||
|
!swapInfo.completed && (additionalFilter == null || additionalFilter(swapInfo))
|
||||||
).length,
|
).length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function useResumeableSwapsCountExcludingPunished() {
|
||||||
|
return useResumeableSwapsCount(
|
||||||
|
(s) => s.state_name !== BobStateName.BtcPunished,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function useIsSwapRunning() {
|
export function useIsSwapRunning() {
|
||||||
return useAppSelector(
|
return useAppSelector(
|
||||||
(state) =>
|
(state) =>
|
||||||
|
@ -30,9 +30,9 @@ export function isBtcAddressValid(address: string, testnet: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getBitcoinTxExplorerUrl(txid: string, testnet: boolean) {
|
export function getBitcoinTxExplorerUrl(txid: string, testnet: boolean) {
|
||||||
return `https://blockchair.com/bitcoin${
|
return `https://mempool.space/${
|
||||||
testnet ? "/testnet" : ""
|
testnet ? "/testnet" : ""
|
||||||
}/transaction/${txid}`;
|
}/tx/${txid}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMoneroTxExplorerUrl(txid: string, stagenet: boolean) {
|
export function getMoneroTxExplorerUrl(txid: string, stagenet: boolean) {
|
||||||
|
@ -3,7 +3,7 @@ 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 = "0.12.0";
|
const MIN_ASB_VERSION = "0.13.3";
|
||||||
|
|
||||||
export function providerToConcatenatedMultiAddr(provider: Provider) {
|
export function providerToConcatenatedMultiAddr(provider: Provider) {
|
||||||
return new Multiaddr(provider.multiAddr)
|
return new Multiaddr(provider.multiAddr)
|
||||||
@ -14,11 +14,16 @@ export function providerToConcatenatedMultiAddr(provider: Provider) {
|
|||||||
export function isProviderCompatible(
|
export function isProviderCompatible(
|
||||||
provider: ExtendedProviderStatus,
|
provider: ExtendedProviderStatus,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (provider.version) {
|
return provider.testnet === isTestnet();
|
||||||
if (!semver.satisfies(provider.version, `>=${MIN_ASB_VERSION}`))
|
}
|
||||||
|
|
||||||
|
export function isProviderOutdated(provider: ExtendedProviderStatus): boolean {
|
||||||
|
if (provider.version != null) {
|
||||||
|
if (semver.satisfies(provider.version, `>=${MIN_ASB_VERSION}`))
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (provider.testnet !== isTestnet()) return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -1,7 +1,11 @@
|
|||||||
import { ExtendedProviderStatus } from "models/apiModel";
|
import { ExtendedProviderStatus } from "models/apiModel";
|
||||||
|
import { isProviderCompatible } from "./multiAddrUtils";
|
||||||
|
|
||||||
export function sortProviderList(list: ExtendedProviderStatus[]) {
|
export function sortProviderList(list: ExtendedProviderStatus[]) {
|
||||||
return list.concat().sort((firstEl, secondEl) => {
|
return list
|
||||||
|
.filter(isProviderCompatible)
|
||||||
|
.concat()
|
||||||
|
.sort((firstEl, secondEl) => {
|
||||||
// If neither of them have a relevancy score, sort by max swap amount
|
// If neither of them have a relevancy score, sort by max swap amount
|
||||||
if (firstEl.relevancy === undefined && secondEl.relevancy === undefined) {
|
if (firstEl.relevancy === undefined && secondEl.relevancy === undefined) {
|
||||||
if (firstEl.maxSwapAmount > secondEl.maxSwapAmount) {
|
if (firstEl.maxSwapAmount > secondEl.maxSwapAmount) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user