diff --git a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx index 7194c92fc..91d82cd09 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx @@ -16,7 +16,10 @@ import WarningIcon from "@mui/icons-material/Warning"; import { isMakerVersionOld, isMakerVersionTooOld } from "utils/multiAddrUtils"; import { RefundPolicy } from "store/features/settingsSlice"; import { useAppSelector } from "store/hooks"; -import { BobStateName } from "models/tauriModelExt"; +import { + EMPTY_SWAP_REPUTATION, + selectSwapReputationByPeer, +} from "store/selectors"; const FULL_WARNING_ANTI_SPAM_DEPOSIT_RATIO = 0.1; @@ -240,31 +243,11 @@ function AntiSpamDepositChip(quote: BidQuote) { } function ReputationChip(peer_id: string) { - const allSwaps = useAppSelector((state) => state.rpc.state.swapInfos); - if (!allSwaps) { - return <>; - } - const swapsWithThisPeer = Object.values(allSwaps).filter( - (swap) => swap.seller.peer_id == peer_id, + const { successfulSwaps, refundedSwaps, failedSwaps } = useAppSelector( + (state) => + selectSwapReputationByPeer(state)[peer_id] ?? EMPTY_SWAP_REPUTATION, ); - const successfulSwaps = swapsWithThisPeer.filter( - (swap) => swap.state_name === BobStateName.XmrRedeemed, - ).length; - // TODO: don't hardcode this check (was swap refunded/punished?) here, put into tauriModelExt or other place - const refundedSwaps = swapsWithThisPeer.filter((swap) => - [ - BobStateName.BtcRefunded, - BobStateName.BtcEarlyRefunded, - BobStateName.BtcMercyConfirmed, - ].includes(swap.state_name), - ).length; - const failedSwaps = swapsWithThisPeer.filter((swap) => - [BobStateName.BtcPunished, BobStateName.BtcWithheld].includes( - swap.state_name, - ), - ).length; - return ( ([ + BobStateName.BtcRefunded, + BobStateName.BtcEarlyRefunded, + BobStateName.BtcMercyConfirmed, +]); + +const FAILED_SWAP_STATES = new Set([ + BobStateName.BtcPunished, + BobStateName.BtcWithheld, +]); + +export const selectSwapReputationByPeer = createSelector( + [selectSwapInfosRaw], + (swapInfos) => { + const reputationByPeer: Record = {}; + + if (!swapInfos) return reputationByPeer; + + for (const swap of Object.values(swapInfos)) { + const peerId = swap.seller.peer_id; + const reputation = + reputationByPeer[peerId] ?? + (reputationByPeer[peerId] = { + successfulSwaps: 0, + refundedSwaps: 0, + failedSwaps: 0, + }); + + if (swap.state_name === BobStateName.XmrRedeemed) { + reputation.successfulSwaps += 1; + } else if (REFUNDED_SWAP_STATES.has(swap.state_name)) { + reputation.refundedSwaps += 1; + } else if (FAILED_SWAP_STATES.has(swap.state_name)) { + reputation.failedSwaps += 1; + } + } + + return reputationByPeer; + }, +); + // TODO: This should be split into multiple selectors/hooks to avoid excessive re-rendering export const selectPeers = createSelector([selectP2pState], (p2p) => { const peerIds = new Set([