diff --git a/src/components/Jam.jsx b/src/components/Jam.jsx index 99df750d6..6859aa7fb 100644 --- a/src/components/Jam.jsx +++ b/src/components/Jam.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react' +import React, { useState, useEffect, useCallback, useMemo, forwardRef } from 'react' import * as rb from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' import { Formik, useFormikContext } from 'formik' @@ -7,11 +7,7 @@ import { useSettings } from '../context/SettingsContext' import { useServiceInfo, useReloadServiceInfo } from '../context/ServiceInfoContext' import { useCurrentWallet, useCurrentWalletInfo, useReloadCurrentWalletInfo } from '../context/WalletContext' import { isDebugFeatureEnabled } from '../constants/debugFeatures' -import { - COINJOIN_PRECONDITIONS, - buildCoinjoinPreconditionSummary, - useCoinjoinPreconditionSummary, -} from '../hooks/CoinjoinPrecondition' +import { DEFAULT_REQUIREMENT_OPTIONS, buildCoinjoinRequirementSummary } from '../hooks/CoinjoinRequirements' import PageTitle from './PageTitle' import ToggleSwitch from './ToggleSwitch' import Sprite from './Sprite' @@ -19,6 +15,8 @@ import Balance from './Balance' import ScheduleProgress from './ScheduleProgress' import styles from './Jam.module.css' +import { jarInitial } from './jars/Jar' +import { shortenStringMiddle } from '../utils' // When running the scheduler with internal destination addresses, the funds // will end up on those 3 mixdepths (one UTXO each). @@ -27,6 +25,8 @@ const INTERNAL_DEST_ACCOUNTS = [0, 1, 2] // Interval in milliseconds between requests to reload the schedule. const SCHEDULER_STOP_RESPONSE_DELAY_MS = 2_000 +const COINJOIN_REQUIREMENT_OPTIONS = DEFAULT_REQUIREMENT_OPTIONS + const ValuesListener = ({ handler }) => { const { values } = useFormikContext() @@ -39,6 +39,90 @@ const ValuesListener = ({ handler }) => { return null } +const CoinjoinRequirementViolationAlert = forwardRef(({ schedulerPreconditionSummary }, ref) => { + const { t } = useTranslation() + const settings = useSettings() + + if (schedulerPreconditionSummary.isFulfilled) return <>> + + if (schedulerPreconditionSummary.numberOfMissingUtxos > 0) { + return ( + + {t('scheduler.precondition.hint_missing_utxos', { + minConfirmations: COINJOIN_REQUIREMENT_OPTIONS.minConfirmations, + })} + + ) + } + + if (schedulerPreconditionSummary.numberOfMissingConfirmations > 0) { + return ( + + {t('scheduler.precondition.hint_missing_confirmations', { + minConfirmations: COINJOIN_REQUIREMENT_OPTIONS.minConfirmations, + amountOfMissingConfirmations: schedulerPreconditionSummary.numberOfMissingConfirmations, + })} + + ) + } + + const utxosViolatingRetriesLeft = schedulerPreconditionSummary.violations + .map((it) => it.utxosViolatingRetriesLeft) + .reduce((acc, utxos) => acc.concat(utxos), []) + + if (utxosViolatingRetriesLeft.length > 0) { + return ( + + <> + + You tried too many times. See + + the docs + {' '} + for more info. + + + + + Following utxos have been used unsuccessfully too many times: + + {utxosViolatingRetriesLeft.map((utxo, index) => ( + + + + + {jarInitial(utxo.mixdepth)} + + : + + + {utxo.address} + ( + + ) + + {shortenStringMiddle(utxo.utxo, 32)} + + + ))} + + + > + + ) + } + + return <>> +}) + export default function Jam() { const { t } = useTranslation() const settings = useSettings() @@ -52,7 +136,10 @@ export default function Jam() { const [isLoading, setIsLoading] = useState(true) const [collaborativeOperationRunning, setCollaborativeOperationRunning] = useState(false) - const schedulerPreconditionSummary = useCoinjoinPreconditionSummary(walletInfo?.data.utxos.utxos || []) + const schedulerPreconditionSummary = useMemo( + () => buildCoinjoinRequirementSummary(walletInfo?.data.utxos.utxos || []), + [walletInfo] + ) const isSchedulerPreconditionsFulfilled = useMemo( () => schedulerPreconditionSummary.isFulfilled, [schedulerPreconditionSummary] @@ -217,39 +304,7 @@ export default function Jam() { mountOnEnter={true} unmountOnExit={true} > - - <> - {schedulerPreconditionSummary.numberOfMissingUtxos > 0 ? ( - <> - {t('scheduler.precondition.hint_missing_utxos', { - minConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS, - amountOfMissingConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS, - })} - > - ) : schedulerPreconditionSummary.amountOfMissingConfirmations > 0 ? ( - <> - {t('scheduler.precondition.hint_missing_confirmations', { - minConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS, - amountOfMissingConfirmations: schedulerPreconditionSummary.amountOfMissingConfirmations, - })} - > - ) : ( - schedulerPreconditionSummary.amountOfMissingOverallRetries > 0 && ( - - You tried too many times. See - - the docs - {' '} - for more info. - - ) - )} - > - + {!collaborativeOperationRunning && walletInfo && ( <> diff --git a/src/components/Send.jsx b/src/components/Send.jsx index 9e8108f47..c90f3d026 100644 --- a/src/components/Send.jsx +++ b/src/components/Send.jsx @@ -222,7 +222,6 @@ function CoinjoinPreconditionFailedAlert({ coinjoinPreconditionSummary }) { <> {t('send.coinjoin_precondition.hint_missing_utxos', { minConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS, - amountOfMissingConfirmations: COINJOIN_PRECONDITIONS.MIN_CONFIRMATIONS, })} > ) : coinjoinPreconditionSummary.amountOfMissingConfirmations > 0 ? ( @@ -234,7 +233,7 @@ function CoinjoinPreconditionFailedAlert({ coinjoinPreconditionSummary }) { > ) : ( coinjoinPreconditionSummary.amountOfMissingOverallRetries > 0 && ( - + You tried too many times. See the docs1> for more information.", + "hint_missing_retries": "You've tried executing a collaborative transaction from this jar unsuccessfully too many times in a row. For security reasons, you need a fresh UTXO to try again. See <1>the docs1> for more information.", "nested_hint_wait_for_block": "Select another jar to send from or wait for one more block.", "nested_hint_wait_for_block_other": "Select another jar to send from or wait for {{ count }} more blocks.", "nested_hint_fund_jar": "Select another jar to send from or fund this jar and wait for one block.", @@ -457,9 +457,11 @@ "progress_current_state": "Waiting for transaction <1>{{ current }}1> of <3>{{ total }}3> to process...", "progress_done": "All transactions completed successfully. The scheduler will stop soon.", "precondition": { - "hint_missing_utxos": "To run the scheduler you need UTXOs with {{ minConfirmations }} or more confirmations. $t(scheduler.precondition.nested_hint_fund_wallet, {\"count\": {{ amountOfMissingConfirmations }} })", + "hint_missing_utxos": "To run the scheduler you need UTXOs with {{ minConfirmations }} or more confirmations. $t(scheduler.precondition.nested_hint_fund_wallet, {\"count\": {{ minConfirmations }} })", "hint_missing_confirmations": "The scheduler requires your UTXOs to have {{ minConfirmations }} or more confirmations. $t(scheduler.precondition.nested_hint_wait_for_block, {\"count\": {{ amountOfMissingConfirmations }} })", - "hint_missing_overall_retries": "You've tried running the scheduler unsuccessfully too many times in a row. For security reasons, you need a fresh UTXO to try again. See <1>the docs1> for more information.", + "hint_missing_retries": "The scheduler failed to send transactions too many times in a row. See <1>the docs1> for more information.", + "hint_missing_retries_detail_one": "The following utxo has been used unsuccessfully too many times and cannot be reused: <1>1>", + "hint_missing_retries_detail_other": "The following {{ count }} utxos have been used unsuccessfully too many times and cannot be reused: <1>1>", "nested_hint_wait_for_block": "Wait for one more block.", "nested_hint_wait_for_block_other": "Wait for {{ count }} more blocks.", "nested_hint_fund_wallet": "Fund your wallet and wait for one more block.", diff --git a/src/utils.test.ts b/src/utils.test.ts new file mode 100644 index 000000000..77650e92f --- /dev/null +++ b/src/utils.test.ts @@ -0,0 +1,19 @@ +import { shortenStringMiddle } from './utils' + +describe('shortenStringMiddle', () => { + it('should shorten string in the middle', () => { + expect(shortenStringMiddle('0')).toBe('0') + expect(shortenStringMiddle('01')).toBe('01') + expect(shortenStringMiddle('01', -1)).toBe('01') + expect(shortenStringMiddle('01', 0)).toBe('01') + expect(shortenStringMiddle('01', 1)).toBe('01') + expect(shortenStringMiddle('01', 2)).toBe('01') + expect(shortenStringMiddle('0123456789abcdef', 2)).toBe('0…f') + expect(shortenStringMiddle('0123456789abcdef', 8)).toBe('0123…cdef') + expect(shortenStringMiddle('0123456789abcdef', 8, '...')).toBe('0123...cdef') + expect(shortenStringMiddle('0123456789abcdef', 14)).toBe('0123456…9abcdef') + expect(shortenStringMiddle('0123456789abcdef', 15)).toBe('0123456789abcdef') + expect(shortenStringMiddle('0123456789abcdef', 16)).toBe('0123456789abcdef') + expect(shortenStringMiddle('0123456789abcdef', Number.MAX_SAFE_INTEGER)).toBe('0123456789abcdef') + }) +}) diff --git a/src/utils.ts b/src/utils.ts index 99a6aa50c..d353e530b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -38,3 +38,11 @@ export const formatBtc = (value: number) => { export const formatSats = (value: number) => { return SATS_FORMATTER.format(value) } + +export const shortenStringMiddle = (value: string, chars = 8, separator = '…') => { + const prefixLength = Math.max(Math.floor(chars / 2), 1) + if (value.length <= prefixLength * 2) { + return `${value}` + } + return `${value.substring(0, prefixLength)}${separator}${value.substring(value.length - prefixLength)}` +}