From 80e40ff51086e00e73de0fcf5cc977e8a1720cfc Mon Sep 17 00:00:00 2001 From: Thebora Kompanioni Date: Fri, 16 Sep 2022 10:15:08 +0200 Subject: [PATCH] feat: abort collaborative transaction (#497) * feat(taker): ability to abort single collaborative transaction * feat(taker): show confirm modal before aborting collaborative transaction * fix: disable abort button if abort confirm modal is already shown * fix: let normal init process reload wallet and service info * doc(send): fix typo in comment Co-authored-by: Gigi <109058+dergigi@users.noreply.github.com> --- src/components/Jam.jsx | 2 +- src/components/Send.jsx | 82 ++++++++++++++++++++++------ src/i18n/locales/en/translation.json | 7 ++- src/libs/JmWalletApi.ts | 4 +- 4 files changed, 74 insertions(+), 21 deletions(-) diff --git a/src/components/Jam.jsx b/src/components/Jam.jsx index af344e85..bd447fe8 100644 --- a/src/components/Jam.jsx +++ b/src/components/Jam.jsx @@ -174,7 +174,7 @@ export default function Jam() { setIsLoading(true) const abortCtrl = new AbortController() - return Api.getSchedulerStop({ signal: abortCtrl.signal, walletName: wallet.name, token: wallet.token }) + return Api.getTakerStop({ signal: abortCtrl.signal, walletName: wallet.name, token: wallet.token }) .then((res) => (res.ok ? true : Api.Helper.throwError(res, t('scheduler.error_stopping_schedule_failed')))) .then((_) => setCollaborativeOperationRunning(false)) .then((_) => diff --git a/src/components/Send.jsx b/src/components/Send.jsx index fc26e6ac..533f84e7 100644 --- a/src/components/Send.jsx +++ b/src/components/Send.jsx @@ -252,7 +252,8 @@ export default function Send() { [isInitializing, waitForUtxosToBeSpent] ) - const [showConfirmInputsModal, setShowConfirmInputsModal] = useState(false) + const [showConfirmAbortModal, setShowConfirmAbortModal] = useState(false) + const [showConfirmSendModal, setShowConfirmSendModal] = useState(false) const submitButtonRef = useRef(null) useEffect(() => { @@ -498,6 +499,34 @@ export default function Send() { return success } + useEffect(() => { + // hide the abort modal, if a user wants to abort a running transaction, + // but the transaction failed or succeeded in the meantime + if (showConfirmAbortModal && !isCoinjoinInProgress) { + setShowConfirmAbortModal(false) + } + }, [isCoinjoinInProgress, showConfirmAbortModal]) + + const abortCoinjoin = async () => { + if (!isCoinjoinInProgress) { + setShowConfirmAbortModal(false) + return + } + + if (!showConfirmAbortModal) { + setShowConfirmAbortModal(true) + return + } + + setShowConfirmAbortModal(false) + setAlert(null) + + const abortCtrl = new AbortController() + return Api.getTakerStop({ signal: abortCtrl.signal, walletName: wallet.name, token: wallet.token }).catch((err) => { + setAlert({ variant: 'danger', message: err.message }) + }) + } + const onSubmit = async (e) => { e.preventDefault() @@ -509,12 +538,12 @@ export default function Send() { const isValid = formIsValid if (isValid) { - if (!showConfirmInputsModal) { - setShowConfirmInputsModal(true) + if (!showConfirmSendModal) { + setShowConfirmSendModal(true) return } - setShowConfirmInputsModal(false) + setShowConfirmSendModal(false) const counterparties = parseInt(numCollaborators, 10) @@ -652,8 +681,17 @@ export default function Send() { )} {isCoinjoinInProgress && ( - + {t('send.text_coinjoin_already_running')} + + abortCoinjoin()} + > + {t('global.abort')} + )} @@ -893,10 +931,20 @@ export default function Send() { t('send.button_send_without_improved_privacy') )} + setShowConfirmInputsModal(false)} + isShown={showConfirmAbortModal} + title={t('send.confirm_abort_modal.title')} + onCancel={() => setShowConfirmAbortModal(false)} + onConfirm={() => abortCoinjoin()} + > + {t('send.confirm_abort_modal.text_body')} + + + setShowConfirmSendModal(false)} onConfirm={() => { submitButtonRef.current?.click() }} @@ -905,23 +953,23 @@ export default function Send() { {isCoinjoin ? ( - {t('send.confirm_modal.text_collaborative_tx_enabled')} + {t('send.confirm_send_modal.text_collaborative_tx_enabled')} ) : ( - {t('send.confirm_modal.text_collaborative_tx_disabled')} + {t('send.confirm_send_modal.text_collaborative_tx_disabled')} )} - {t('send.confirm_modal.label_source_jar')} + {t('send.confirm_send_modal.label_source_jar')} - {t('send.confirm_modal.text_source_jar', { jarId: jarInitial(account) })} + {t('send.confirm_send_modal.text_source_jar', { jarId: jarInitial(account) })} - {t('send.confirm_modal.label_recipient')} + {t('send.confirm_send_modal.label_recipient')} {destination} @@ -929,12 +977,12 @@ export default function Send() { - {t('send.confirm_modal.label_amount')} + {t('send.confirm_send_modal.label_amount')} {isSweep ? (
- + Sweep - {t('send.confirm_modal.text_sweep_info_popover')} + {t('send.confirm_send_modal.text_sweep_info_popover')} } > @@ -967,7 +1015,7 @@ export default function Send() { {isCoinjoin && ( - {t('send.confirm_modal.label_num_collaborators')} + {t('send.confirm_send_modal.label_num_collaborators')} {numCollaborators} diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index d8aa496b..5a8b899c 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -6,6 +6,7 @@ "next": "Next", "back": "Back", "close": "Close", + "abort": "Abort", "table": { "pagination": { "items_per_page": { @@ -208,7 +209,7 @@ "sweep_amount_breakdown_frozen_balance": "Frozen or locked balance", "sweep_amount_breakdown_estimated_amount": "Estimated amount to be sent", "sweep_amount_breakdown_explanation": "A sweep transaction will consume all UTXOs of a mixdepth leaving no coins behind except those that have been <1>frozen or <3>time-locked. Onchain transaction fees and market maker fees will be deducted from the amount so as to leave zero change. The exact transaction amount can only be calculated by JoinMarket at the point when the transaction is made. Therefore the estimated amount shown might deviate from the actually sent amount. Refer to the <5>JoinMarket documentation for more details.", - "confirm_modal": { + "confirm_send_modal": { "title": "Confirm payment", "label_amount": "Amount", "text_sweep_balance": "Sweep whole jar (<1>)", @@ -220,6 +221,10 @@ "text_collaborative_tx_enabled": "Payment with privacy improvement", "text_collaborative_tx_disabled": "Payment without privacy improvement" }, + "confirm_abort_modal": { + "title": "Abort payment", + "text_body": "Are you sure you want to abort the collaborative transaction?" + }, "coinjoin_precondition": { "hint_missing_utxos": "To execute a collaborative transaction you need UTXOs with {{ minConfirmations }} or more confirmations in the source jar. $t(send.coinjoin_precondition.nested_hint_fund_jar, {\"count\": {{ minConfirmations }} })", "hint_missing_confirmations": "A collaborative transaction requires your UTXOs to have {{ minConfirmations }} or more confirmations. $t(send.coinjoin_precondition.nested_hint_wait_for_block, {\"count\": {{ amountOfMissingConfirmations }} })", diff --git a/src/libs/JmWalletApi.ts b/src/libs/JmWalletApi.ts index b6bcf6e5..51ec8ffe 100644 --- a/src/libs/JmWalletApi.ts +++ b/src/libs/JmWalletApi.ts @@ -358,7 +358,7 @@ const postSchedulerStart = async ({ token, signal, walletName }: WalletRequestCo }) } -const getSchedulerStop = async ({ token, signal, walletName }: WalletRequestContext) => { +const getTakerStop = async ({ token, signal, walletName }: WalletRequestContext) => { return await fetch(`${basePath()}/v1/wallet/${encodeURIComponent(walletName)}/taker/stop`, { headers: { ...Helper.buildAuthHeader(token) }, signal, @@ -414,7 +414,7 @@ export { postConfigGet, getWalletSeed, postSchedulerStart, - getSchedulerStop, + getTakerStop, getSchedule, Helper, }