@exodus/ethereum-api 8.34.2 → 8.34.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,71 +1,140 @@
1
- import BN from 'bn.js'
1
+ import { memoize } from '@exodus/basic-utils'
2
2
 
3
- import { getServer } from '../../exodus-eth-server/index.js'
4
- import { estimateGasLimit } from '../../gas-estimation.js'
3
+ import { estimateGasLimit, scaleGasLimitEstimate } from '../../gas-estimation.js'
4
+ import { getFeeFactoryGasPrices } from '../../get-fee.js'
5
5
  import { createWatchTx as defaultCreateWatch } from '../../watch-tx.js'
6
6
  import { stakingProviderClientFactory } from '../staking-provider-client.js'
7
- import { amountToCurrency, getEvmStakingServiceFee } from '../utils/index.js'
7
+ import {
8
+ amountToCurrency,
9
+ DISABLE_BALANCE_CHECKS,
10
+ resolveFeeData as defaultResolveFeeData,
11
+ } from '../utils/index.js'
8
12
  import { MaticStakingApi } from './api.js'
9
13
 
10
- const DISABLE_BALANCE_CHECKS = '0x0'
11
- const MAX_PRIORITY_FEE_PER_GAS = '0.06 Gwei' // semi-urgent
14
+ const createStakingApiForFeeAsset = ({ feeAsset: { server } }) =>
15
+ new MaticStakingApi(undefined, undefined, server)
12
16
 
