Skip to content

Commit 036f9fb

Browse files
committed
chore(suite): priority fees - work in progress
1 parent eb88a82 commit 036f9fb

File tree

28 files changed

+1098
-187
lines changed

28 files changed

+1098
-187
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# EIP-1559 - priority fees
2+
3+
More info in [notion](https://www.notion.so/satoshilabs/EIP-1559-Priority-fees-177dc5260606801fbc82e05e0db9cb3e)
4+
5+
- ✅ Ethereum Mainnet + Holesky + Sepolia
6+
- ✅ Arbuirum
7+
- ✅ Base
8+
- ⚠ (Supported by infura, not yet impmemented in suite) BNB mainnet (Base fee is 0)
9+
- ✅ Optimism
10+
- ✅ Polygon mainnet
11+
- ❌ Ethereum Classic (not supported)
12+
13+
# Blockbook
14+
15+
It is possible to send EIP-1559 transaction, although when we receive transaction data, eip1559 information is not present.
16+
17+
This has to be fixed on blockbook side. (Martin Böhm should be working on it right now)
18+
19+
This is a blocker for sending RBF and correctly displaying transaction fees data in transaction details.
20+
21+
# Passing eip1559 transaction to firmware
22+
23+
### In Firmware maximum fee is calculated:
24+
25+
https://github.com/trezor/trezor-firmware/blob/13c078f8af75135ac757423da3fc0c013186c32c/core/src/apps/ethereum/sign_tx_eip1559.py#L65
26+
27+
`maximum_fee = format_ethereum_amount(max_gas_fee * gas_limit, None, defs.network)`
28+
29+
Since we can’t provide baseFeePerGas to firmware, we should calculate **_effective gas price_** on the suite side and pass the selected price to maximum_fee. Priority fee is being passed just for the purpose of displaying it, in fact it's being already included into effective gas price.
30+
31+
### Misc
32+
33+
I have some thought processes documented [here](https://excalidraw.com/#json=0l28PPN7iSp-ekzch1igV,iQnn7yW7gpQ-6mWcK1nUAw), although it's not complete, it can help you to understand how I reasoned about the forms. Which is the most challenging part about implementing this feature.
34+
35+
While testing, I recommend having a list of each part of the app where you have the fees component and after you test it document the send transaction.
36+
37+
### Staking
38+
39+
#### Stake
40+
41+
#### Unstake/Claim
42+
43+
### Send
44+
45+
### Swap
46+
47+
#### Approval tx for tokens on DEX
48+
49+
#### The swap tx on DEX
50+
51+
#### CEX
52+
53+
### Sell
54+
55+
Each of the above scenarios has to be tested with:
56+
57+
- legacy fees with backend disconnected/returning only normal fee level (standard and custom)
58+
- legacy fees when the network feature is turned off
59+
- priority fees with a standard fee level
60+
- priority fees with custom fee level

packages/suite/src/actions/wallet/stake/stakeFormActions.ts

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export const calculate = (
8181
max,
8282
fee: feeInBaseUnits,
8383
feePerByte: feeLevel.feePerUnit,
84+
maxFeePerGas: feeLevel.effectiveGasPrice || undefined,
85+
maxPriorityFeePerGas: feeLevel.maxPriorityFeePerGas || undefined,
8486
feeLimit: feeLevel.feeLimit,
8587
bytes: 0,
8688
inputs: [],

packages/suite/src/actions/wallet/stake/stakeFormEthereumActions.ts

+23-4
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import {
99
UNSTAKE_INTERCHANGES,
1010
} from '@suite-common/wallet-constants';
1111
import { ComposeActionContext, selectSelectedDevice } from '@suite-common/wallet-core';
12+
import { calculateEffectiveGasPrice } from '@suite-common/wallet-core/src/send/sendFormEthereumUtils';
1213
import {
1314
AddressDisplayOptions,
1415
ExternalOutput,
1516
PrecomposedTransaction,
1617
PrecomposedTransactionFinal,
1718
StakeFormState,
1819
} from '@suite-common/wallet-types';
19-
import { calculateEthFee, getAccountIdentity, isPending } from '@suite-common/wallet-utils';
20+
import { calculateMaxEthFee, getAccountIdentity, isPending } from '@suite-common/wallet-utils';
2021
import TrezorConnect, { FeeLevel } from '@trezor/connect';
2122
import { BigNumber } from '@trezor/utils/src/bigNumber';
2223

@@ -31,14 +32,18 @@ import {
3132

3233
import { calculate, composeStakingTransaction } from './stakeFormActions';
3334

34-
const calculateTransaction = (
35+
const calculateStakingTransaction = (
3536
availableBalance: string,
3637
output: ExternalOutput,
3738
feeLevel: FeeLevel,
3839
compareWithAmount = true,
3940
symbol: NetworkSymbol,
4041
): PrecomposedTransaction => {
41-
const feeInWei = calculateEthFee(toWei(feeLevel.feePerUnit, 'gwei'), feeLevel.feeLimit || '0');
42+
const isEip1559 = feeLevel.maxPriorityFeePerGas !== undefined;
43+
44+
const feeInWei = isEip1559
45+
? calculateMaxEthFee(feeLevel.effectiveGasPrice, feeLevel.feeLimit)
46+
: calculateMaxEthFee(toWei(feeLevel.feePerUnit, 'gwei'), feeLevel.feeLimit);
4247

4348
const stakingParams = {
4449
feeInBaseUnits: feeInWei,
@@ -82,10 +87,18 @@ export const composeTransaction =
8287
}
8388
// in case when selectedFee is set to 'custom' construct this FeeLevel from values
8489
if (formValues.selectedFee === 'custom') {
90+
const calculatedEffectiveGasPrice = calculateEffectiveGasPrice({
91+
maxFeePerGasGwei: formValues.customMaxBaseFeePerGas || '0',
92+
maxPriorityFeePerGasGwei: formValues.customMaxPriorityFeePerGas,
93+
});
8594
predefinedLevels.push({
8695
label: 'custom',
8796
feePerUnit: formValues.feePerUnit,
8897
feeLimit: formValues.feeLimit,
98+
customMaxBaseFeePerGas: formValues.customMaxBaseFeePerGas,
99+
customMaxPriorityFeePerGas: formValues.customMaxPriorityFeePerGas || '0',
100+
effectiveGasPrice: calculatedEffectiveGasPrice,
101+
maxPriorityFeePerGas: toWei(Number(formValues.customMaxPriorityFeePerGas), 'gwei'),
89102
blocks: -1,
90103
});
91104
}
@@ -94,7 +107,7 @@ export const composeTransaction =
94107
formValues,
95108
formState,
96109
predefinedLevels,
97-
calculateTransaction,
110+
calculateStakingTransaction,
98111
undefined,
99112
customFeeLimit,
100113
);
@@ -150,6 +163,8 @@ export const signTransaction =
150163
identity,
151164
amount: formValues.outputs[0].amount,
152165
gasPrice: transactionInfo.feePerByte,
166+
maxFeePerGas: transactionInfo.maxFeePerGas,
167+
maxPriorityFeePerGas: transactionInfo.maxPriorityFeePerGas,
153168
nonce,
154169
chainId: network.chainId,
155170
});
@@ -161,6 +176,8 @@ export const signTransaction =
161176
identity,
162177
amount: formValues.outputs[0].amount,
163178
gasPrice: transactionInfo.feePerByte,
179+
maxFeePerGas: transactionInfo.maxFeePerGas,
180+
maxPriorityFeePerGas: transactionInfo.maxPriorityFeePerGas,
164181
nonce,
165182
chainId: network.chainId,
166183
interchanges: UNSTAKE_INTERCHANGES,
@@ -172,6 +189,8 @@ export const signTransaction =
172189
from: account.descriptor,
173190
identity,
174191
gasPrice: transactionInfo.feePerByte,
192+
maxFeePerGas: transactionInfo.maxFeePerGas,
193+
maxPriorityFeePerGas: transactionInfo.maxPriorityFeePerGas,
175194
nonce,
176195
chainId: network.chainId,
177196
});

packages/suite/src/components/suite/modals/ReduxModal/TransactionReviewModal/TransactionReviewSummary.tsx

+18-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { formatDurationStrict } from '@suite-common/suite-utils';
2-
import { NetworkType, networks } from '@suite-common/wallet-config';
2+
import { NetworkType, getNetworkFeatures, networks } from '@suite-common/wallet-config';
33
import { FeeInfo, GeneralPrecomposedTransactionFinal, StakeType } from '@suite-common/wallet-types';
44
import { getFee } from '@suite-common/wallet-utils';
55
import { Box, IconButton, Note, Row, Text } from '@trezor/components';
@@ -48,13 +48,20 @@ export const TransactionReviewSummary = ({
4848
const locale = useLocales();
4949
const { symbol, accountType, index, networkType } = account;
5050
const network = networks[symbol];
51-
const fee = getFee(networkType, tx);
51+
52+
const baseFee = fees[symbol].levels[0].baseFeePerGas;
53+
const hasEip1559Feature = getNetworkFeatures(symbol).includes('eip1559');
54+
const shouldUsePriorityFees = !!tx.fee && hasEip1559Feature && !!baseFee;
55+
const fee = getFee({ account, tx, shouldUsePriorityFees });
56+
5257
const estimateTime = getEstimatedTime(networkType, fees[account.symbol], tx);
5358

5459
const formFeeRate = drafts[currentAccountKey]?.feePerUnit;
5560
const isFeeCustom = drafts[currentAccountKey]?.selectedFee === 'custom';
5661
const isComposedFeeRateDifferent = isFeeCustom && formFeeRate !== fee;
5762

63+
const isEthereumNetworkType = networkType === 'ethereum';
64+
5865
return (
5966
<Row columnGap={spacings.md} rowGap={spacings.xxs} flexWrap="wrap">
6067
<Row gap={spacings.xxs}>
@@ -82,17 +89,15 @@ export const TransactionReviewSummary = ({
8289
</Note>
8390
)}
8491

85-
{networkType === 'ethereum' ? (
86-
<Note iconName="gasPump">
87-
<Translation id="TR_GAS_PRICE" />
88-
{': '}
89-
<FeeRate feeRate={fee} networkType={network.networkType} symbol={symbol} />
90-
</Note>
91-
) : (
92-
<Note iconName="receipt">
93-
<FeeRate feeRate={fee} networkType={network.networkType} symbol={symbol} />
94-
</Note>
95-
)}
92+
<Note iconName={isEthereumNetworkType ? 'gasPump' : 'receipt'}>
93+
{isEthereumNetworkType && (
94+
<>
95+
<Translation id="TR_GAS_PRICE" />
96+
{': '}
97+
</>
98+
)}
99+
<FeeRate feeRate={fee} networkType={network.networkType} symbol={symbol} />
100+
</Note>
96101

97102
{isComposedFeeRateDifferent && network.networkType === 'bitcoin' && (
98103
<Translation id="TR_FEE_RATE_CHANGED" />

packages/suite/src/hooks/wallet/form/useCompose.ts

+33-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { useCallback, useEffect, useRef, useState } from 'react';
22
import { FieldPath, UseFormReturn } from 'react-hook-form';
33

44
import { isFulfilled } from '@reduxjs/toolkit';
5+
import { fromWei } from 'web3-utils';
56

67
import { COMPOSE_ERROR_TYPES } from '@suite-common/wallet-constants';
78
import {
89
ComposeActionContext,
910
composeSendFormTransactionFeeLevelsThunk,
1011
} from '@suite-common/wallet-core';
12+
import { calculateBaseFeeFromEffectiveGasPrice } from '@suite-common/wallet-core/src/send/sendFormEthereumUtils';
1113
import {
1214
FormState,
1315
PrecomposedLevels,
@@ -170,7 +172,17 @@ export const useCompose = <TFieldValues extends FormState>({
170172
const prevLevel = composedLevels[prev || 'normal'];
171173
const levels = {
172174
...composedLevels,
173-
custom: prevLevel,
175+
custom: {
176+
...prevLevel,
177+
maxFeePerGas:
178+
'effectiveGasPrice' in prevLevel
179+
? prevLevel.effectiveGasPrice
180+
: undefined,
181+
maxPriorityFeePerGas:
182+
'maxPriorityFeePerGas' in prevLevel
183+
? prevLevel.maxPriorityFeePerGas
184+
: undefined,
185+
},
174186
} as
175187
| (PrecomposedLevels & { custom: PrecomposedTransaction })
176188
| (PrecomposedLevelsCardano & { custom: PrecomposedTransactionCardano });
@@ -204,9 +216,28 @@ export const useCompose = <TFieldValues extends FormState>({
204216
setValue('selectedFee', nearest);
205217
if (nearest === 'custom') {
206218
// @ts-expect-error: type = error already filtered above
207-
const { feePerByte, feeLimit } = composed;
219+
const { feePerByte, feeLimit, effectiveGasPrice, maxPriorityFeePerGas } =
220+
composed;
208221
setValue('feePerUnit', feePerByte);
209222
setValue('feeLimit', feeLimit || '');
223+
setValue('maxFeePerGas', effectiveGasPrice || '');
224+
setValue('maxPriorityFeePerGas', maxPriorityFeePerGas || '');
225+
setValue(
226+
'customMaxBaseFeePerGas',
227+
fromWei(
228+
Number(
229+
calculateBaseFeeFromEffectiveGasPrice({
230+
effectiveGasPriceWei: effectiveGasPrice || '0',
231+
maxPriorityFeePerGasWei: maxPriorityFeePerGas || '0',
232+
}),
233+
),
234+
'gwei',
235+
),
236+
);
237+
setValue(
238+
'customMaxPriorityFeePerGas',
239+
fromWei(maxPriorityFeePerGas || '0', 'gwei'),
240+
);
210241
}
211242
}
212243
// or do nothing, use default composed tx

0 commit comments

Comments
 (0)