mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-03 05:55:03 +01:00
Revert "Feat: update fee rate options regularly"
This commit is contained in:
committed by
Jiri Zbytovsky
parent
0b8406a03e
commit
26069e28b5
@@ -1,8 +1,5 @@
|
||||
import { selectAreFeesLoading } from '@suite-common/wallet-core';
|
||||
import { Note } from '@trezor/components';
|
||||
import { isApproxEqual } from '@trezor/utils';
|
||||
|
||||
import { useSelector } from '../../../hooks/suite';
|
||||
import { Translation } from '../../suite';
|
||||
|
||||
type DustPreventionNoticeProps = {
|
||||
@@ -18,14 +15,10 @@ export const DustPreventionNotice = ({
|
||||
baseFee,
|
||||
feeUnits,
|
||||
}: DustPreventionNoticeProps) => {
|
||||
const areFeesLoading = useSelector(state => selectAreFeesLoading(state));
|
||||
|
||||
const relativeTolerance = 1e-3;
|
||||
const isComposedFeeRateDifferent =
|
||||
!areFeesLoading &&
|
||||
composedFeePerByte !== undefined &&
|
||||
chosenFeePerByte !== undefined &&
|
||||
!isApproxEqual(composedFeePerByte, chosenFeePerByte, relativeTolerance);
|
||||
composedFeePerByte !== '' &&
|
||||
chosenFeePerByte !== composedFeePerByte;
|
||||
|
||||
if (!isComposedFeeRateDifferent) return null;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import {
|
||||
Control,
|
||||
FieldErrors,
|
||||
@@ -13,6 +13,7 @@ import { useTheme } from 'styled-components';
|
||||
|
||||
import { TranslationKey } from '@suite-common/intl-types';
|
||||
import { NetworkSymbol, NetworkType } from '@suite-common/wallet-config';
|
||||
import { updateFeeInfoThunk } from '@suite-common/wallet-core';
|
||||
import {
|
||||
FeeInfo,
|
||||
FormState,
|
||||
@@ -26,7 +27,7 @@ import { spacings } from '@trezor/theme';
|
||||
import { HELP_CENTER_TRANSACTION_FEES_URL } from '@trezor/urls';
|
||||
|
||||
import { Translation } from 'src/components/suite';
|
||||
import { useRefetchFees } from 'src/hooks/wallet/useRefetchFees';
|
||||
import { useDispatch } from 'src/hooks/suite';
|
||||
import { Account } from 'src/types/wallet';
|
||||
|
||||
import { CustomFee } from './CustomFee/CustomFee';
|
||||
@@ -143,6 +144,7 @@ export const Fees = <TFieldValues extends FormState>({
|
||||
// Type assertion allowing to make the component reusable, see https://stackoverflow.com/a/73624072.
|
||||
const { getValues, register, setValue, trigger } = props as unknown as UseFormReturn<FormState>;
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const selectedOption = getValues('selectedFee') || 'normal';
|
||||
const isCustomFee = selectedOption === 'custom';
|
||||
@@ -156,7 +158,9 @@ export const Fees = <TFieldValues extends FormState>({
|
||||
|
||||
const supportsCustomFee = networkType !== 'solana';
|
||||
|
||||
useRefetchFees({ networkSymbol: symbol });
|
||||
useEffect(() => {
|
||||
dispatch(updateFeeInfoThunk({ networkSymbol: symbol }));
|
||||
}, [dispatch, symbol]);
|
||||
|
||||
const feeLabelId = useMemo(() => {
|
||||
switch (networkType) {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { formatDurationStrict } from '@suite-common/suite-utils';
|
||||
import { selectAreFeesLoading } from '@suite-common/wallet-core';
|
||||
import { getFeeUnits } from '@suite-common/wallet-utils';
|
||||
import { Row, Text } from '@trezor/components';
|
||||
import { FeeRate } from '@trezor/product-components';
|
||||
|
||||
import { Translation } from 'src/components/suite';
|
||||
import { FiatValue } from 'src/components/suite/FiatValue';
|
||||
import { useLocales, useSelector } from 'src/hooks/suite';
|
||||
import { useLocales } from 'src/hooks/suite';
|
||||
|
||||
import { FeeCard } from './FeeCard';
|
||||
import { FeeCardsWrapper, StandardFeeProps } from './StandardFee';
|
||||
@@ -27,7 +26,6 @@ export const BitcoinFeeCards = ({
|
||||
getValues,
|
||||
}: StandardFeeProps) => {
|
||||
const locale = useLocales();
|
||||
const areFeesLoading = useSelector(state => selectAreFeesLoading(state));
|
||||
|
||||
const [cachedBytes, setCachedBytes] = useState<number | undefined>(undefined);
|
||||
|
||||
@@ -62,7 +60,6 @@ export const BitcoinFeeCards = ({
|
||||
value={fee.value}
|
||||
isSelected={selectedLevel.label === fee.value}
|
||||
changeFeeLevel={changeFeeLevel}
|
||||
isLoading={areFeesLoading}
|
||||
topLeftChild={
|
||||
<span data-testid={`@fee-card/${fee.value}`}>
|
||||
<Translation id={getFeeLevelTranslationId(fee.value)} />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { formatDurationStrict } from '@suite-common/suite-utils';
|
||||
import { selectAreFeesLoading } from '@suite-common/wallet-core';
|
||||
import { getFeeUnits, isEip1559 } from '@suite-common/wallet-utils';
|
||||
import { Badge, Grid, Row, Text } from '@trezor/components';
|
||||
import { FeeRate } from '@trezor/product-components';
|
||||
@@ -27,8 +26,6 @@ export const EthereumFeeCards = ({
|
||||
}: StandardFeeProps) => {
|
||||
const locale = useLocales();
|
||||
const isDebug = useSelector(selectIsDebugModeActive);
|
||||
const areFeesLoading = useSelector(state => selectAreFeesLoading(state));
|
||||
|
||||
const [cachedGasLimit, setCachedGasLimit] = useState<string | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -61,7 +58,6 @@ export const EthereumFeeCards = ({
|
||||
key={fee.value}
|
||||
isSelected={selectedLevel.label === fee.value}
|
||||
changeFeeLevel={changeFeeLevel}
|
||||
isLoading={areFeesLoading}
|
||||
topLeftChild={
|
||||
<span data-testid={`@fee-card/${fee.value}`}>
|
||||
<Translation id={getFeeLevelTranslationId(fee.value)} />
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Column,
|
||||
RadioCard,
|
||||
Row,
|
||||
SkeletonRectangle,
|
||||
TOOLTIP_DELAY_NORMAL,
|
||||
Text,
|
||||
Tooltip,
|
||||
@@ -18,12 +17,11 @@ type FeeCardProps = {
|
||||
value: FeeLevel['label'];
|
||||
isSelected: boolean;
|
||||
changeFeeLevel: (level: FeeLevel['label']) => void;
|
||||
topLeftChild: ReactNode;
|
||||
topRightChild?: ReactNode;
|
||||
bottomLeftChild: ReactNode;
|
||||
bottomRightChild: ReactNode;
|
||||
tooltipContent?: ReactNode;
|
||||
isLoading?: boolean;
|
||||
topLeftChild: React.ReactNode;
|
||||
topRightChild?: React.ReactNode;
|
||||
bottomLeftChild: React.ReactNode;
|
||||
bottomRightChild: React.ReactNode;
|
||||
tooltipContent?: React.ReactNode;
|
||||
'data-testid'?: string;
|
||||
};
|
||||
|
||||
@@ -36,7 +34,6 @@ export const FeeCard = ({
|
||||
bottomLeftChild,
|
||||
bottomRightChild,
|
||||
tooltipContent,
|
||||
isLoading,
|
||||
'data-testid': dataTestId,
|
||||
}: FeeCardProps) => (
|
||||
<Box data-testid={dataTestId} minWidth={FEE_CARD_MIN_WIDTH}>
|
||||
@@ -46,15 +43,13 @@ export const FeeCard = ({
|
||||
<Row justifyContent="space-between">
|
||||
<Text typographyStyle="highlight">{topLeftChild}</Text>
|
||||
<Text variant="tertiary" typographyStyle="hint">
|
||||
{isLoading ? <SkeletonRectangle animate={true} /> : topRightChild}
|
||||
{topRightChild}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row justifyContent="space-between" height={24}>
|
||||
<Text>
|
||||
{isLoading ? <SkeletonRectangle animate={true} /> : bottomLeftChild}
|
||||
</Text>
|
||||
<Text>{bottomLeftChild}</Text>
|
||||
<Text variant="tertiary" typographyStyle="hint">
|
||||
{isLoading ? <SkeletonRectangle animate={true} /> : bottomRightChild}
|
||||
{bottomRightChild}
|
||||
</Text>
|
||||
</Row>
|
||||
</Column>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { selectAreFeesLoading } from '@suite-common/wallet-core';
|
||||
import { getFeeUnits } from '@suite-common/wallet-utils';
|
||||
import { Text } from '@trezor/components';
|
||||
|
||||
import { FiatValue, Translation } from 'src/components/suite';
|
||||
import { useSelector } from 'src/hooks/suite';
|
||||
|
||||
import { FeeCard } from './FeeCard';
|
||||
import { FeeCardsWrapper, StandardFeeProps } from './StandardFee';
|
||||
@@ -16,7 +14,6 @@ export const MiscFeeCards = ({
|
||||
symbol,
|
||||
changeFeeLevel,
|
||||
}: StandardFeeProps) => {
|
||||
const areFeesLoading = useSelector(state => selectAreFeesLoading(state));
|
||||
if (!feeOptions.length) return null;
|
||||
|
||||
const isSolanaNetwork = networkType === 'solana';
|
||||
@@ -31,7 +28,6 @@ export const MiscFeeCards = ({
|
||||
value={fee.value}
|
||||
isSelected={true}
|
||||
changeFeeLevel={changeFeeLevel}
|
||||
isLoading={areFeesLoading}
|
||||
topLeftChild={
|
||||
<span data-testid={`@fee-card/${fee.value}`}>
|
||||
<Translation id={getFeeLevelTranslationId(fee.value)} />
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useInterval } from 'react-use';
|
||||
|
||||
import { NetworkSymbol } from '@suite-common/wallet-config';
|
||||
import {
|
||||
FEES_UPDATE_INTERVAL_MILLISECONDS,
|
||||
delayedUpdateFeeInfoThunk,
|
||||
updateFeeInfoThunk,
|
||||
} from '@suite-common/wallet-core';
|
||||
|
||||
import { useDispatch } from 'src/hooks/suite';
|
||||
|
||||
type UseRefetchFeesProps = { networkSymbol: NetworkSymbol };
|
||||
|
||||
export const useRefetchFees = ({ networkSymbol }: UseRefetchFeesProps) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Initial fetch only when component mounts
|
||||
useEffect(() => {
|
||||
dispatch(updateFeeInfoThunk({ networkSymbol }));
|
||||
}, [dispatch, networkSymbol]);
|
||||
|
||||
// Refetch fees periodically incl. loading behavior
|
||||
useInterval(() => {
|
||||
dispatch(delayedUpdateFeeInfoThunk({ networkSymbol }));
|
||||
}, FEES_UPDATE_INTERVAL_MILLISECONDS);
|
||||
};
|
||||
@@ -74,6 +74,7 @@ const getStateFromProps = (props: UseSendFormProps) => {
|
||||
return {
|
||||
account,
|
||||
network,
|
||||
|
||||
localCurrencyOption,
|
||||
online: props.online,
|
||||
metadataEnabled: props.metadataEnabled,
|
||||
@@ -319,7 +320,7 @@ export const useSendForm = (props: UseSendFormProps): SendContextValues => {
|
||||
state.network.decimals,
|
||||
]);
|
||||
|
||||
// load draft from reducer and reset current form values, this should be only called once on mount
|
||||
// load draft from reducer
|
||||
useEffect(() => {
|
||||
const loadDraftValues = async () => {
|
||||
const storedState = await dispatch(getSendFormDraftThunk()).unwrap();
|
||||
@@ -334,9 +335,7 @@ export const useSendForm = (props: UseSendFormProps): SendContextValues => {
|
||||
}
|
||||
};
|
||||
loadDraftValues();
|
||||
// composeDraft is excluded because its reference changes with each feeInfo update.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dispatch, getLoadedValues, reset]);
|
||||
}, [dispatch, getLoadedValues, reset, composeDraft]);
|
||||
|
||||
// register custom form fields (without HTMLElement)
|
||||
useEffect(() => {
|
||||
@@ -349,14 +348,7 @@ export const useSendForm = (props: UseSendFormProps): SendContextValues => {
|
||||
if (!draft.current) return;
|
||||
composeDraft(draft.current);
|
||||
draft.current = undefined;
|
||||
// composeDraft is excluded because its reference changes with each feeInfo update.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [draft]);
|
||||
|
||||
// update composedLevels when feeInfo changes
|
||||
useEffect(() => {
|
||||
composeDraft(getValues());
|
||||
}, [composeDraft, getValues]);
|
||||
}, [draft, composeDraft]);
|
||||
|
||||
// handle draftSaveRequest
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,13 +2,12 @@ import { useWatch } from 'react-hook-form';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { selectAreFeesLoading } from '@suite-common/wallet-core';
|
||||
import { isLowAnonymityWarning } from '@suite-common/wallet-utils';
|
||||
import { Banner, Button, Checkbox, Tooltip, variables } from '@trezor/components';
|
||||
import { spacingsPx } from '@trezor/theme';
|
||||
|
||||
import { Translation } from 'src/components/suite/Translation';
|
||||
import { useDevice, useSelector } from 'src/hooks/suite';
|
||||
import { useDevice } from 'src/hooks/suite';
|
||||
import { useSendFormContext } from 'src/hooks/wallet';
|
||||
|
||||
const Container = styled.div`
|
||||
@@ -69,7 +68,7 @@ export const ReviewButton = () => {
|
||||
control,
|
||||
formState: { errors },
|
||||
online,
|
||||
isLoading: isSendFormLoading,
|
||||
isLoading,
|
||||
signTransaction,
|
||||
getValues,
|
||||
getDefaultValue,
|
||||
@@ -82,8 +81,6 @@ export const ReviewButton = () => {
|
||||
toggleAnonymityWarning,
|
||||
},
|
||||
} = useSendFormContext();
|
||||
const areFeesLoading = useSelector(state => selectAreFeesLoading(state));
|
||||
const isLoading = isSendFormLoading || areFeesLoading;
|
||||
|
||||
const options = useWatch({
|
||||
name: 'options',
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
import { PropsWithChildren, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { selectAreFeesLoading } from '@suite-common/wallet-core';
|
||||
import { formatAmount, formatNetworkAmount } from '@suite-common/wallet-utils';
|
||||
import { Card, Column, InfoItem, SkeletonRectangle } from '@trezor/components';
|
||||
import { Card, Column, InfoItem } from '@trezor/components';
|
||||
import { spacings } from '@trezor/theme';
|
||||
|
||||
import { FiatValue, FormattedCryptoAmount, Translation } from 'src/components/suite';
|
||||
import { useSelector } from 'src/hooks/suite';
|
||||
import { useSendFormContext } from 'src/hooks/wallet';
|
||||
|
||||
import { ReviewButton } from './ReviewButton';
|
||||
|
||||
type ChildOrSkeletonProps = PropsWithChildren<{ isLoading?: boolean }>;
|
||||
|
||||
const ChildOrSkeleton = ({ children, isLoading }: ChildOrSkeletonProps) =>
|
||||
isLoading ? <SkeletonRectangle animate={true} /> : children;
|
||||
|
||||
const Container = styled.div`
|
||||
position: sticky;
|
||||
top: 80px;
|
||||
@@ -29,7 +22,6 @@ export const TotalSent = () => {
|
||||
composedLevels,
|
||||
getValues,
|
||||
} = useSendFormContext();
|
||||
const areFeesLoading = useSelector(state => selectAreFeesLoading(state));
|
||||
|
||||
const selectedFee = getValues().selectedFee || 'normal';
|
||||
const transactionInfo = composedLevels ? composedLevels[selectedFee] : undefined;
|
||||
@@ -60,49 +52,39 @@ export const TotalSent = () => {
|
||||
variant="default"
|
||||
typographyStyle="body"
|
||||
>
|
||||
<ChildOrSkeleton isLoading={areFeesLoading}>
|
||||
{hasTransactionInfo && (
|
||||
<FormattedCryptoAmount
|
||||
disableHiddenPlaceholder
|
||||
value={
|
||||
tokenInfo
|
||||
? formatAmount(
|
||||
transactionInfo.totalSpent,
|
||||
tokenInfo.decimals,
|
||||
)
|
||||
: formatNetworkAmount(
|
||||
transactionInfo.totalSpent,
|
||||
symbol,
|
||||
)
|
||||
}
|
||||
symbol={tokenInfo?.symbol ?? symbol}
|
||||
contractAddress={tokenInfo?.contract}
|
||||
/>
|
||||
)}
|
||||
</ChildOrSkeleton>
|
||||
{hasTransactionInfo && (
|
||||
<FormattedCryptoAmount
|
||||
disableHiddenPlaceholder
|
||||
value={
|
||||
tokenInfo
|
||||
? formatAmount(
|
||||
transactionInfo.totalSpent,
|
||||
tokenInfo.decimals,
|
||||
)
|
||||
: formatNetworkAmount(transactionInfo.totalSpent, symbol)
|
||||
}
|
||||
symbol={tokenInfo?.symbol ?? symbol}
|
||||
contractAddress={tokenInfo?.contract}
|
||||
/>
|
||||
)}
|
||||
</InfoItem>
|
||||
|
||||
<InfoItem label={<Translation id={feeLabelId} />} direction="row">
|
||||
<ChildOrSkeleton isLoading={areFeesLoading}>
|
||||
{hasTransactionInfo &&
|
||||
(tokenInfo ? (
|
||||
<FormattedCryptoAmount
|
||||
disableHiddenPlaceholder
|
||||
value={formatNetworkAmount(transactionInfo.fee, symbol)}
|
||||
symbol={symbol}
|
||||
contractAddress={tokenInfo.contract}
|
||||
/>
|
||||
) : (
|
||||
<FiatValue
|
||||
disableHiddenPlaceholder
|
||||
amount={formatNetworkAmount(
|
||||
transactionInfo.totalSpent,
|
||||
symbol,
|
||||
)}
|
||||
symbol={symbol}
|
||||
/>
|
||||
))}
|
||||
</ChildOrSkeleton>
|
||||
{hasTransactionInfo &&
|
||||
(tokenInfo ? (
|
||||
<FormattedCryptoAmount
|
||||
disableHiddenPlaceholder
|
||||
value={formatNetworkAmount(transactionInfo.fee, symbol)}
|
||||
symbol={symbol}
|
||||
contractAddress={tokenInfo.contract}
|
||||
/>
|
||||
) : (
|
||||
<FiatValue
|
||||
disableHiddenPlaceholder
|
||||
amount={formatNetworkAmount(transactionInfo.totalSpent, symbol)}
|
||||
symbol={symbol}
|
||||
/>
|
||||
))}
|
||||
</InfoItem>
|
||||
</Column>
|
||||
<ReviewButton />
|
||||
|
||||
@@ -32,7 +32,6 @@ export * from './getWeakRandomNumberInRange';
|
||||
export * from './hasUppercaseLetter';
|
||||
export { hexToRgba } from './hexToRgba';
|
||||
export { hexToRgbaArray } from './hexToRgbaArray';
|
||||
export { isApproxEqual } from './isApproxEqual';
|
||||
export * from './isArrayMember';
|
||||
export * from './isFullPath';
|
||||
export * from './isHex';
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { BigNumber, BigNumberValue } from './bigNumber';
|
||||
|
||||
/**
|
||||
* Check if two BigNumber values are approximately equal based on relative difference
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @param relativeTolerance maximum threshold for relative difference of values to declare them approx. equal
|
||||
*/
|
||||
export const isApproxEqual = (
|
||||
value1: BigNumberValue,
|
||||
value2: BigNumberValue,
|
||||
relativeTolerance: BigNumberValue,
|
||||
): boolean => {
|
||||
value1 = new BigNumber(value1);
|
||||
value2 = new BigNumber(value2);
|
||||
relativeTolerance = new BigNumber(relativeTolerance);
|
||||
|
||||
// Cannot calculate relative difference if one value is zero, but then they must be equal
|
||||
if (value1.eq(0)) return value1.eq(value2);
|
||||
|
||||
const relativeDifference = value2.minus(value1).abs().dividedBy(value1);
|
||||
|
||||
return relativeDifference.lte(relativeTolerance);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { BigNumber } from '../src/bigNumber';
|
||||
import { isApproxEqual } from '../src/isApproxEqual';
|
||||
|
||||
describe(isApproxEqual.name, () => {
|
||||
it('always returns true for equal values', () => {
|
||||
expect(isApproxEqual(100, 100, 0.001)).toBe(true);
|
||||
expect(isApproxEqual(100, '100', 0.001)).toBe(true);
|
||||
expect(isApproxEqual('100', '100', 0.001)).toBe(true);
|
||||
expect(isApproxEqual(new BigNumber(-123), '-123', 0.001)).toBe(true);
|
||||
expect(isApproxEqual('-123.001', '-123.001000', 0.001)).toBe(true);
|
||||
});
|
||||
|
||||
it('handles zero values correctly', () => {
|
||||
expect(isApproxEqual(0, 0, 0.01)).toBe(true);
|
||||
expect(isApproxEqual(0, 1, 0.01)).toBe(false);
|
||||
});
|
||||
|
||||
it('handles relative tolerance', () => {
|
||||
expect(isApproxEqual(100, 101, 0.005)).toBe(false);
|
||||
expect(isApproxEqual(100, 101, 0.01)).toBe(true);
|
||||
expect(isApproxEqual(100, 101, 0.02)).toBe(true);
|
||||
});
|
||||
|
||||
it('handles NaN values', () => {
|
||||
expect(isApproxEqual(100, NaN, 0.01)).toBe(false);
|
||||
expect(isApproxEqual(NaN, 100, 0.01)).toBe(false);
|
||||
expect(isApproxEqual(NaN, NaN, 0.01)).toBe(false);
|
||||
expect(isApproxEqual('nonsense', '123', 0.01)).toBe(false);
|
||||
expect(isApproxEqual('123', 'nonsense', 0.01)).toBe(false);
|
||||
expect(isApproxEqual('nonsense', '', 0.01)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
export const FEES_UPDATE_INTERVAL_MILLISECONDS = 60_000; // interval to refetch estimated fees from backend
|
||||
@@ -1,7 +1,5 @@
|
||||
import { createThunk } from '@suite-common/redux-utils';
|
||||
import { TrezorDevice } from '@suite-common/suite-types';
|
||||
import {
|
||||
Network,
|
||||
NetworkSymbol,
|
||||
getNetwork,
|
||||
getNetworkOptional,
|
||||
@@ -10,13 +8,11 @@ import {
|
||||
import type { NetworksFees } from '@suite-common/wallet-types';
|
||||
import { isEip1559 } from '@suite-common/wallet-utils';
|
||||
import TrezorConnect, { FeeLevel } from '@trezor/connect';
|
||||
import { BlockchainEstimatedFeeLevel } from '@trezor/connect/src/types/api/blockchainEstimateFee';
|
||||
import { isNative } from '@trezor/env-utils';
|
||||
|
||||
import { FEES_MODULE_PREFIX, feesActions } from './feesActions';
|
||||
import { selectFees } from './feesReducer';
|
||||
import { selectNetworkBlockchainInfo } from '../blockchain/blockchainReducer';
|
||||
import { selectSelectedDevice } from '../device/deviceSelectors';
|
||||
import { selectFees } from '../fees/feesReducer';
|
||||
import { selectEnabledNetworks } from '../settings/walletSettingsReducer';
|
||||
|
||||
// Conditionally subscribe to blockchain backend
|
||||
@@ -75,84 +71,84 @@ export const preloadFeeInfoThunk = createThunk(
|
||||
},
|
||||
);
|
||||
|
||||
type GetNewFeeInfoProps = { network: Network; device?: TrezorDevice };
|
||||
const getNewFeeInfo = async ({
|
||||
network,
|
||||
device,
|
||||
}: GetNewFeeInfoProps): Promise<BlockchainEstimatedFeeLevel | undefined> => {
|
||||
if (network.networkType === 'ethereum') {
|
||||
const result = await TrezorConnect.blockchainEstimateFee({
|
||||
coin: network.symbol,
|
||||
request: {
|
||||
blocks: [2],
|
||||
feeLevels: 'smart',
|
||||
specific: {
|
||||
from: '0x0000000000000000000000000000000000000000',
|
||||
to: '0x0000000000000000000000000000000000000000',
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!result.success) return;
|
||||
|
||||
const feeLevelBase = result.payload.levels[0];
|
||||
const isEip1559ActivatedAndAvailable =
|
||||
getNetwork(network.symbol).features.includes('eip1559') &&
|
||||
isEip1559(feeLevelBase) &&
|
||||
!device?.unavailableCapabilities?.['eip1559'] &&
|
||||
!isNative(); // suite-native does not have eip1559 implementation yet #16372
|
||||
|
||||
if (isEip1559ActivatedAndAvailable) return result.payload;
|
||||
|
||||
return {
|
||||
...result.payload,
|
||||
levels: [
|
||||
{
|
||||
...feeLevelBase,
|
||||
baseFeePerGas: undefined,
|
||||
maxFeePerGas: undefined,
|
||||
maxPriorityFeePerGas: undefined,
|
||||
label: 'normal' as const,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const result = await TrezorConnect.blockchainEstimateFee({
|
||||
coin: network.symbol,
|
||||
request: {
|
||||
feeLevels: 'smart',
|
||||
},
|
||||
});
|
||||
if (!result.success) return;
|
||||
|
||||
return {
|
||||
...result.payload,
|
||||
levels: sortLevels(
|
||||
result.payload.levels
|
||||
// hack to hide "low" fee option
|
||||
// (we do not want to change the connect API as it is a potentially breaking change)
|
||||
.filter(level => level.label !== 'low'),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const updateFeeInfoThunk = createThunk(
|
||||
`${FEES_MODULE_PREFIX}/updateFeeInfoThunk`,
|
||||
async ({ networkSymbol }: { networkSymbol: NetworkSymbol }, { dispatch, getState }) => {
|
||||
async ({ networkSymbol }: { networkSymbol: NetworkSymbol }, { dispatch, getState, extra }) => {
|
||||
const network = getNetworkOptional(networkSymbol.toLowerCase());
|
||||
if (!network) return;
|
||||
const blockchainInfo = selectNetworkBlockchainInfo(getState(), network.symbol);
|
||||
const device = selectSelectedDevice(getState());
|
||||
|
||||
const newFeeInfo = await getNewFeeInfo({ network, device });
|
||||
if (newFeeInfo === undefined) return;
|
||||
const device = extra.selectors.selectDevice(getState());
|
||||
|
||||
const partialFees: Partial<NetworksFees> = {};
|
||||
partialFees[network.symbol] = {
|
||||
blockHeight: blockchainInfo.blockHeight,
|
||||
...newFeeInfo,
|
||||
};
|
||||
dispatch(feesActions.updateFee(partialFees));
|
||||
let newFeeInfo;
|
||||
|
||||
if (network.networkType === 'ethereum') {
|
||||
const result = await TrezorConnect.blockchainEstimateFee({
|
||||
coin: network.symbol,
|
||||
request: {
|
||||
blocks: [2],
|
||||
feeLevels: 'smart',
|
||||
specific: {
|
||||
from: '0x0000000000000000000000000000000000000000',
|
||||
to: '0x0000000000000000000000000000000000000000',
|
||||
},
|
||||
},
|
||||
});
|
||||
if (result.success) {
|
||||
const feeLevelBase = result.payload.levels[0];
|
||||
const isEip1559ActivatedAndAvailable =
|
||||
getNetwork(network.symbol).features.includes('eip1559') &&
|
||||
isEip1559(feeLevelBase) &&
|
||||
!device?.unavailableCapabilities?.['eip1559'] &&
|
||||
!isNative(); // suite-native does not have eip1559 implementation yet #16372
|
||||
|
||||
if (isEip1559ActivatedAndAvailable) {
|
||||
newFeeInfo = result.payload;
|
||||
} else {
|
||||
newFeeInfo = {
|
||||
...result.payload,
|
||||
levels: [
|
||||
{
|
||||
...feeLevelBase,
|
||||
baseFeePerGas: undefined,
|
||||
maxFeePerGas: undefined,
|
||||
maxPriorityFeePerGas: undefined,
|
||||
label: 'normal' as const,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const result = await TrezorConnect.blockchainEstimateFee({
|
||||
coin: network.symbol,
|
||||
request: {
|
||||
feeLevels: 'smart',
|
||||
},
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
newFeeInfo = {
|
||||
...result.payload,
|
||||
levels: sortLevels(
|
||||
result.payload.levels
|
||||
// hack to hide "low" fee option
|
||||
// (we do not want to change the connect API as it is a potentially breaking change)
|
||||
.filter(level => level.label !== 'low'),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (newFeeInfo) {
|
||||
const partial: Partial<NetworksFees> = {};
|
||||
partial[network.symbol] = {
|
||||
blockHeight: blockchainInfo.blockHeight,
|
||||
...newFeeInfo,
|
||||
};
|
||||
|
||||
dispatch(feesActions.updateFee(partial));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ export * from './discovery/discoveryThunks';
|
||||
export * from './discovery/discoverySelectors';
|
||||
export * from './discovery/discoveryRunningStateLocks';
|
||||
export * from './fees/feesActions';
|
||||
export * from './fees/feesConstants';
|
||||
export * from './fees/feesReducer';
|
||||
export * from './fees/feesThunks';
|
||||
export * from './fiat-rates/fiatRatesMiddleware';
|
||||
|
||||
@@ -50,11 +50,6 @@ const sendRaw = createAction(`${SEND_MODULE_PREFIX}/sendRaw`, (payload: boolean)
|
||||
|
||||
export const dispose = createAction(`${SEND_MODULE_PREFIX}/dispose`);
|
||||
|
||||
const setAreFeesLoading = createAction(
|
||||
`${SEND_MODULE_PREFIX}/setAreFeesLoading`,
|
||||
(payload: { areFeesLoading: boolean }) => ({ payload }),
|
||||
);
|
||||
|
||||
export const sendFormActions = {
|
||||
storeDraft,
|
||||
removeDraft,
|
||||
@@ -63,5 +58,4 @@ export const sendFormActions = {
|
||||
discardTransaction,
|
||||
sendRaw,
|
||||
dispose,
|
||||
setAreFeesLoading,
|
||||
};
|
||||
|
||||
@@ -21,7 +21,6 @@ export type SendState = {
|
||||
precomposedForm?: FormState; // Used to pass the form state to the review modal. Holds similar data as drafts, but drafts are not used in RBF form.
|
||||
signedTx?: BlockbookTransaction;
|
||||
serializedTx?: SerializedTx; // Hexadecimal representation of signed transaction (payload for TrezorConnect.pushTransaction).
|
||||
areFeesLoading: boolean; // visual indication of fees being loaded, independent of the fees state
|
||||
};
|
||||
|
||||
export const initialState: SendState = {
|
||||
@@ -29,7 +28,6 @@ export const initialState: SendState = {
|
||||
precomposedTx: undefined,
|
||||
serializedTx: undefined,
|
||||
signedTx: undefined,
|
||||
areFeesLoading: false,
|
||||
};
|
||||
|
||||
export type SendRootState = {
|
||||
@@ -94,8 +92,5 @@ export const prepareSendFormReducer = createReducerWithExtraDeps(initialState, (
|
||||
payload.forEach(account => {
|
||||
delete state.drafts[account.key];
|
||||
});
|
||||
})
|
||||
.addCase(sendFormActions.setAreFeesLoading, (state, { payload: { areFeesLoading } }) => {
|
||||
state.areFeesLoading = areFeesLoading;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,5 +73,3 @@ export const selectSendFormReviewButtonRequestsCount = (
|
||||
|
||||
return isCardano ? sendFormReviewRequest.length - 1 : sendFormReviewRequest.length;
|
||||
};
|
||||
|
||||
export const selectAreFeesLoading = (state: SendRootState) => state.wallet.send.areFeesLoading;
|
||||
|
||||
@@ -52,7 +52,6 @@ import {
|
||||
signRippleStellarSendFormTransactionThunk,
|
||||
} from './sendFormRippleStellarThunks';
|
||||
import {
|
||||
selectAreFeesLoading,
|
||||
selectSendFormDrafts,
|
||||
selectSendPrecomposedTx,
|
||||
selectSendSerializedTx,
|
||||
@@ -72,7 +71,6 @@ import { accountsActions } from '../accounts/accountsActions';
|
||||
import { selectAccountByKey } from '../accounts/accountsSelectors';
|
||||
import { syncAccountsWithBlockchainThunk } from '../blockchain/blockchainThunks';
|
||||
import { selectSelectedDevice } from '../device/deviceSelectors';
|
||||
import { updateFeeInfoThunk } from '../fees/feesThunks';
|
||||
import {
|
||||
selectAreSatsAmountUnit,
|
||||
selectBitcoinAmountUnit,
|
||||
@@ -608,22 +606,3 @@ export const enhancePrecomposedTransactionThunk = createThunk<
|
||||
return enhancedPrecomposedTransaction;
|
||||
},
|
||||
);
|
||||
|
||||
const FEE_UPDATE_DELAY_MILLISECONDS = 1000;
|
||||
/**
|
||||
* Delay updateFeeInfoThunk with an arbitrary timeout, because backend request is usually very quick.
|
||||
* This can be used to display loader for a bit longer, to visually draw users attention to the fees which are changing.
|
||||
*/
|
||||
export const delayedUpdateFeeInfoThunk = createThunk(
|
||||
`${SEND_MODULE_PREFIX}/delayedUpdateFeeInfoThunk`,
|
||||
async ({ networkSymbol }: { networkSymbol: NetworkSymbol }, { dispatch, getState }) => {
|
||||
if (selectAreFeesLoading(getState())) return;
|
||||
|
||||
dispatch(sendFormActions.setAreFeesLoading({ areFeesLoading: true }));
|
||||
await Promise.all([
|
||||
dispatch(updateFeeInfoThunk({ networkSymbol })),
|
||||
new Promise(resolve => setTimeout(resolve, FEE_UPDATE_DELAY_MILLISECONDS)),
|
||||
]);
|
||||
dispatch(sendFormActions.setAreFeesLoading({ areFeesLoading: false }));
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user