-
Notifications
You must be signed in to change notification settings - Fork 149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/cancel pending transaction #5520
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { StacksNetwork } from '@stacks/network'; | ||
import { ChainID } from '@stacks/transactions'; | ||
|
||
export function getBurnAddress(network: StacksNetwork): string { | ||
switch (network.chainId) { | ||
case ChainID.Mainnet: | ||
return 'SP00000000000003SCNSJTCSE62ZF4MSE'; | ||
case ChainID.Testnet: | ||
return 'ST000000000000000000002AMW42H'; | ||
default: | ||
return 'ST000000000000000000002AMW42H'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,38 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { HStack, styled } from 'leather-styles/jsx'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
interface CancelTransactionButtonProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isEnabled?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isSelected: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onCancelTransaction(): void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export function CancelTransactionButton(props: CancelTransactionButtonProps) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { isEnabled, isSelected, onCancelTransaction } = props; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const isActive = isEnabled && !isSelected; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isActive && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<styled.button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_hover={{ color: 'ink.text-subdued' }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bg="ink.background-primary" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
maxWidth="125px" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ml="auto" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onClick={e => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onCancelTransaction(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
e.stopPropagation(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pointerEvents={!isActive ? 'none' : 'all'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
position="relative" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
px="space.02" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
py="space.01" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
rounded="xs" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
zIndex={999} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<HStack gap="space.01"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<styled.span textStyle="label.03" color="yellow.action-primary-default"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Cancel transaction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</styled.span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</HStack> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</styled.button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+12
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I'd suggest short circuiting the component's render with an if statement
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,27 +12,28 @@ export function IncreaseFeeButton(props: IncreaseFeeButtonProps) { | |
const isActive = isEnabled && !isSelected; | ||
|
||
return ( | ||
<styled.button | ||
_hover={{ color: 'ink.text-subdued' }} | ||
bg="ink.background-primary" | ||
maxWidth="110px" | ||
ml="auto" | ||
onClick={e => { | ||
onIncreaseFee(); | ||
e.stopPropagation(); | ||
}} | ||
opacity={!isActive ? 0 : 1} | ||
pointerEvents={!isActive ? 'none' : 'all'} | ||
position="relative" | ||
px="space.02" | ||
py="space.01" | ||
rounded="xs" | ||
zIndex={999} | ||
> | ||
<HStack gap="space.01"> | ||
<ChevronsRightIcon color="stacks" variant="small" /> | ||
<styled.span textStyle="label.03">Increase fee</styled.span> | ||
</HStack> | ||
</styled.button> | ||
isActive && ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here too |
||
<styled.button | ||
_hover={{ color: 'ink.text-subdued' }} | ||
bg="ink.background-primary" | ||
maxWidth="110px" | ||
ml="auto" | ||
onClick={e => { | ||
onIncreaseFee(); | ||
e.stopPropagation(); | ||
}} | ||
pointerEvents={!isActive ? 'none' : 'all'} | ||
position="relative" | ||
px="space.02" | ||
py="space.01" | ||
rounded="xs" | ||
zIndex={999} | ||
> | ||
<HStack gap="space.01"> | ||
<ChevronsRightIcon color="stacks" variant="small" /> | ||
<styled.span textStyle="label.03">Increase fee</styled.span> | ||
</HStack> | ||
</styled.button> | ||
) | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { Suspense, useEffect } from 'react'; | ||
import { Outlet, useLocation, useNavigate, useSearchParams } from 'react-router-dom'; | ||
|
||
import { microStxToStx, stxToMicroStx } from '@leather-wallet/utils'; | ||
import BigNumber from 'bignumber.js'; | ||
import { Formik } from 'formik'; | ||
import { Flex, Stack } from 'leather-styles/jsx'; | ||
|
||
import { RouteUrls } from '@shared/route-urls'; | ||
|
||
import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; | ||
import { stacksValue } from '@app/common/stacks-utils'; | ||
import { FeesRow } from '@app/components/fees-row/fees-row'; | ||
import { LoadingSpinner } from '@app/components/loading-spinner'; | ||
import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; | ||
import { useToast } from '@app/features/toasts/use-toast'; | ||
import { Dialog } from '@app/ui/components/containers/dialog/dialog'; | ||
import { Footer } from '@app/ui/components/containers/footers/footer'; | ||
import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; | ||
import { Spinner } from '@app/ui/components/spinner'; | ||
import { Caption } from '@app/ui/components/typography/caption'; | ||
|
||
import { CancelTransactionActions } from './components/cancel-transaction-actions'; | ||
import { useStxCancelTransaction } from './hooks/use-stx-cancel-transaction'; | ||
|
||
export function CancelStxTransactionDialog() { | ||
const { | ||
rawTx, | ||
rawTxId, | ||
setRawTxId, | ||
tx, | ||
setTxId, | ||
onSubmit, | ||
validationSchema, | ||
availableUnlockedBalance, | ||
stxFees, | ||
} = useStxCancelTransaction(); | ||
const { isLoading, setIsIdle } = useLoading(LoadingKeys.CANCEL_TRANSACTION_DRAWER); | ||
const navigate = useNavigate(); | ||
const location = useLocation(); | ||
const [searchParams] = useSearchParams(); | ||
const txIdFromParams = searchParams.get('txId'); | ||
const toast = useToast(); | ||
|
||
const fee = Number(rawTx?.auth.spendingCondition?.fee); | ||
|
||
useEffect(() => { | ||
if (tx?.tx_status !== 'pending' && rawTx) { | ||
setTxId(null); | ||
toast.info('Your transaction went through! Cancellation not possible.'); | ||
} | ||
}, [rawTx, tx?.tx_status, setTxId, toast]); | ||
Comment on lines
+47
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactor to simplify the effect handling transaction status. The effect on lines 47-52 could be simplified or broken down into smaller, more testable functions. This would improve readability and maintainability. |
||
|
||
useEffect(() => { | ||
if (!rawTxId && txIdFromParams) { | ||
setRawTxId(txIdFromParams); | ||
} | ||
if (isLoading && !rawTxId) { | ||
setIsIdle(); | ||
} | ||
}, [isLoading, rawTxId, setIsIdle, setRawTxId, txIdFromParams]); | ||
|
||
if (!tx || !fee) return <LoadingSpinner />; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle potential null values gracefully in rendering logic. The rendering logic on line 63 directly accesses properties which might be null or undefined. This could lead to runtime errors. Consider adding null checks or using optional chaining to safeguard against these issues. |
||
|
||
const onClose = () => { | ||
setRawTxId(null); | ||
navigate(RouteUrls.Home); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Formik | ||
initialValues={{ fee: new BigNumber(microStxToStx(fee)).toNumber() }} | ||
onSubmit={onSubmit} | ||
validateOnChange={false} | ||
validateOnBlur={false} | ||
validateOnMount={true} | ||
validationSchema={validationSchema} | ||
> | ||
{props => ( | ||
<> | ||
<Dialog | ||
isShowing={location.pathname === RouteUrls.CancelStxTransaction} | ||
onClose={onClose} | ||
header={<DialogHeader title="Cancel transaction" />} | ||
footer={ | ||
<Footer flexDirection="row"> | ||
<CancelTransactionActions | ||
onCancel={() => { | ||
setTxId(null); | ||
navigate(RouteUrls.Home); | ||
}} | ||
isDisabled={stxToMicroStx(props.values.fee).isEqualTo(fee)} | ||
/> | ||
</Footer> | ||
} | ||
> | ||
<Stack gap="space.05" px="space.05" pb="space.05"> | ||
<Suspense | ||
fallback={ | ||
<Flex alignItems="center" justifyContent="center" p="space.06"> | ||
<Spinner /> | ||
</Flex> | ||
} | ||
> | ||
<Caption> | ||
Canceling a transaction isn't guaranteed to work. A higher fee can help replace | ||
the old transaction | ||
</Caption> | ||
<Stack gap="space.06"> | ||
{tx && <StacksTransactionItem transaction={tx} />} | ||
<Stack gap="space.04"> | ||
<FeesRow fees={stxFees} defaultFeeValue={fee + 1} isSponsored={false} /> | ||
{availableUnlockedBalance?.amount && ( | ||
<Caption> | ||
Balance: | ||
{stacksValue({ | ||
value: availableUnlockedBalance.amount, | ||
fixedDecimals: true, | ||
})} | ||
</Caption> | ||
)} | ||
</Stack> | ||
</Stack> | ||
</Suspense> | ||
</Stack> | ||
</Dialog> | ||
<Outlet /> | ||
</> | ||
)} | ||
</Formik> | ||
</> | ||
); | ||
Comment on lines
+70
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Review and optimize the Formik usage for performance. The Formik component is used extensively in the rendering logic. Consider optimizing the re-renders and validations to enhance performance, especially since |
||
} | ||
Comment on lines
+26
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure proper error handling and edge case management in The function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider handling the default case explicitly in
getBurnAddress
to avoid potential issues in production environments.Committable suggestion