From 9400b8628e7b7cf07c7d10c390db1de95422402d Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:49:19 +0200 Subject: [PATCH] feat: Allow cooperative Monero redeem after being punished (#210) - Allow cooperative Monero redeem after being punished - Upgrade swap-cli to 0.13.3 - Warn if provider is running outdated version of the software --- .erb/scripts/download-swap-binaries.ts | 4 +- src/main/cli/dirs.ts | 2 +- src/models/cliModel.ts | 77 +++++++++++++++---- src/models/storeModel.ts | 34 ++++++++ .../components/alert/SwapStatusAlert.tsx | 2 +- .../components/alert/UnfinishedSwapsAlert.tsx | 4 +- .../modal/provider/ProviderInfo.tsx | 8 ++ .../modal/swap/pages/SwapStatePage.tsx | 19 ++++- .../swap/pages/done/BitcoinPunishedPage.tsx | 25 +++++- .../pages/done/XmrRedeemInMempoolPage.tsx | 13 ++-- .../swap/pages/exited/ProcessExitedPage.tsx | 4 +- .../navigation/UnfinishedSwapsCountBadge.tsx | 4 +- .../pages/history/table/HistoryRowActions.tsx | 9 ++- src/store/features/providersSlice.ts | 7 +- src/store/features/swapSlice.ts | 37 +++++++++ src/store/hooks.ts | 14 +++- src/utils/multiAddrUtils.ts | 17 ++-- src/utils/sortUtils.ts | 30 ++++---- 18 files changed, 248 insertions(+), 62 deletions(-) diff --git a/.erb/scripts/download-swap-binaries.ts b/.erb/scripts/download-swap-binaries.ts index 3f9697d..571a558 100644 --- a/.erb/scripts/download-swap-binaries.ts +++ b/.erb/scripts/download-swap-binaries.ts @@ -16,9 +16,9 @@ async function makeFileExecutable(binary: Binary) { ); } -const CLI_VERSION = '0.13.2'; +const CLI_VERSION = '0.13.3'; // Ensure the value here matches with the one in src/main/cli/dirs.ts -const CLI_FILE_NAME_VERSION_PREFIX = '0_13_2_'; +const CLI_FILE_NAME_VERSION_PREFIX = '0_13_3_'; const binaries = [ { diff --git a/src/main/cli/dirs.ts b/src/main/cli/dirs.ts index 5978218..238dcdb 100644 --- a/src/main/cli/dirs.ts +++ b/src/main/cli/dirs.ts @@ -14,7 +14,7 @@ import { RpcProcessStateType } from 'models/rpcModel'; // Ensure the CLI version is updated in both places when updating the CLI version. // We need a different name for each CLI version to make sure, that the CLI is definitely updated // electron-builder sometimes doesn't update the CLI binary -const CLI_FILE_NAME_VERSION_PREFIX = '0_13_2_'; +const CLI_FILE_NAME_VERSION_PREFIX = '0_13_3_'; // Be consistent with the way the cli generates the // data-dir on linux diff --git a/src/models/cliModel.ts b/src/models/cliModel.ts index 47e87a6..ede1239 100644 --- a/src/models/cliModel.ts +++ b/src/models/cliModel.ts @@ -352,31 +352,80 @@ export function isCliLogDownloadingMoneroWalletRpc( return log.fields.message === 'Downloading monero-wallet-rpc'; } -export interface CliLogStartedSyncingMoneroWallet extends CliLog { +export interface CliLogGotNotificationForNewBlock extends CliLog { fields: { - message: 'Syncing Monero wallet'; - current_sync_height?: boolean; + message: 'Got notification for new block'; + block_height: string; }; } -export interface CliLogDownloadingMoneroWalletRpc extends CliLog { +export function isCliLogGotNotificationForNewBlock( + log: CliLog, +): log is CliLogGotNotificationForNewBlock { + return log.fields.message === 'Got notification for new block'; +} + +export interface CliLogAttemptingToCooperativelyRedeemXmr extends CliLog { fields: { - message: 'Downloading monero-wallet-rpc'; - progress: string; - size: string; - download_url: string; + message: 'Attempting to cooperatively redeem XMR after being punished'; }; } -export interface CliLogGotNotificationForNewBlock extends CliLog { +export function isCliLogAttemptingToCooperativelyRedeemXmr( + log: CliLog, +): log is CliLogAttemptingToCooperativelyRedeemXmr { + return ( + log.fields.message === + 'Attempting to cooperatively redeem XMR after being punished' + ); +} + +export interface CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr + extends CliLog { fields: { - message: 'Got notification for new block'; - block_height: string; + message: 'Alice has accepted our request to cooperatively redeem the XMR'; }; } -export function isCliLogGotNotificationForNewBlock( +export function isCliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr( log: CliLog, -): log is CliLogGotNotificationForNewBlock { - return log.fields.message === 'Got notification for new block'; +): log is CliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr { + return ( + log.fields.message === + 'Alice has accepted our request to cooperatively redeem the XMR' + ); +} + +export interface CliLogAliceRejectedOurRequestForCooperativeXmrRedeem + extends CliLog { + fields: { + message: 'Alice rejected our request for cooperative XMR redeem'; + reason: string; + }; +} + +export function isCliLogAliceRejectedOurRequestForCooperativeXmrRedeem( + log: CliLog, +): log is CliLogAliceRejectedOurRequestForCooperativeXmrRedeem { + return ( + log.fields.message === + 'Alice rejected our request for cooperative XMR redeem' + ); +} + +// tracing::error!(?error, "Failed to request cooperative XMR redeem from Alice"); +export interface CliLogFailedToRequestCooperativeXmrRedeemFromAlice + extends CliLog { + fields: { + message: 'Failed to request cooperative XMR redeem from Alice'; + error: string; + }; +} + +export function isCliLogFailedToRequestCooperativeXmrRedeemFromAlice( + log: CliLog, +): log is CliLogFailedToRequestCooperativeXmrRedeemFromAlice { + return ( + log.fields.message === 'Failed to request cooperative XMR redeem from Alice' + ); } diff --git a/src/models/storeModel.ts b/src/models/storeModel.ts index f81860b..0ccda1c 100644 --- a/src/models/storeModel.ts +++ b/src/models/storeModel.ts @@ -33,6 +33,9 @@ export enum SwapStateType { BTC_CANCELLED = 'btc cancelled', BTC_REFUNDED = 'btc refunded', BTC_PUNISHED = 'btc punished', + ATTEMPTING_COOPERATIVE_REDEEM = 'attempting cooperative redeem', + COOPERATIVE_REDEEM_REJECTED = 'cooperative redeem rejected', + COOPERATIVE_REDEEM_ACCEPTED = 'cooperative redeem accepted', } export function isSwapState(state?: SwapState | null): state is SwapState { @@ -138,6 +141,37 @@ export function isSwapStateBtcRedemeed( return state?.type === SwapStateType.BTC_REDEEMED; } +export interface SwapStateAttemptingCooperativeRedeeem extends SwapState { + type: SwapStateType.ATTEMPTING_COOPERATIVE_REDEEM; +} + +export function isSwapStateAttemptingCooperativeRedeeem( + state?: SwapState | null, +): state is SwapStateAttemptingCooperativeRedeeem { + return state?.type === SwapStateType.ATTEMPTING_COOPERATIVE_REDEEM; +} + +export interface SwapStateCooperativeRedeemAccepted extends SwapState { + type: SwapStateType.COOPERATIVE_REDEEM_ACCEPTED; +} + +export function isSwapStateCooperativeRedeemAccepted( + state?: SwapState | null, +): state is SwapStateCooperativeRedeemAccepted { + return state?.type === SwapStateType.COOPERATIVE_REDEEM_ACCEPTED; +} + +export interface SwapStateCooperativeRedeemRejected extends SwapState { + type: SwapStateType.COOPERATIVE_REDEEM_REJECTED; + reason: string; +} + +export function isSwapStateCooperativeRedeemRejected( + state?: SwapState | null, +): state is SwapStateCooperativeRedeemRejected { + return state?.type === SwapStateType.COOPERATIVE_REDEEM_REJECTED; +} + export interface SwapStateXmrRedeemInMempool extends SwapState { type: SwapStateType.XMR_REDEEM_IN_MEMPOOL; bobXmrRedeemTxId: string; diff --git a/src/renderer/components/alert/SwapStatusAlert.tsx b/src/renderer/components/alert/SwapStatusAlert.tsx index 91fcb06..01ef28f 100644 --- a/src/renderer/components/alert/SwapStatusAlert.tsx +++ b/src/renderer/components/alert/SwapStatusAlert.tsx @@ -221,7 +221,7 @@ export default function SwapStatusAlert({ } + action={Resume} variant="filled" > diff --git a/src/renderer/components/alert/UnfinishedSwapsAlert.tsx b/src/renderer/components/alert/UnfinishedSwapsAlert.tsx index bdeda60..5a298a6 100644 --- a/src/renderer/components/alert/UnfinishedSwapsAlert.tsx +++ b/src/renderer/components/alert/UnfinishedSwapsAlert.tsx @@ -1,10 +1,10 @@ import { Button } from '@material-ui/core'; import Alert from '@material-ui/lab/Alert'; import { useNavigate } from 'react-router-dom'; -import { useResumeableSwapsCount } from 'store/hooks'; +import { useResumeableSwapsCountExcludingPunished } from 'store/hooks'; export default function UnfinishedSwapsAlert() { - const resumableSwapsCount = useResumeableSwapsCount(); + const resumableSwapsCount = useResumeableSwapsCountExcludingPunished(); const navigate = useNavigate(); if (resumableSwapsCount > 0) { diff --git a/src/renderer/components/modal/provider/ProviderInfo.tsx b/src/renderer/components/modal/provider/ProviderInfo.tsx index 592bf1d..9706da5 100644 --- a/src/renderer/components/modal/provider/ProviderInfo.tsx +++ b/src/renderer/components/modal/provider/ProviderInfo.tsx @@ -6,6 +6,8 @@ import { MoneroBitcoinExchangeRate, SatsAmount, } from 'renderer/components/other/Units'; +import { isProviderOutdated } from 'utils/multiAddrUtils'; +import WarningIcon from '@material-ui/icons/Warning'; const useStyles = makeStyles((theme) => ({ content: { @@ -28,6 +30,7 @@ export default function ProviderInfo({ provider: ExtendedProviderStatus; }) { const classes = useStyles(); + const isOutdated = isProviderOutdated(provider); return ( @@ -69,6 +72,11 @@ export default function ProviderInfo({ } color="primary" /> )} + {isOutdated && ( + + } color="primary" /> + + )} ); diff --git a/src/renderer/components/modal/swap/pages/SwapStatePage.tsx b/src/renderer/components/modal/swap/pages/SwapStatePage.tsx index de0429a..b049d2f 100644 --- a/src/renderer/components/modal/swap/pages/SwapStatePage.tsx +++ b/src/renderer/components/modal/swap/pages/SwapStatePage.tsx @@ -1,11 +1,14 @@ import { Box } from '@material-ui/core'; import { useAppSelector } from 'store/hooks'; import { + isSwapStateAttemptingCooperativeRedeeem, isSwapStateBtcCancelled, isSwapStateBtcLockInMempool, isSwapStateBtcPunished, isSwapStateBtcRedemeed, isSwapStateBtcRefunded, + isSwapStateCooperativeRedeemAccepted, + isSwapStateCooperativeRedeemRejected, isSwapStateInitiated, isSwapStateProcessExited, isSwapStateReceivedQuote, @@ -32,6 +35,7 @@ import BitcoinCancelledPage from './in_progress/BitcoinCancelledPage'; import BitcoinRefundedPage from './done/BitcoinRefundedPage'; import BitcoinPunishedPage from './done/BitcoinPunishedPage'; import { SyncingMoneroWalletPage } from './in_progress/SyncingMoneroWalletPage'; +import CircularProgressWithSubtitle from '../CircularProgressWithSubtitle'; export default function SwapStatePage({ swapState, @@ -83,7 +87,20 @@ export default function SwapStatePage({ return ; } if (isSwapStateBtcPunished(swapState)) { - return ; + return ; + } + if (isSwapStateAttemptingCooperativeRedeeem(swapState)) { + return ( + + ); + } + if (isSwapStateCooperativeRedeemAccepted(swapState)) { + return ( + + ); + } + if (isSwapStateCooperativeRedeemRejected(swapState)) { + return ; } if (isSwapStateProcessExited(swapState)) { return ; diff --git a/src/renderer/components/modal/swap/pages/done/BitcoinPunishedPage.tsx b/src/renderer/components/modal/swap/pages/done/BitcoinPunishedPage.tsx index e98f9fc..f54d873 100644 --- a/src/renderer/components/modal/swap/pages/done/BitcoinPunishedPage.tsx +++ b/src/renderer/components/modal/swap/pages/done/BitcoinPunishedPage.tsx @@ -1,13 +1,30 @@ import { Box, DialogContentText } from '@material-ui/core'; import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox'; +import { + SwapStateCooperativeRedeemRejected, + isSwapStateCooperativeRedeemRejected, +} from 'models/storeModel'; -export default function BitcoinPunishedPage() { +export default function BitcoinPunishedPage({ + state, +}: { + state: null | SwapStateCooperativeRedeemRejected; +}) { return ( - Unfortunately, the swap was not successful, and you've incurred a - penalty because the swap was not refunded in time. Both the Bitcoin and - Monero are irretrievable. + Unfortunately, the swap was unsuccessful. Since you did not refund in + time, the Bitcoin has been lost. However, with the cooperation of the + other party, you might still be able to redeem the Monero, although this + is not guaranteed.{' '} + {isSwapStateCooperativeRedeemRejected(state) && ( + <> +
+ We tried to redeem the Monero with the other party's help, but it + was unsuccessful (reason: {state.reason}). Attempting again at a + later time might yield success.
+ + )}
diff --git a/src/renderer/components/modal/swap/pages/done/XmrRedeemInMempoolPage.tsx b/src/renderer/components/modal/swap/pages/done/XmrRedeemInMempoolPage.tsx index 18f4959..8c839a1 100644 --- a/src/renderer/components/modal/swap/pages/done/XmrRedeemInMempoolPage.tsx +++ b/src/renderer/components/modal/swap/pages/done/XmrRedeemInMempoolPage.tsx @@ -4,6 +4,7 @@ import { useActiveSwapInfo } from 'store/hooks'; import { getSwapXmrAmount } from 'models/rpcModel'; import MoneroTransactionInfoBox from '../../MoneroTransactionInfoBox'; import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox'; +import { MoneroAmount } from 'renderer/components/other/Units'; type XmrRedeemInMempoolPageProps = { state: SwapStateXmrRedeemInMempool | null; @@ -13,11 +14,13 @@ export default function XmrRedeemInMempoolPage({ state, }: XmrRedeemInMempoolPageProps) { const swap = useActiveSwapInfo(); - const additionalContent = swap - ? `This transaction transfers ${getSwapXmrAmount(swap).toFixed(6)} XMR to ${ - state?.bobXmrRedeemAddress - }` - : null; + const additionalContent = swap ? ( + <> + This transaction transfers{' '} + to the{' '} + {state?.bobXmrRedeemAddress} + + ) : null; return ( diff --git a/src/renderer/components/modal/swap/pages/exited/ProcessExitedPage.tsx b/src/renderer/components/modal/swap/pages/exited/ProcessExitedPage.tsx index 2e78ff8..fd5acec 100644 --- a/src/renderer/components/modal/swap/pages/exited/ProcessExitedPage.tsx +++ b/src/renderer/components/modal/swap/pages/exited/ProcessExitedPage.tsx @@ -3,6 +3,7 @@ import { SwapStateName } from 'models/rpcModel'; import { isSwapStateBtcPunished, isSwapStateBtcRefunded, + isSwapStateCooperativeRedeemRejected, isSwapStateXmrRedeemInMempool, SwapStateProcessExited, } from '../../../../../../models/storeModel'; @@ -24,7 +25,8 @@ export default function ProcessExitedPage({ state }: ProcessExitedPageProps) { if ( isSwapStateXmrRedeemInMempool(state.prevState) || isSwapStateBtcRefunded(state.prevState) || - isSwapStateBtcPunished(state.prevState) + isSwapStateBtcPunished(state.prevState) || + isSwapStateCooperativeRedeemRejected(state.prevState) ) { return ; } diff --git a/src/renderer/components/navigation/UnfinishedSwapsCountBadge.tsx b/src/renderer/components/navigation/UnfinishedSwapsCountBadge.tsx index 1304b77..d32dce5 100644 --- a/src/renderer/components/navigation/UnfinishedSwapsCountBadge.tsx +++ b/src/renderer/components/navigation/UnfinishedSwapsCountBadge.tsx @@ -1,12 +1,12 @@ import { Badge } from '@material-ui/core'; -import { useResumeableSwapsCount } from 'store/hooks'; +import { useResumeableSwapsCountExcludingPunished } from 'store/hooks'; export default function UnfinishedSwapsBadge({ children, }: { children: JSX.Element; }) { - const resumableSwapsCount = useResumeableSwapsCount(); + const resumableSwapsCount = useResumeableSwapsCountExcludingPunished(); if (resumableSwapsCount > 0) { return ( diff --git a/src/renderer/components/pages/history/table/HistoryRowActions.tsx b/src/renderer/components/pages/history/table/HistoryRowActions.tsx index 0caf854..49b2f22 100644 --- a/src/renderer/components/pages/history/table/HistoryRowActions.tsx +++ b/src/renderer/components/pages/history/table/HistoryRowActions.tsx @@ -14,6 +14,7 @@ import { export function SwapResumeButton({ swap, + children, ...props }: { swap: GetSwapInfoResponse } & ButtonProps) { return ( @@ -27,7 +28,7 @@ export function SwapResumeButton({ requiresRpc {...props} > - Resume + {children} ); } @@ -80,11 +81,11 @@ export default function HistoryRowActions({ if (swap.stateName === SwapStateName.BtcPunished) { return ( - - + + Attempt recovery ); } - return ; + return Resume; } diff --git a/src/store/features/providersSlice.ts b/src/store/features/providersSlice.ts index d4af559..a3833fa 100644 --- a/src/store/features/providersSlice.ts +++ b/src/store/features/providersSlice.ts @@ -1,7 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { ExtendedProviderStatus, ProviderStatus } from 'models/apiModel'; import { sortProviderList } from 'utils/sortUtils'; -import { isProviderCompatible } from 'utils/multiAddrUtils'; +import { isProviderOutdated } from 'utils/multiAddrUtils'; import { getStubTestnetProvider } from 'store/config'; const stubTestnetProvider = getStubTestnetProvider(); @@ -81,13 +81,12 @@ export const providersSlice = createSlice({ slice, action: PayloadAction, ) { + // Add stub testnet provider if it exists if (stubTestnetProvider) { action.payload.push(stubTestnetProvider); } - slice.registry.providers = sortProviderList(action.payload).filter( - isProviderCompatible, - ); + slice.registry.providers = sortProviderList(action.payload); slice.selectedProvider = selectNewSelectedProvider(slice); }, increaseFailedRegistryReconnectAttemptsSinceLastSuccess(slice) { diff --git a/src/store/features/swapSlice.ts b/src/store/features/swapSlice.ts index 622ad17..22ee940 100644 --- a/src/store/features/swapSlice.ts +++ b/src/store/features/swapSlice.ts @@ -8,11 +8,14 @@ import { isSwapStateWaitingForBtcDeposit, isSwapStateXmrLockInMempool, SwapSlice, + SwapStateAttemptingCooperativeRedeeem, SwapStateBtcCancelled, SwapStateBtcLockInMempool, SwapStateBtcPunished, SwapStateBtcRedemeed, SwapStateBtcRefunded, + SwapStateCooperativeRedeemAccepted, + SwapStateCooperativeRedeemRejected, SwapStateInitiated, SwapStateProcessExited, SwapStateReceivedQuote, @@ -42,6 +45,10 @@ import { isCliLogAcquiringSwapLockLog, isCliLogApiCallError, isCliLogDeterminedSwapAmount, + isCliLogAttemptingToCooperativelyRedeemXmr, + isCliLogAliceRejectedOurRequestForCooperativeXmrRedeem, + isCliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr, + isCliLogFailedToRequestCooperativeXmrRedeemFromAlice, } from '../../models/cliModel'; import logger from '../../utils/logger'; @@ -249,6 +256,36 @@ export const swapSlice = createSlice({ type: SwapStateType.BTC_PUNISHED, }; + slice.state = nextState; + } else if (isCliLogAttemptingToCooperativelyRedeemXmr(log)) { + const nextState: SwapStateAttemptingCooperativeRedeeem = { + type: SwapStateType.ATTEMPTING_COOPERATIVE_REDEEM, + }; + + slice.state = nextState; + } else if ( + isCliLogAliceRejectedOurRequestForCooperativeXmrRedeem(log) + ) { + const nextState: SwapStateCooperativeRedeemRejected = { + type: SwapStateType.COOPERATIVE_REDEEM_REJECTED, + reason: log.fields.reason, + }; + + slice.state = nextState; + } else if ( + isCliLogAliceHasAcceptedOurRequestToCooperativelyRedeemTheXmr(log) + ) { + const nextState: SwapStateCooperativeRedeemAccepted = { + type: SwapStateType.COOPERATIVE_REDEEM_ACCEPTED, + }; + + slice.state = nextState; + } else if (isCliLogFailedToRequestCooperativeXmrRedeemFromAlice(log)) { + const nextState: SwapStateCooperativeRedeemRejected = { + type: SwapStateType.COOPERATIVE_REDEEM_REJECTED, + reason: 'Failed to connect to Alice: ' + log.fields.error, + }; + slice.state = nextState; } else if ( isCliLogReleasingSwapLockLog(log) && diff --git a/src/store/hooks.ts b/src/store/hooks.ts index df19f6e..b417a7e 100644 --- a/src/store/hooks.ts +++ b/src/store/hooks.ts @@ -2,20 +2,30 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; import type { AppDispatch, RootState } from 'renderer/store/storeRenderer'; import { sortBy } from 'lodash'; import { parseDateString } from 'utils/parseUtils'; +import { GetSwapInfoResponse, SwapStateName } from 'models/rpcModel'; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = () => useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector; -export function useResumeableSwapsCount() { +export function useResumeableSwapsCount( + filter?: (s: GetSwapInfoResponse) => boolean, +) { return useAppSelector( (state) => Object.values(state.rpc.state.swapInfos).filter( - (swapInfo) => !swapInfo.completed, + (swapInfo) => + !swapInfo.completed && (filter == null || filter(swapInfo)), ).length, ); } +export function useResumeableSwapsCountExcludingPunished() { + return useResumeableSwapsCount( + (s) => s.stateName !== SwapStateName.BtcPunished, + ); +} + export function useIsSwapRunning() { return useAppSelector((state) => state.swap.state !== null); } diff --git a/src/utils/multiAddrUtils.ts b/src/utils/multiAddrUtils.ts index 71a3669..8536c76 100644 --- a/src/utils/multiAddrUtils.ts +++ b/src/utils/multiAddrUtils.ts @@ -3,7 +3,7 @@ import semver from 'semver'; import { ExtendedProviderStatus, Provider } from 'models/apiModel'; import { isTestnet } from 'store/config'; -const MIN_ASB_VERSION = '0.12.0'; +const MIN_ASB_VERSION = '0.13.3'; export function providerToConcatenatedMultiAddr(provider: Provider) { return new Multiaddr(provider.multiAddr) @@ -11,14 +11,19 @@ export function providerToConcatenatedMultiAddr(provider: Provider) { .toString(); } -export function isProviderCompatible( - provider: ExtendedProviderStatus, -): boolean { +export function isProviderOutdated(provider: ExtendedProviderStatus): boolean { if (provider.version) { - if (!semver.satisfies(provider.version, `>=${MIN_ASB_VERSION}`)) + if (semver.satisfies(provider.version, `>=${MIN_ASB_VERSION}`)) return false; + } else { + return false; } - if (provider.testnet !== isTestnet()) return false; return true; } + +export function isProviderCompatible( + provider: ExtendedProviderStatus, +): boolean { + return provider.testnet === isTestnet(); +} diff --git a/src/utils/sortUtils.ts b/src/utils/sortUtils.ts index 0b6185c..f2059c1 100644 --- a/src/utils/sortUtils.ts +++ b/src/utils/sortUtils.ts @@ -1,19 +1,23 @@ import { ExtendedProviderStatus } from 'models/apiModel'; +import { isProviderCompatible } from './multiAddrUtils'; export function sortProviderList(list: ExtendedProviderStatus[]) { - return list.concat().sort((firstEl, secondEl) => { - // If neither of them have a relevancy score, sort by max swap amount - if (firstEl.relevancy === undefined && secondEl.relevancy === undefined) { - if (firstEl.maxSwapAmount > secondEl.maxSwapAmount) { + return list + .filter(isProviderCompatible) + .concat() + .sort((firstEl, secondEl) => { + // If neither of them have a relevancy score, sort by max swap amount + if (firstEl.relevancy === undefined && secondEl.relevancy === undefined) { + if (firstEl.maxSwapAmount > secondEl.maxSwapAmount) { + return -1; + } + } + // If only on of the two don't have a relevancy score, prioritize the one that does + if (firstEl.relevancy === undefined) return 1; + if (secondEl.relevancy === undefined) return -1; + if (firstEl.relevancy > secondEl.relevancy) { return -1; } - } - // If only on of the two don't have a relevancy score, prioritize the one that does - if (firstEl.relevancy === undefined) return 1; - if (secondEl.relevancy === undefined) return -1; - if (firstEl.relevancy > secondEl.relevancy) { - return -1; - } - return 1; - }); + return 1; + }); }