17
+ // TODO: This should be `createMaticStakingService` to avoid confusion with Polygon staking.
13
18
  export function createPolygonStakingService({
14
19
  assetClientInterface,
15
20
  createWatchTx = defaultCreateWatch,
21
+ stakingProvider = stakingProviderClientFactory(),
16
22
  }) {
17
- const stakingApi = new MaticStakingApi()
18
23
  const assetName = 'ethereum'
19
- const stakingProvider = stakingProviderClientFactory()
20
24
 
21
- async function getStakeAssets() {
22
- const { polygon: asset, ethereum: feeAsset } = await assetClientInterface.getAssetsForNetwork({
23
- baseAssetName: assetName,
24
- })
25
+ const getStakeAssets = memoize(async () => {
26
+ const { polygon: asset, [assetName]: feeAsset } =
27
+ await assetClientInterface.getAssetsForNetwork({
28
+ baseAssetName: assetName,
29
+ })
25
30
  return { asset, feeAsset }
31
+ })
32
+
33
+ const createStakingApi = memoize(async () => {
34
+ const { asset, feeAsset } = await getStakeAssets()
35
+ return { asset, feeAsset, stakingApi: createStakingApiForFeeAsset({ feeAsset }) }
36
+ })
37
+
38
+ // Helper function which selects the correct `asset` to use for when
39
+ // determining `feeData` for MATIC staking.
40
+ const resolveFeeData = async ({ feeData }) => {
41
+ const { feeAsset } = await getStakeAssets()
42
+ return defaultResolveFeeData({ asset: feeAsset, assetClientInterface, feeData })
43
+ }
44
+
45
+ const getLatestFeeData = async () => {
46
+ const { feeAsset } = await getStakeAssets()
47
+ return assetClientInterface.getFeeData({ assetName: feeAsset.name })
26
48
  }
27
49
 
28
- async function delegate({ walletAccount, amount } = {}) {
50
+ const resolveOptionalFeeData = async ({ feeData }) => {
51
+ // If the caller provides truthy `feeData`, we can continue
52
+ // as normal.
53
+ if (feeData) return feeData
54
+
55
+ // If the caller specifically omits `feeData`, then we
56
+ // provide a backup without warning. This is useful for
57
+ // calls with no expectations on the caller to provide
58
+ // `feeData`, i.e. one-shot transactions, or transactions
59
+ // which do not render a fee estimation.
60
+ return getLatestFeeData()
61
+ }
62
+
63
+ const getDelegatorAddress = async ({ walletAccount }) => {
29
64
  const address = await assetClientInterface.getReceiveAddress({
30
65
  assetName,
31
66
  walletAccount,
32
67
  })
33
- const delegatorAddress = address.toLowerCase()
68
+ return address.toLowerCase()
69
+ }
70
+
71
+ async function delegate({ walletAccount, amount, feeData, waitForConfirmation = true } = {}) {
72
+ const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
73
+ getDelegatorAddress({ walletAccount }),
74
+ createStakingApi(),
75
+ ])
76
+
77
+ // NOTE: We do not provide a silent fallback for `feeData` omission
78
+ // for a delegation transaction, since `feeData` is expected
79
+ // to have been provided during `estimateDelegateTxFee`.
80
+ feeData = await resolveFeeData({ feeData })
34
81
 
35
- const { asset } = await getStakeAssets()
36
82
  amount = amountToCurrency({ asset, amount })
37
83
 
38
84
  const txApproveData = await stakingApi.approveStakeManager(amount)
39
- let { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee(
40
- delegatorAddress,
41
- stakingApi.polygonContract.address,
42
- txApproveData
43
- )
85
+ let { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
86
+ from: delegatorAddress,
87
+ to: stakingApi.polygonContract.address,
88
+ txInput: txApproveData,
89
+ feeData,
90
+ })
44
91
  await prepareAndSendTx({
45
92
  walletAccount,
46
- waitForConfirmation: true,
93
+ waitForConfirmation,
47
94
  to: stakingApi.polygonContract.address,
48
95
  txData: txApproveData,
49
96
  gasPrice,
50
97
  gasLimit,
51
98
  tipGasPrice,
99
+ feeData,
52
100
  })
53
101
 
102
+ // NOTE: Whenever we `waitForConfirmation`, we must recalculate
103
+ // the `feeData` as this may have changed since our last
104
+ // transaction was mined.
105
+ //
106
+ // In most cases, this shouldn't affect previous estimates,
107
+ // since the amount we aren't trying to stake using the
108
+ // base currency; however for users with marginal balances,
109
+ // an increase in `gasPrice` could result in transaction
110
+ // failure.
111
+ //
112
+ // HACK: This will invaldiate the original estimate in calculated
113
+ // in `estimateDelegateTxFee`!
114
+ // TODO: Submit these transactions as an atomic bundle.
115
+ if (waitForConfirmation) {
116
+ feeData = await getLatestFeeData()
117
+ }
118
+
54
119
  const txDelegateData = await stakingApi.delegate({ amount })
55
120
 
56
- ;({ gasPrice, gasLimit, tipGasPrice } = await estimateTxFee(
57
- delegatorAddress,
58
- stakingApi.validatorShareContract.address,
59
- txDelegateData
60
- ))
121
+ ;({ gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
122
+ from: delegatorAddress,
123
+ to: stakingApi.validatorShareContract.address,
124
+ txInput: txDelegateData,
125
+ feeData,
126
+ }))
61
127
 
62
128
  const txId = await prepareAndSendTx({
63
129
  walletAccount,
130
+ // TOOD: Why shouldn't we wait for confirmation here? Is it because we want to `notifyStaking`?
131
+ waitForConfirmation: false,
64
132
  to: stakingApi.validatorShareContract.address,
65
133
  txData: txDelegateData,
66
134
  gasPrice,
67
135
  gasLimit,
68
136
  tipGasPrice,
137
+ feeData,
69
138
  })
70
139
 
71
140
  await stakingProvider.notifyStaking({
@@ -78,22 +147,24 @@ export function createPolygonStakingService({
78
147
  return txId
79
148
  }
80
149
 
81
- async function undelegate({ walletAccount, amount } = {}) {
82
- const address = await assetClientInterface.getReceiveAddress({
83
- assetName,
84
- walletAccount,
85
- })
86
- const delegatorAddress = address.toLowerCase()
150
+ async function undelegate({ walletAccount, amount, feeData, waitForConfirmation = false } = {}) {
151
+ feeData = await resolveOptionalFeeData({ feeData })
152
+
153
+ const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
154
+ getDelegatorAddress({ walletAccount }),
155
+ createStakingApi(),
156
+ ])
87
157
 
88
- const { asset } = await getStakeAssets()
89
158
  amount = amountToCurrency({ asset, amount })
90
159
 
91
160
  const txUndelegateData = await stakingApi.undelegate({ amount })
92
- const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee(
93
- delegatorAddress.toLowerCase(),
94
- stakingApi.validatorShareContract.address,
95
- txUndelegateData
96
- )
161
+ const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
162
+ from: delegatorAddress.toLowerCase(),
163
+ to: stakingApi.validatorShareContract.address,
164
+ txInput: txUndelegateData,
165
+ feeData,
166
+ })
167
+
97
168
  return prepareAndSendTx({
98
169
  walletAccount,
99
170
  to: stakingApi.validatorShareContract.address,
@@ -101,22 +172,26 @@ export function createPolygonStakingService({
101
172
  gasPrice,
102
173
  gasLimit,
103
174
  tipGasPrice,
175
+ feeData,
176
+ waitForConfirmation,
104
177
  })
105
178
  }
106
179
 
107
- async function claimRewards({ walletAccount } = {}) {
108
- const address = await assetClientInterface.getReceiveAddress({
109
- assetName,
110
- walletAccount,
111
- })
112
- const delegatorAddress = address.toLowerCase()
180
+ async function claimRewards({ walletAccount, feeData } = {}) {
181
+ feeData = await resolveOptionalFeeData({ feeData })
182
+
183
+ const [delegatorAddress, { stakingApi }] = await Promise.all([
184
+ getDelegatorAddress({ walletAccount }),
185
+ createStakingApi(),
186
+ ])
113
187
 
114
188
  const txWithdrawRewardsData = await stakingApi.withdrawRewards()
115
- const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee(
116
- delegatorAddress,
117
- stakingApi.validatorShareContract.address,
118
- txWithdrawRewardsData
119
- )
189
+ const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
190
+ from: delegatorAddress,
191
+ to: stakingApi.validatorShareContract.address,
192
+ txInput: txWithdrawRewardsData,
193
+ feeData,
194
+ })
120
195
  return prepareAndSendTx({
121
196
  walletAccount,
122
197
  to: stakingApi.validatorShareContract.address,
@@ -124,17 +199,18 @@ export function createPolygonStakingService({
124
199
  gasPrice,
125
200
  gasLimit,
126
201
  tipGasPrice,
202
+ feeData,
127
203
  })
128
204
  }
129
205
 
130
- async function claimUndelegatedBalance({ walletAccount, unbondNonce } = {}) {
131
- const address = await assetClientInterface.getReceiveAddress({
132
- assetName,
133
- walletAccount,
134
- })
135
- const delegatorAddress = address.toLowerCase()
206
+ async function claimUndelegatedBalance({ walletAccount, unbondNonce, feeData } = {}) {
207
+ feeData = await resolveOptionalFeeData({ feeData })
208
+
209
+ const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
210
+ getDelegatorAddress({ walletAccount }),
211
+ createStakingApi(),
212
+ ])
136
213
 
137
- const { asset } = await getStakeAssets()
138
214
  const { currency } = asset
139
215
  const unstakedClaimInfo = await fetchUnstakedClaimInfo({
140
216
  stakingApi,
@@ -149,11 +225,12 @@ export function createPolygonStakingService({
149
225
  })
150
226
 
151
227
  const txClaimUndelegatedData = await stakingApi.claimUndelegatedBalance({ unbondNonce })
152
- const { gasPrice, gasLimit, tipGasPrice, fee } = await estimateTxFee(
153
- delegatorAddress,
154
- stakingApi.validatorShareContract.address,
155
- txClaimUndelegatedData
156
- )
228
+ const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
229
+ from: delegatorAddress,
230
+ to: stakingApi.validatorShareContract.address,
231
+ txInput: txClaimUndelegatedData,
232
+ feeData,
233
+ })
157
234
  const txId = await prepareAndSendTx({
158
235
  walletAccount,
159
236
  to: stakingApi.validatorShareContract.address,
@@ -161,7 +238,7 @@ export function createPolygonStakingService({
161
238
  gasPrice,
162
239
  gasLimit,
163
240
  tipGasPrice,
164
- fee,
241
+ feeData,
165
242
  })
166
243
 
167
244
  await stakingProvider.notifyUnstaking({
@@ -174,31 +251,42 @@ export function createPolygonStakingService({
174
251
  return txId
175
252
  }
176
253
 
177
- async function estimateDelegateOperation({ walletAccount, operation, args }) {
178
- const delegateOperation = stakingApi[operation]
179
-
180
- if (!delegateOperation) {
181
- return
182
- }
254
+ async function estimateDelegateOperation({
255
+ walletAccount,
256
+ operation,
257
+ args,
258
+ // NOTE: When estimating transactions, ideally we'd expect the `feeData`
259
+ // that we intend to send the transaction using. If this is not
260
+ // defined, we'll fallback to a default (with a warning).
261
+ feeData,
262
+ }) {
263
+ // HACK: For delegation transactions, we must fall back to the
264
+ // custom implementation, since we can't currently estimate
265
+ // the transaction due to a dependence upon approvals.
266
+ if (operation === 'delegate') return estimateDelegateTxFee({ feeData })
267
+
268
+ const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
269
+ getDelegatorAddress({ walletAccount }),
270
+ createStakingApi(),
271
+ ])
272
+
273
+ feeData = await resolveFeeData({ feeData })
183
274
 
184
- const address = await assetClientInterface.getReceiveAddress({
185
- assetName,
186
- walletAccount,
187
- })
188
- const delegatorAddress = address.toLowerCase()
275
+ const delegateOperation = stakingApi[operation]
276
+ if (!delegateOperation) return
189
277
 
190
278
  const { amount } = args
191
279
  if (amount) {
192
- const { asset } = await getStakeAssets()
193
280
  args = { ...args, amount: amountToCurrency({ asset, amount }) }
194
281
  }
195
282
 
196
283
  const operationTxData = await delegateOperation({ ...args, walletAccount })
197
- const { fee } = await estimateTxFee(
198
- delegatorAddress,
199
- stakingApi.validatorShareContract.address,
200
- operationTxData
201
- )
284
+ const { fee } = await estimateTxFee({
285
+ from: delegatorAddress,
286
+ to: stakingApi.validatorShareContract.address,
287
+ txInput: operationTxData,
288
+ feeData,
289
+ })
202
290
 
203
291
  return fee
204
292
  }
@@ -213,23 +301,27 @@ export function createPolygonStakingService({
213
301
  * This is just for displaying purposes and it's just an aproximation of the delegate gas cost,
214
302
  * NOT the real fee cost
215
303
  */
216
- async function estimateDelegateTxFee() {
304
+ async function estimateDelegateTxFee({ feeData } = {}) {
217
305
  // approx gas limits
218
306
  const { ethereum } = await assetClientInterface.getAssetsForNetwork({
219
307
  baseAssetName: 'ethereum',
220
308
  })
221
- const erc20ApproveGas = 4900
222
- const delegateGas = 240_000
223
- const gasPrice = parseInt(await getServer(ethereum).gasPrice(), 16)
224
- const extraPercentage = 20
225
-
226
- const gasLimit = erc20ApproveGas + delegateGas
227
- const gasLimitWithBuffer = new BN(gasLimit)
228
- .imuln(100 + extraPercentage)
229
- .idivn(100)
230
- .toString()
231
309
 
232
- const fee = new BN(gasLimitWithBuffer).mul(new BN(gasPrice))
310
+ feeData = await resolveFeeData({ feeData })
311
+
312
+ // TODO: update estimation to use a mock source address for
313
+ // deposits so we can simulate the necessary approvals,
314
+ // we shouldn't maintain constants like these
315
+ const erc20ApproveGas = 80_000
316
+ const delegateGas = 250_000
317
+
318
+ const { gasPrice } = getFeeFactoryGasPrices({ feeData })
319
+
320
+ const gasLimitWithBuffer = scaleGasLimitEstimate({
321
+ estimatedGasLimit: BigInt(erc20ApproveGas + delegateGas),
322
+ })
323
+
324
+ const fee = BigInt(gasLimitWithBuffer) * BigInt(gasPrice.toBaseNumber())
233
325
 
234
326
  return {
235
327
  gasLimit: gasLimitWithBuffer,
@@ -238,7 +330,7 @@ export function createPolygonStakingService({
238
330
  }
239
331
  }
240
332
 
241
- async function estimateTxFee(from, to, txInput) {
333
+ async function estimateTxFee({ from, to, txInput, feeData }) {
242
334
  const { ethereum } = await assetClientInterface.getAssetsForNetwork({
243
335
  baseAssetName: 'ethereum',
244
336
  })
@@ -254,13 +346,7 @@ export function createPolygonStakingService({
254
346
  DISABLE_BALANCE_CHECKS
255
347
  )
256
348
 
257
- return getEvmStakingServiceFee({
258
- amount,
259
- asset: ethereum,
260
- assetClientInterface,
261
- maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS,
262
- gasLimit,
263
- })
349
+ return ethereum.api.getFee({ asset: ethereum, feeData, gasLimit, amount })
264
350
  }
265
351
 
266
352
  async function prepareAndSendTx({
@@ -271,6 +357,7 @@ export function createPolygonStakingService({
271
357
  gasLimit,
272
358
  tipGasPrice,
273
359
  waitForConfirmation = false,
360
+ feeData,
274
361
  } = {}) {
275
362
  const { ethereum: asset } = await assetClientInterface.getAssetsForNetwork({
276
363
  baseAssetName: 'ethereum',
@@ -288,6 +375,7 @@ export function createPolygonStakingService({
288
375
  tipGasPrice,
289
376
  },
290
377
  waitForConfirmation,
378
+ feeData,
291
379
  }
292
380
 
293
381
  const { txId } = await asset.baseAsset.api.sendTx(sendTxArgs)
@@ -409,9 +497,8 @@ async function fetchRewardsInfo({ stakingApi, delegator, currency }) {
409
497
  }
410
498
  }
411
499
 
412
- export async function getPolygonStakingInfo({ address, asset, server }) {
413
- const { currency } = asset
414
- const stakingApi = new MaticStakingApi(undefined, undefined, server)
500
+ export async function getPolygonStakingInfo({ address, asset: { currency, baseAsset: feeAsset } }) {
501
+ const stakingApi = createStakingApiForFeeAsset({ feeAsset })
415
502
  const delegator = address.toLowerCase()
416
503
  const [
417
504
  delegatedBalance,
@@ -1,73 +1,39 @@
1
1
  import { isNumberUnit } from '@exodus/currency'
2
2
 
3
+ export const DISABLE_BALANCE_CHECKS = '0x0'
4
+
3
5
  export function amountToCurrency({ asset, amount }) {
4
6
  return isNumberUnit(amount) ? amount : asset.currency.parse(amount)
5
7
  }
6
8
 
7
- // HACK: Empirically, we can observe that a `feeData` object uses
8
- // stringified values for gas i.e. `baseFeePerGas: "10 gwei"`,
9
- // however to hook into `asset.api.getFees()`, these values
10
- // must be expressed using currency objects.
11
- function maybeNormalizeFeeData({ asset, feeData }) {
12
- const { baseFeePerGas, tipGasPrice, serverGasPrice, gasPrice, ...extras } = feeData
13
-
14
- const maybeNormalizeAmount = (amount) =>
15
- typeof amount === 'string' ? amountToCurrency({ amount, asset }) : amount
16
-
17
- return {
18
- ...extras,
19
- gasPrice: maybeNormalizeAmount(gasPrice),
20
- baseFeePerGas: maybeNormalizeAmount(baseFeePerGas),
21
- tipGasPrice: maybeNormalizeAmount(tipGasPrice),
22
- serverGasPrice: maybeNormalizeAmount(serverGasPrice),
23
- }
24
- }
25
-
26
9
  /**
27
- * A common handler for the computation of the `fee`, `gasPrice`,
28
- * `gasLimit` and `tipGasPrice` or a staking call. Allows the caller
29
- * to specify a custom `maxPriorityFeePerGas` whilst the current
30
- * `tipGasPrice` is incompatible with EIP-1559 pricing.
10
+ * Previously, EVM staking services would determine `feeData`
11
+ * dynamically on behalf of callers, however, this risks invalidating
12
+ * fee estimation over the course of a sequence of multiple staking
13
+ * transactions.
14
+ *
15
+ * For example, when determining the amount for a `sendAll`
16
+ * transaction, if the gas price were to increase between estimation
17
+ * and execution, this would invalidate the previous estimation.
18
+ *
19
+ * In future usage, we would want assets code to fetch FeeData from ACI,
20
+ * construct a transaction including determining the fee and then report
21
+ * back to the client what the fee is going to be.
22
+ *
23
+ * Importantly, the provided `feeData` must be sufficiently
24
+ * up-to-date; else, we risk the transactions not being included at all.
31
25
  */
32
- export async function getEvmStakingServiceFee({
33
- amount,
26
+ export const resolveFeeData = async ({
27
+ // TODO: maybe asset name would be better
34
28
  asset,
35
29
  assetClientInterface,
36
- maxPriorityFeePerGas: maybeMaxPriorityFeePerGas,
37
- gasLimit,
38
- }) {
39
- const defaultFeeData = await assetClientInterface.getFeeData({
40
- assetName: asset.name,
41
- })
42
-
43
- // HACK: We wish to explicitly enable `useBaseGasPrice`
44
- // since we're using a custom `tipGasPrice`. This
45
- // will be compatible during the transition from
46
- // Magnifier to Clarity, and can be safely removed
47
- // once `useBaseGasPrice` is the default behaviour
48
- // (alongside the custom tip).
49
- const { eip1559Enabled } = defaultFeeData
50
- const useBaseGasPrice = Boolean(eip1559Enabled) && Boolean(maybeMaxPriorityFeePerGas)
30
+ feeData,
31
+ }) => {
32
+ if (feeData) return feeData
51
33
 
52
- const normalizedFeeData = maybeNormalizeFeeData({
53
- asset,
54
- feeData: {
55
- ...defaultFeeData,
56
- // HACK: The backend currently exports a very large `tipGasPrice` that is
57
- // compatible with Magnifier's legacy `gasPrice`, but it would
58
- // be incompatible with EIP-1559, since this would evaluate into
59
- // a very large `maxPriorityFeePerGas`.
60
- ...(useBaseGasPrice ? { tipGasPrice: maybeMaxPriorityFeePerGas } : null),
61
- useBaseGasPrice,
62
- },
63
- })
34
+ console.warn(
35
+ 'The evm staking service was not explicitly passed `feeData`. This can result in transaction nondeterminism.'
36
+ )
64
37
 
65
- // Returns `fee`, `gasPrice`, `extraFeeData`, and `tipGasPrice`
66
- // if the config defines we should `useBaseGasPrice`.
67
- return asset.api.getFee({
68
- asset,
69
- feeData: normalizedFeeData,
70
- gasLimit,
71
- amount,
72
- })
38
+ return assetClientInterface.getFeeData({ assetName: asset.name })
73
39
  }
@@ -1,4 +1,6 @@
1
+ import { ensureSaneEip1559GasPriceForTipGasPrice } from '../fee-utils.js'
1
2
  import { fetchGasLimit } from '../gas-estimation.js'
3
+ import { getFeeFactoryGasPrices } from '../get-fee.js'
2
4
 
3
5
  const getFeeInfo = async function getFeeInfo({
4
6
  assetClientInterface,
@@ -8,25 +10,48 @@ const getFeeInfo = async function getFeeInfo({
8
10
  amount,
9
11
  txInput,
10
12
  feeOpts = {},
13
+ feeData,
14
+ customFee,
11
15
  }) {
12
- const { gasPrice: gasPrice_, tipGasPrice: tipGasPrice_ } = await assetClientInterface.getFeeData({
13
- assetName: asset.name,
14
- })
16
+ // HACK: Previously, calls `getFeeInfo` were not provided a reference
17
+ // to `feeData`. For backwards compatibility, we'll revert to
18
+ // legacy behaviour.
19
+ // NOTE: This shouldn't actually be used outside of the `assets` repo;
20
+ // this is done just for safety.
21
+ if (!feeData) {
22
+ console.warn('`getFeeInfo` was not explicitly passed a `feeData` object.')
23
+ const { name: assetName } = asset
24
+ feeData = await assetClientInterface.getFeeData({ assetName })
25
+ }
26
+
27
+ const {
28
+ gasPrice: gasPrice_,
29
+ feeData: { tipGasPrice: tipGasPrice_, eip1559Enabled },
30
+ } = getFeeFactoryGasPrices({ customFee, feeData })
31
+
32
+ const tipGasPrice = feeOpts.tipGasPrice || tipGasPrice_
15
33
 
16
- let { gasLimit, gasPrice = gasPrice_, tipGasPrice = tipGasPrice_ } = feeOpts
34
+ const maybeGasPrice = feeOpts.gasPrice || gasPrice_
17
35
 
18
- if (!gasLimit) {
19
- gasLimit = await fetchGasLimit({
36
+ // HACK: If we've received an invalid combination of `tipGasPrice`
37
+ // (maxPriorityFeePerGas) and `gasPrice` (maxFeePerGas), then
38
+ // we must normalize these before returning.
39
+ const gasPrice = eip1559Enabled
40
+ ? ensureSaneEip1559GasPriceForTipGasPrice({ gasPrice: maybeGasPrice, tipGasPrice })
41
+ : maybeGasPrice
42
+
43
+ const gasLimit =
44
+ feeOpts.gasLimit ||
45
+ (await fetchGasLimit({
20
46
  asset,
21
47
  fromAddress,
22
48
  toAddress,
23
49
  amount,
24
50
  txInput,
25
51
  throwOnError: false,
26
- })
27
- }
52
+ }))
28
53
 
29
- return { gasPrice, gasLimit, tipGasPrice }
54
+ return { gasPrice, gasLimit, tipGasPrice, eip1559Enabled }
30
55
  }
31
56
 
32
57
  export default getFeeInfo