Skip to content

Commit

Permalink
feat(connect): change feeLevels based on specific ethereum network
Browse files Browse the repository at this point in the history
  • Loading branch information
adderpositive committed Dec 20, 2024
1 parent 2de1c84 commit 4a4c13a
Show file tree
Hide file tree
Showing 22 changed files with 207 additions and 94 deletions.
36 changes: 22 additions & 14 deletions packages/connect/src/data/defaultFeeLevels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,28 @@ export const getBitcoinFeeLevels = (coin: CoinsJsonData): FeeInfoWithLevels => {
};
};

export const getEthereumFeeLevels = (): FeeInfoWithLevels => ({
blockTime: -1, // unknown
defaultFees: [
{
label: 'normal' as const,
feePerUnit: '5000000000',
feeLimit: '21000', // unlike the other networks ethereum have additional value "feeLimit" (Gas limit)
blocks: -1, // unknown
},
],
minFee: 1,
maxFee: 10000,
dustLimit: -1, // unknown/unused
});
export const getEthereumFeeLevels = (chain: string): FeeInfoWithLevels => {
const DEFAULT_FEE_PER_UNIT = 5;

const GWeiChains = ['eth', 'pol', 'bsc'];
const ONE_GWEI = 1000000000;
const multiplier = GWeiChains.includes(chain) ? ONE_GWEI : 1; // 1 gwei or 1 wei

return {
blockTime: -1, // unknown
defaultFees: [
{
label: 'normal' as const,
feePerUnit: (DEFAULT_FEE_PER_UNIT * multiplier).toString(),
feeLimit: '21000', // unlike the other networks ethereum have additional value "feeLimit" (Gas limit)
blocks: -1, // unknown
},
],
minFee: 1 * multiplier,
maxFee: 10000 * ONE_GWEI,
dustLimit: -1, // unknown/unused
};
};

const RIPPLE_FEE_INFO: FeeInfoWithLevels = {
blockTime: -1, // unknown
Expand Down
5 changes: 5 additions & 0 deletions packages/suite/src/actions/wallet/send/sendFormThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import { isCardanoTx, isRbfTransaction } from '@suite-common/wallet-utils';
import { MetadataAddPayload } from '@suite-common/metadata-types';
import { getSynchronize } from '@trezor/utils';
import { NetworkSymbol } from '@suite-common/wallet-config';

import {
selectSelectedAccountKey,
Expand Down Expand Up @@ -98,10 +99,12 @@ const updateRbfLabelsThunk = createThunk(
labelsToBeEdited,
precomposedTransaction,
txid,
symbol,
}: {
labelsToBeEdited: RbfLabelsToBeUpdated;
precomposedTransaction: PrecomposedTransactionFinalRbf;
txid: string;
symbol: NetworkSymbol;
},
{ dispatch },
) => {
Expand All @@ -119,6 +122,7 @@ const updateRbfLabelsThunk = createThunk(
replaceTransactionThunk({
precomposedTransaction,
newTxid: txid,
symbol,
}),
);
},
Expand Down Expand Up @@ -267,6 +271,7 @@ export const signAndPushSendFormTransactionThunk = createThunk(
labelsToBeEdited: rbfLabelsToBeEdited,
precomposedTransaction,
txid,
symbol: selectedAccount.symbol,
}),
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/suite/src/actions/wallet/stakeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const pushTransaction =
replaceTransactionThunk({
precomposedTransaction: precomposedTx,
newTxid: txid,
symbol: account.symbol,
}),
);
}
Expand Down
9 changes: 6 additions & 3 deletions packages/suite/src/components/wallet/Fees/CustomFee.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { BigNumber } from '@trezor/utils/src/bigNumber';
import { Note, Banner, variables, Grid, Column, useMediaQuery, Text } from '@trezor/components';
import { getInputState, getFeeUnits, isInteger } from '@suite-common/wallet-utils';
import { FeeInfo, FormState } from '@suite-common/wallet-types';
import { NetworkType } from '@suite-common/wallet-config';
import { NetworkSymbol, NetworkType } from '@suite-common/wallet-config';
import { spacings } from '@trezor/theme';
import { HELP_CENTER_TRANSACTION_FEES_URL } from '@trezor/urls';

