Skip to content

Commit

Permalink
feat: abort collaborative transaction (#497)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
theborakompanioni and dergigi authored Sep 16, 2022
1 parent db29235 commit 80e40ff
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/components/Jam.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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((_) =>
Expand Down
82 changes: 65 additions & 17 deletions src/components/Send.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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()

Expand All @@ -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)

Expand Down Expand Up @@ -652,8 +681,17 @@ export default function Send() {
</Link>
)}
{isCoinjoinInProgress && (
<rb.Alert variant="info" className="mb-4">
<rb.Alert variant="info" className="mb-4 d-flex align-items-center">
{t('send.text_coinjoin_already_running')}

<rb.Button
variant={'outline-light'}
className="ms-auto"
disabled={showConfirmAbortModal}
onClick={() => abortCoinjoin()}
>
{t('global.abort')}
</rb.Button>
</rb.Alert>
)}
</>
Expand Down Expand Up @@ -893,10 +931,20 @@ export default function Send() {
t('send.button_send_without_improved_privacy')
)}
</rb.Button>

<ConfirmModal
isShown={showConfirmAbortModal}
title={t('send.confirm_abort_modal.title')}
onCancel={() => setShowConfirmAbortModal(false)}
onConfirm={() => abortCoinjoin()}
>
{t('send.confirm_abort_modal.text_body')}
</ConfirmModal>

<ConfirmModal
isShown={showConfirmInputsModal}
title={t('send.confirm_modal.title')}
onCancel={() => setShowConfirmInputsModal(false)}
isShown={showConfirmSendModal}
title={t('send.confirm_send_modal.title')}
onCancel={() => setShowConfirmSendModal(false)}
onConfirm={() => {
submitButtonRef.current?.click()
}}
Expand All @@ -905,36 +953,36 @@ export default function Send() {
<rb.Row className="mt-2 mb-3">
<rb.Col xs={12} className="text-center">
{isCoinjoin ? (
<strong className="text-success">{t('send.confirm_modal.text_collaborative_tx_enabled')}</strong>
<strong className="text-success">{t('send.confirm_send_modal.text_collaborative_tx_enabled')}</strong>
) : (
<strong className="text-danger">{t('send.confirm_modal.text_collaborative_tx_disabled')}</strong>
<strong className="text-danger">{t('send.confirm_send_modal.text_collaborative_tx_disabled')}</strong>
)}
</rb.Col>
</rb.Row>
<rb.Row>
<rb.Col xs={3} className="text-end">
<strong>{t('send.confirm_modal.label_source_jar')}</strong>
<strong>{t('send.confirm_send_modal.label_source_jar')}</strong>
</rb.Col>
<rb.Col xs={9} className="text-start">
{t('send.confirm_modal.text_source_jar', { jarId: jarInitial(account) })}
{t('send.confirm_send_modal.text_source_jar', { jarId: jarInitial(account) })}
</rb.Col>
</rb.Row>
<rb.Row>
<rb.Col xs={3} className="text-end">
<strong>{t('send.confirm_modal.label_recipient')}</strong>
<strong>{t('send.confirm_send_modal.label_recipient')}</strong>
</rb.Col>
<rb.Col xs={9} className="text-start text-break slashed-zeroes">
{destination}
</rb.Col>
</rb.Row>
<rb.Row>
<rb.Col xs={3} className="text-end">
<strong>{t('send.confirm_modal.label_amount')}</strong>
<strong>{t('send.confirm_send_modal.label_amount')}</strong>
</rb.Col>
<rb.Col xs={9} className="text-start">
{isSweep ? (
<div className="d-flex justify-content-start align-items-center">
<Trans i18nKey="send.confirm_modal.text_sweep_balance">
<Trans i18nKey="send.confirm_send_modal.text_sweep_balance">
Sweep
<Balance
valueString={amountFieldValue().toString()}
Expand All @@ -946,7 +994,7 @@ export default function Send() {
placement="right"
overlay={
<rb.Popover>
<rb.Popover.Body>{t('send.confirm_modal.text_sweep_info_popover')}</rb.Popover.Body>
<rb.Popover.Body>{t('send.confirm_send_modal.text_sweep_info_popover')}</rb.Popover.Body>
</rb.Popover>
}
>
Expand All @@ -967,7 +1015,7 @@ export default function Send() {
{isCoinjoin && (
<rb.Row>
<rb.Col xs={3} className="text-end">
<strong>{t('send.confirm_modal.label_num_collaborators')}</strong>
<strong>{t('send.confirm_send_modal.label_num_collaborators')}</strong>
</rb.Col>
<rb.Col xs={9} className="text-start">
{numCollaborators}
Expand Down
7 changes: 6 additions & 1 deletion src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"next": "Next",
"back": "Back",
"close": "Close",
"abort": "Abort",
"table": {
"pagination": {
"items_per_page": {
Expand Down Expand Up @@ -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</1> or <3>time-locked</3>. 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</5> for more details.",
"confirm_modal": {
"confirm_send_modal": {
"title": "Confirm payment",
"label_amount": "Amount",
"text_sweep_balance": "Sweep whole jar (<1></1>)",
Expand All @@ -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 }} })",
Expand Down
4 changes: 2 additions & 2 deletions src/libs/JmWalletApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -414,7 +414,7 @@ export {
postConfigGet,
getWalletSeed,
postSchedulerStart,
getSchedulerStop,
getTakerStop,
getSchedule,
Helper,
}

0 comments on commit 80e40ff

Please sign in to comment.