Skip to content

Commit

Permalink
fix: stx send form high fee warning, closes #5362
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-eggo committed Jun 19, 2024
1 parent b03d6af commit 0af3ae9
Show file tree
Hide file tree
Showing 16 changed files with 173 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -1,53 +1,46 @@
import { useEffect, useState } from 'react';

import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { useFormikContext } from 'formik';
import { HStack, Stack } from 'leather-styles/jsx';

import { Button, Caption, ErrorIcon, Link, Title } from '@leather-wallet/ui';

import {
BitcoinSendFormValues,
StacksSendFormValues,
StacksTransactionFormValues,
} from '@shared/models/form.model';
import { StacksSendFormValues } from '@shared/models/form.model';

import { openInNewTab } from '@app/common/utils/open-in-new-tab';
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 { useStacksHighFeeWarningContext } from './stacks-high-fee-warning-container';

interface HighFeeDialogProps {
learnMoreUrl: string;
isShowing?: boolean;
}
export function HighFeeDialog({ learnMoreUrl }: HighFeeDialogProps) {
const { handleSubmit, values } = useFormikContext<StacksSendFormValues>();
const { showHighFeeWarningDialog, setHasBypassedFeeWarning, setShowHighFeeWarningDialog } =
useStacksHighFeeWarningContext();

export function HighFeeDialog({ learnMoreUrl, isShowing = false }: HighFeeDialogProps) {
const [isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation] = useState(isShowing);

useEffect(() => {
return () => {
if (isShowingHighFeeConfirmation) setIsShowingHighFeeConfirmation(false);
};
}, [isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation]);

const { handleSubmit, values } = useFormikContext<
BitcoinSendFormValues | StacksSendFormValues | StacksTransactionFormValues
>();
return (
<Dialog
data-testid={SendCryptoAssetSelectors.HighFeeWarningDialog}
header={<DialogHeader />}
isShowing={isShowingHighFeeConfirmation}
onClose={() => setIsShowingHighFeeConfirmation(false)}
isShowing={showHighFeeWarningDialog}
onClose={() => setShowHighFeeWarningDialog(false)}
footer={
<Footer flexDirection="row">
<Button onClick={() => setShowHighFeeWarningDialog(false)} variant="outline" flexGrow={1}>
Edit fee
</Button>
<Button
onClick={() => setIsShowingHighFeeConfirmation(false)}
variant="outline"
onClick={() => {
setHasBypassedFeeWarning(true);
handleSubmit();
}}
data-testid={SendCryptoAssetSelectors.HighFeeWarningDialogSubmit}
type="submit"
flexGrow={1}
>
Edit fee
</Button>
<Button onClick={() => handleSubmit()} type="submit" flexGrow={1}>
Yes, I'm sure
</Button>
</Footer>
Expand All @@ -62,9 +55,8 @@ export function HighFeeDialog({ learnMoreUrl, isShowing = false }: HighFeeDialog
</Title>
</HStack>
<Caption>
This action cannot be undone and the fees won't be returned, even if the transaction
fails.
<Link onClick={() => openInNewTab(learnMoreUrl)} size="sm">
This action cannot be undone. The fees won't be returned, even if the transaction fails.
<Link onClick={() => openInNewTab(learnMoreUrl)} size="sm" ml="space.01">
Learn more
</Link>
</Caption>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { createContext, useContext, useState } from 'react';

import BigNumber from 'bignumber.js';
import type { FormikErrors } from 'formik';

import { HIGH_FEE_AMOUNT_STX } from '@leather-wallet/constants';
import { isEmpty } from '@leather-wallet/utils';

import type { HasChildren } from '@app/common/has-children';

interface StacksHighFeeWarningContext {
showHighFeeWarningDialog: boolean;
setShowHighFeeWarningDialog(val: boolean): void;
hasBypassedFeeWarning: boolean;
setHasBypassedFeeWarning(val: boolean): void;
isHighFeeWithNoFormErrors(errors: FormikErrors<unknown>, fee: number | string): boolean;
}

const stacksHighFeeWarningContext = createContext<StacksHighFeeWarningContext | null>(null);

export function useStacksHighFeeWarningContext() {
const ctx = useContext(stacksHighFeeWarningContext);
if (!ctx) throw new Error(`stacksCommonSendFormContext must be used within a context`);
return ctx;
}

const StacksHighFeeWarningProvider = stacksHighFeeWarningContext.Provider;

export function StacksHighFeeWarningContainer({ children }: HasChildren) {
const [showHighFeeWarningDialog, setShowHighFeeWarningDialog] = useState(false);
const [hasBypassedFeeWarning, setHasBypassedFeeWarning] = useState(false);

function isHighFeeWithNoFormErrors(errors: FormikErrors<unknown>, fee: number | string) {
if (hasBypassedFeeWarning) return false;
return isEmpty(errors) && new BigNumber(fee).isGreaterThan(HIGH_FEE_AMOUNT_STX);
}

return (
<StacksHighFeeWarningProvider
value={{
showHighFeeWarningDialog,
setShowHighFeeWarningDialog,
hasBypassedFeeWarning,
setHasBypassedFeeWarning,
isHighFeeWithNoFormErrors,
}}
>
{children}
</StacksHighFeeWarningProvider>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useState } from 'react';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';

import { StacksTransaction } from '@stacks/transactions';
Expand All @@ -24,7 +23,6 @@ import { useOnMount } from '@app/common/hooks/use-on-mount';
import { stxFeeValidator } from '@app/common/validation/forms/fee-validators';
import { nonceValidator } from '@app/common/validation/nonce-validators';
import { NonceSetter } from '@app/components/nonce-setter';
import { HighFeeDialog } from '@app/features/dialogs/high-fee-dialog/high-fee-dialog';
import { RequestingTabClosedWarningMessage } from '@app/features/errors/requesting-tab-closed-error-msg';
import { ContractCallDetails } from '@app/features/stacks-transaction-request/contract-call-details/contract-call-details';
import { ContractDeployDetails } from '@app/features/stacks-transaction-request/contract-deploy-details/contract-deploy-details';
Expand All @@ -36,9 +34,10 @@ import { TransactionError } from '@app/features/stacks-transaction-request/trans
import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { useTransactionRequestState } from '@app/store/transactions/requests.hooks';

import { HighFeeDialog } from '../stacks-high-fee-warning/stacks-high-fee-dialog';
import { FeeForm } from './fee-form';
import { MinimalErrorMessage } from './minimal-error-message';
import { SubmitAction } from './submit-action';
import { StacksTxSubmitAction } from './submit-action';

interface StacksTransactionSignerProps {
stacksTransaction: StacksTransaction;
Expand All @@ -55,7 +54,6 @@ export function StacksTransactionSigner({
onSignStacksTransaction,
isMultisig,
}: StacksTransactionSignerProps) {
const [isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation] = useState(false);
const transactionRequest = useTransactionRequestState();
const { data: stxFees } = useCalculateStacksTxFees(stacksTransaction);

Expand Down Expand Up @@ -136,13 +134,8 @@ export function StacksTransactionSigner({
</Link>
)}
<MinimalErrorMessage />
<SubmitAction
setIsShowingHighFeeConfirmation={() => setIsShowingHighFeeConfirmation(true)}
/>
<HighFeeDialog
isShowing={isShowingHighFeeConfirmation}
learnMoreUrl={HIGH_FEE_WARNING_LEARN_MORE_URL_STX}
/>
<StacksTxSubmitAction />
<HighFeeDialog learnMoreUrl={HIGH_FEE_WARNING_LEARN_MORE_URL_STX} />
<Outlet />
</>
)}
Expand Down
23 changes: 11 additions & 12 deletions src/app/features/stacks-transaction-request/submit-action.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
import { TransactionRequestSelectors } from '@tests/selectors/requests.selectors';
import { useFormikContext } from 'formik';

import { HIGH_FEE_AMOUNT_STX } from '@leather-wallet/constants';
import { Button } from '@leather-wallet/ui';
import { isEmpty } from '@leather-wallet/utils';

import { StacksTransactionFormValues } from '@shared/models/form.model';

import { useTransactionError } from '@app/features/stacks-transaction-request/hooks/use-transaction-error';

interface SubmitActionProps {
setIsShowingHighFeeConfirmation(): void;
}
export function SubmitAction({ setIsShowingHighFeeConfirmation }: SubmitActionProps) {
import { useStacksHighFeeWarningContext } from '../stacks-high-fee-warning/stacks-high-fee-warning-container';

export function StacksTxSubmitAction() {
const { handleSubmit, values, validateForm, isSubmitting } =
useFormikContext<StacksTransactionFormValues>();

const context = useStacksHighFeeWarningContext();

const error = useTransactionError();

const isDisabled = !!error || Number(values.fee) < 0;

const onConfirmTransaction = async () => {
// Check for errors before showing the high fee confirmation
async function onConfirmTransaction() {
const formErrors = await validateForm();
if (isEmpty(formErrors) && Number(values.fee) > HIGH_FEE_AMOUNT_STX) {
return setIsShowingHighFeeConfirmation();
}

if (context.isHighFeeWithNoFormErrors(formErrors, values.fee))
return context.setShowHighFeeWarningDialog(true);

handleSubmit();
};
}

return (
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { StacksHighFeeWarningContainer } from '@app/features/stacks-high-fee-warning/stacks-high-fee-warning-container';
import { StacksTransactionSigner } from '@app/features/stacks-transaction-request/stacks-transaction-signer';
import { useRpcSignStacksTransaction } from '@app/pages/rpc-sign-stacks-transaction/use-rpc-sign-stacks-transaction';
import { useBreakOnNonCompliantEntity } from '@app/query/common/compliance-checker/compliance-checker.query';
Expand All @@ -16,13 +17,15 @@ export function RpcSignStacksTransaction() {
useBreakOnNonCompliantEntity(txSender);

return (
<StacksTransactionSigner
onSignStacksTransaction={onSignStacksTransaction}
onCancel={onCancel}
isMultisig={isMultisig}
stacksTransaction={stacksTransaction}
disableFeeSelection={disableFeeSelection}
disableNonceSelection={disableNonceSelection}
/>
<StacksHighFeeWarningContainer>
<StacksTransactionSigner
onSignStacksTransaction={onSignStacksTransaction}
onCancel={onCancel}
isMultisig={isMultisig}
stacksTransaction={stacksTransaction}
disableFeeSelection={disableFeeSelection}
disableNonceSelection={disableNonceSelection}
/>
</StacksHighFeeWarningContainer>
);
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import BigNumber from 'bignumber.js';
import { FormikHelpers } from 'formik';

import { HIGH_FEE_AMOUNT_STX } from '@leather-wallet/constants';
import { FeeTypes, type Money } from '@leather-wallet/models';
import { useNextNonce } from '@leather-wallet/query';
import { isEmpty } from '@leather-wallet/utils';

import { FormErrorMessages } from '@shared/error-messages';
import { StacksSendFormValues } from '@shared/models/form.model';

import { stxMemoValidator } from '@app/common/validation/forms/memo-validators';
import { stxRecipientValidator } from '@app/common/validation/forms/recipient-validators';
import { nonceValidator } from '@app/common/validation/nonce-validators';
import { useStacksHighFeeWarningContext } from '@app/features/stacks-high-fee-warning/stacks-high-fee-warning-container';
import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';

Expand All @@ -22,7 +20,6 @@ interface UseStacksCommonSendFormArgs {
symbol: string;
availableTokenBalance: Money;
}

export function useStacksCommonSendForm({
symbol,
availableTokenBalance,
Expand All @@ -31,8 +28,12 @@ export function useStacksCommonSendForm({
const stxAddress = useCurrentStacksAccountAddress();
const { data: nextNonce } = useNextNonce(stxAddress);
const currentNetwork = useCurrentNetworkState();
const { isHighFeeWithNoFormErrors, setShowHighFeeWarningDialog } =
useStacksHighFeeWarningContext();

const initialValues: StacksSendFormValues = createDefaultInitialFormValues({
hasDismissedHighFeeWarning: false,
isShowingHighFeeDiaglog: false,
fee: '',
feeCurrency: 'STX',
feeType: FeeTypes[FeeTypes.Unknown],
Expand All @@ -42,13 +43,15 @@ export function useStacksCommonSendForm({
symbol,
...routeState,
});

async function checkFormValidation(
values: StacksSendFormValues,
formikHelpers: FormikHelpers<StacksSendFormValues>
) {
// Validate and check high fee warning first
const formErrors = formikHelpers.validateForm();
if (isEmpty(formErrors) && new BigNumber(values.fee).isGreaterThan(HIGH_FEE_AMOUNT_STX)) {
const formErrors = await formikHelpers.validateForm();

if (isHighFeeWithNoFormErrors(formErrors, values.fee)) {
setShowHighFeeWarningDialog(true);
return false;
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { Form, Formik } from 'formik';
import { Box } from 'leather-styles/jsx';

import { HIGH_FEE_WARNING_LEARN_MORE_URL_BTC } from '@leather-wallet/constants';
import type { CryptoCurrencies } from '@leather-wallet/models';
import { useCryptoCurrencyMarketDataMeanAverage } from '@leather-wallet/query';
import { Button, Callout, Link } from '@leather-wallet/ui';
import { formatMoney } from '@leather-wallet/utils';

import { HighFeeDialog } from '@app/features/dialogs/high-fee-dialog/high-fee-dialog';
import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon';
import { AvailableBalance } from '@app/ui/components/containers/footers/available-balance';
import { Footer } from '@app/ui/components/containers/footers/footer';
Expand Down Expand Up @@ -113,7 +111,6 @@ export function BtcSendForm() {
)}
</CardContent>
</Card>
<HighFeeDialog learnMoreUrl={HIGH_FEE_WARNING_LEARN_MORE_URL_BTC} />
<Outlet />

{/* This is for testing purposes only, to make sure the form is ready to be submitted. */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
useGenerateFtTokenTransferUnsignedTx,
} from '@app/store/transactions/token-transfer.hooks';

import { useStacksCommonSendForm } from '../../family/stacks/use-stacks-common-send-form';
import { useSendFormNavigate } from '../../hooks/use-send-form-navigate';
import { useStacksCommonSendForm } from '../stacks/use-stacks-common-send-form';

interface UseSip10SendFormArgs {
balance: CryptoAssetBalance;
Expand Down
Loading

0 comments on commit 0af3ae9

Please sign in to comment.