Expand All @@ -29,6 +29,7 @@ const FEE_LIMIT = 'feeLimit';

interface CustomFeeProps<TFieldValues extends FormState> {
networkType: NetworkType;
symbol?: NetworkSymbol;
feeInfo: FeeInfo;
errors: FieldErrors<TFieldValues>;
register: UseFormRegister<TFieldValues>;
Expand All @@ -41,6 +42,7 @@ interface CustomFeeProps<TFieldValues extends FormState> {

export const CustomFee = <TFieldValues extends FormState>({
networkType,
symbol,
feeInfo,
register,
control,
Expand All @@ -58,7 +60,7 @@ export const CustomFee = <TFieldValues extends FormState>({
const { maxFee, minFee } = feeInfo;

const feePerUnitValue = getValues(FEE_PER_UNIT);
const feeUnits = getFeeUnits(networkType);
const feeUnits = getFeeUnits(networkType, symbol);
const estimatedFeeLimit = getValues('estimatedFeeLimit');

const feePerUnitError = errors.feePerUnit;
Expand Down Expand Up @@ -105,6 +107,7 @@ export const CustomFee = <TFieldValues extends FormState>({
},
},
};
const GWeiNetworkSymbols: NetworkSymbol[] = ['eth', 'pol', 'bsc'];
const feeRules = {
...sharedRules,
validate: {
Expand All @@ -115,7 +118,7 @@ export const CustomFee = <TFieldValues extends FormState>({
}),
// GWEI: 9 decimal places.
ethereumDecimalsLimit: validateDecimals(translationString, {
decimals: 9,
decimals: !symbol || GWeiNetworkSymbols.includes(symbol) ? 9 : 0,
except: networkType !== 'ethereum',
}),
range: (value: string) => {
Expand Down
7 changes: 5 additions & 2 deletions packages/suite/src/components/wallet/Fees/FeeDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Row, Text } from '@trezor/components';
import { FeeLevel } from '@trezor/connect';
import { getFeeUnits } from '@suite-common/wallet-utils';
import { formatDuration } from '@suite-common/suite-utils';
import { NetworkType } from '@suite-common/wallet-config';
import { NetworkSymbol, NetworkType } from '@suite-common/wallet-config';
import {
PrecomposedTransaction,
PrecomposedTransactionCardano,
Expand All @@ -16,6 +16,8 @@ import { Translation } from 'src/components/suite/Translation';

type DetailsProps = {
networkType: NetworkType;
// include symbol if ethereum networkType to decide unit of a computed fee
symbol?: NetworkSymbol;
selectedLevel: FeeLevel;
// fields below are validated as false-positives, eslint claims that they are not used...

Expand Down Expand Up @@ -69,6 +71,7 @@ const EthereumDetails = ({
selectedLevel,
transactionInfo,
showFee,
symbol,
}: DetailsProps) => {
// States to remember the last known values of feeLimit and feePerByte when isComposedTx was true.
const [lastKnownFeeLimit, setLastKnownFeeLimit] = useState('');
Expand Down Expand Up @@ -96,7 +99,7 @@ const EthereumDetails = ({
<Item label={<Translation id="TR_GAS_LIMIT" />}>{gasLimit}</Item>

<Item label={<Translation id="TR_GAS_PRICE" />}>
{gasPrice} {getFeeUnits(networkType)}
{gasPrice} {getFeeUnits(networkType, symbol)}
</Item>
</>
)
Expand Down
2 changes: 2 additions & 0 deletions packages/suite/src/components/wallet/Fees/Fees.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export const Fees = <TFieldValues extends FormState>({
selectedLevel={selectedLevel}
transactionInfo={transactionInfo}
showFee={showNormalFee}
symbol={symbol}
/>
</motion.div>
)}
Expand All @@ -185,6 +186,7 @@ export const Fees = <TFieldValues extends FormState>({
<CustomFee
control={control}
networkType={networkType}
symbol={symbol}
feeInfo={feeInfo}
errors={errors}
register={register}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { UseFormReturn } from 'react-hook-form';
import { COMPOSE_ERROR_TYPES } from '@suite-common/wallet-constants';
import { selectAccounts, selectSelectedDevice } from '@suite-common/wallet-core';
import { AddressDisplayOptions } from '@suite-common/wallet-types';
import { getFeeLevels } from '@suite-common/wallet-utils';
import { getFeeInfo } from '@suite-common/wallet-utils';

import { saveComposedTransactionInfo } from 'src/actions/wallet/coinmarket/coinmarketCommonActions';
import { FORM_OUTPUT_ADDRESS, FORM_OUTPUT_AMOUNT } from 'src/constants/wallet/coinmarket/form';
Expand Down Expand Up @@ -41,9 +41,15 @@ export const useCoinmarketComposeTransaction = <T extends CoinmarketSellExchange
>;
const chunkify = addressDisplayType === AddressDisplayOptions.CHUNKED;
const { symbol, networkType } = account;
const coinFees = fees[symbol];
const levels = getFeeLevels(networkType, coinFees);
const feeInfo = useMemo(() => ({ ...coinFees, levels }), [coinFees, levels]);
const feeInfo = useMemo(
() =>
getFeeInfo({
networkType,
symbol,
feeInfo: fees[symbol],
}),
[networkType, symbol, fees],
);
const initState = useMemo(() => ({ account, network, feeInfo }), [account, network, feeInfo]);
const outputAddress = values?.outputs?.[0].address;
const [state, setState] = useState<CoinmarketUseComposeTransactionStateProps>(initState);
Expand Down
17 changes: 11 additions & 6 deletions packages/suite/src/hooks/wallet/useClaimEthForm.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createContext, useCallback, useContext, useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';

import { getFeeLevels } from '@suite-common/wallet-utils';
import { getFeeInfo } from '@suite-common/wallet-utils';
import { PrecomposedTransactionFinal } from '@suite-common/wallet-types';

import { useDispatch, useSelector } from 'src/hooks/suite';
Expand Down Expand Up @@ -40,8 +40,11 @@ export const useClaimEthForm = ({ selectedAccount }: UseStakeFormsProps): ClaimC
}, [account.symbol]);

const state = useMemo(() => {
const levels = getFeeLevels(account.networkType, symbolFees);
const feeInfo = { ...symbolFees, levels };
const feeInfo = getFeeInfo({
networkType: account.networkType,
symbol: account.symbol,
feeInfo: symbolFees,
});

return {
account,
Expand Down Expand Up @@ -101,9 +104,11 @@ export const useClaimEthForm = ({ selectedAccount }: UseStakeFormsProps): ClaimC

// sub-hook, FeeLevels handler
const fees = useSelector(state => state.wallet.fees);
const coinFees = fees[account.symbol];
const levels = getFeeLevels(account.networkType, coinFees);
const feeInfo = { ...coinFees, levels };
const feeInfo = getFeeInfo({
networkType: account.networkType,
symbol: account.symbol,
feeInfo: fees[account.symbol],
});
const { changeFeeLevel, selectedFee: _selectedFee } = useFees({
defaultValue: 'normal',
feeInfo,
Expand Down
10 changes: 6 additions & 4 deletions packages/suite/src/hooks/wallet/useCoinmarketRecomposeAndSign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCallback } from 'react';
import { notificationsActions } from '@suite-common/toast-notifications';
import { DEFAULT_VALUES, DEFAULT_PAYMENT } from '@suite-common/wallet-constants';
import { FormState } from '@suite-common/wallet-types';
import { getFeeLevels } from '@suite-common/wallet-utils';
import { getFeeInfo } from '@suite-common/wallet-utils';
import { networks } from '@suite-common/wallet-config';
import type { Account, FormOptions } from '@suite-common/wallet-types';
import { composeSendFormTransactionFeeLevelsThunk } from '@suite-common/wallet-core';
Expand Down Expand Up @@ -80,9 +80,11 @@ export const useCoinmarketRecomposeAndSign = () => {
};

// prepare form state for composeAction
const coinFees = fees[account.symbol];
const levels = getFeeLevels(account.networkType, coinFees);
const feeInfo = { ...coinFees, levels };
const feeInfo = getFeeInfo({
networkType: account.networkType,
symbol: account.symbol,
feeInfo: fees[account.symbol],
});
const composeContext = { account, network, feeInfo };

// recalcCustomLimit is used in case of custom fee level, when we want to keep the feePerUnit defined by the user
Expand Down
38 changes: 23 additions & 15 deletions packages/suite/src/hooks/wallet/useRbfForm.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { createContext, useContext, useState, useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';

import { fromWei } from 'web3-utils';

import { BigNumber } from '@trezor/utils/src/bigNumber';
import { DEFAULT_PAYMENT, DEFAULT_OPRETURN, DEFAULT_VALUES } from '@suite-common/wallet-constants';
import { getFeeLevels } from '@suite-common/wallet-utils';
import { getFeeInfo } from '@suite-common/wallet-utils';
import {
SelectedAccountLoaded,
Account,
RbfTransactionParams,
ChainedTransactions,
FormState,
FeeInfo,
} from '@suite-common/wallet-types';
import type { NetworkSymbol, NetworkType } from '@suite-common/wallet-config';

import { useSelector } from 'src/hooks/suite';
import { selectCurrentTargetAnonymity } from 'src/reducers/wallet/coinjoinReducer';
Expand All @@ -31,21 +29,30 @@ export type UseRbfProps = {

const getBitcoinFeeInfo = (info: FeeInfo, feeRate: string) => {
// increase FeeLevels (old rate + defined rate)
const levels = getFeeLevels('bitcoin', info).map(l => ({
const feeInfo = getFeeInfo({
networkType: 'bitcoin',
feeInfo: info,
});
const levels = feeInfo.levels.map(l => ({
...l,
feePerUnit: new BigNumber(l.feePerUnit).plus(feeRate).toString(),
}));

return {
...info,
...feeInfo,
levels,
minFee: new BigNumber(feeRate).plus(info.minFee).toNumber(), // increase required minFee rate
minFee: new BigNumber(feeRate).plus(feeInfo.minFee).toNumber(), // increase required minFee rate
};
};

const getEthereumFeeInfo = (info: FeeInfo, gasPrice: string) => {
const getEthereumFeeInfo = (info: FeeInfo, gasPrice: string, symbol: NetworkSymbol) => {
const current = new BigNumber(gasPrice);
const minFeeFromNetwork = new BigNumber(fromWei(info.levels[0].feePerUnit, 'gwei'));
const feeInfo = getFeeInfo({
networkType: 'ethereum',
symbol,
feeInfo: info,
});
const minFeeFromNetwork = new BigNumber(feeInfo.minFee);

const getFee = () => {
if (minFeeFromNetwork.lte(current.plus(1))) {
Expand All @@ -58,25 +65,26 @@ const getEthereumFeeInfo = (info: FeeInfo, gasPrice: string) => {
const fee = getFee();

// increase FeeLevel only if it's lower than predefined
const levels = getFeeLevels('ethereum', info).map(l => ({
const levels = feeInfo.levels.map(l => ({
...l,
feePerUnit: fee.toString(),
}));

return {
...info,
...feeInfo,
levels,
minFee: current.plus(1).toNumber(), // increase required minFee rate
};
};

const getFeeInfo = (
networkType: Account['networkType'],
const getRbfFeeInfo = (
networkType: NetworkType,
info: FeeInfo,
rbfParams: RbfTransactionParams,
symbol: NetworkSymbol,
) => {
if (networkType === 'bitcoin') return getBitcoinFeeInfo(info, rbfParams.feeRate);
if (networkType === 'ethereum') return getEthereumFeeInfo(info, rbfParams.feeRate);
if (networkType === 'ethereum') return getEthereumFeeInfo(info, rbfParams.feeRate, symbol);

return info;
};
Expand All @@ -91,7 +99,7 @@ const useRbfState = ({ selectedAccount, rbfParams, chainedTxs }: UseRbfProps) =>
const { shouldSendInSats } = useBitcoinAmountUnit(account.symbol);

return useMemo(() => {
const feeInfo = getFeeInfo(account.networkType, symbolFees, rbfParams);
const feeInfo = getRbfFeeInfo(account.networkType, symbolFees, rbfParams, account.symbol);
// filter out utxos generated by this transaction
const otherUtxo = (account.utxo || []).filter(input => input.txid !== rbfParams.txid);
// filter out utxos with anonymity level below target and currently registered
Expand Down
Loading

0 comments on commit 4a4c13a

Please sign in to comment.