@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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [8.34.4](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.34.3...@exodus/ethereum-api@8.34.4) (2025-05-07)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: remove fixed gas limit tokens on evm (#5543)
13
+
14
+
15
+
16
+ ## [8.34.3](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.34.2...@exodus/ethereum-api@8.34.3) (2025-04-29)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+
22
+ * fix: prevent insufficient evm custom fees on eip1559Enabled networks (#5519)
23
+
24
+
25
+
6
26
  ## [8.34.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.34.1...@exodus/ethereum-api@8.34.2) (2025-04-18)
7
27
 
8
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.34.2",
3
+ "version": "8.34.4",
4
4
  "description": "Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Ethereum and EVM-based blockchains",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -28,7 +28,7 @@
28
28
  "@exodus/bip44-constants": "^195.0.0",
29
29
  "@exodus/crypto": "^1.0.0-rc.13",
30
30
  "@exodus/currency": "^6.0.1",
31
- "@exodus/ethereum-lib": "^5.10.1",
31
+ "@exodus/ethereum-lib": "^5.10.2",
32
32
  "@exodus/ethereum-meta": "^2.5.0",
33
33
  "@exodus/ethereumholesky-meta": "^2.0.2",
34
34
  "@exodus/ethereumjs": "^1.0.0",
@@ -64,5 +64,5 @@
64
64
  "type": "git",
65
65
  "url": "git+https://github.com/ExodusMovement/assets.git"
66
66
  },
67
- "gitHead": "e7d49daa108ffa7200e33f6cfe2d1c175cd675ea"
67
+ "gitHead": "ef3710f80aaff8c5d84bc53026800f9a1c0e029f"
68
68
  }
@@ -28,7 +28,7 @@ import { createEvmServer, ValidMonitorTypes } from './exodus-eth-server/index.js
28
28
  import { createFeeData } from './fee-data-factory.js'
29
29
  import { createGetBalanceForAddress } from './get-balance-for-address.js'
30
30
  import { getBalancesFactory } from './get-balances.js'
31
- import { getEffectiveGasPrice, getFeeFactory } from './get-fee.js'
31
+ import { getFeeFactory } from './get-fee.js'
32
32
  import getFeeAsyncFactory from './get-fee-async.js'
33
33
  import { createEthereumHooks } from './hooks/index.js'
34
34
  import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas/index.js'
@@ -323,7 +323,6 @@ export const createAssetFactory = ({
323
323
  server,
324
324
  ...(erc20FuelBuffer && { erc20FuelBuffer }),
325
325
  ...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
326
- getEffectiveGasPrice,
327
326
  }
328
327
  return overrideCallback({
329
328
  asset: fullAsset,
@@ -2,21 +2,51 @@ import NumberUnit from '@exodus/currency'
2
2
 
3
3
  import { resolveGasPrice } from './fee-utils.js'
4
4
 
5
+ const calculateMinCustomFee = ({ feeData }) => {
6
+ const { eip1559Enabled, baseFeePerGas, gasPriceMinimumRate } = feeData
7
+
8
+ // On `eip1559Enabled` networks, the `baseFeePerGas` is
9
+ // the minimum a transaction should be sent using, else
10
+ // get rejected by the RPC.
11
+ if (eip1559Enabled && baseFeePerGas) return baseFeePerGas.mul(Math.max(gasPriceMinimumRate, 1))
12
+
13
+ // On legacy networks, senders are permitted to send
14
+ // transactions below the `baseFeePerGas` (since these
15
+ // would not be rejected by the RPC - in fact, it was
16
+ // even possible to mine transactions with a `gasPrice`
17
+ // of `0`!).
18
+ return resolveGasPrice({ feeData }).mul(gasPriceMinimumRate)
19
+ }
20
+
21
+ const calculateRecommendedCustomFee = ({ min, feeData }) => {
22
+ const recommended = resolveGasPrice({ feeData })
23
+ // The `recommended` fee must be at least the `min`.
24
+ return recommended.gt(min) ? recommended : min
25
+ }
26
+
27
+ const calculateMaxCustomFee = ({ feeData, recommended }) => {
28
+ const { gasPriceMinimumRate, gasPriceMaximumRate } = feeData
29
+
30
+ const maxGasPrice = resolveGasPrice({ feeData }).mul(
31
+ Math.max(gasPriceMaximumRate, gasPriceMinimumRate)
32
+ )
33
+
34
+ // When calculating the `max` custom fee, the returned fee
35
+ // must be at least the `recommended` price.
36
+ return recommended.gt(maxGasPrice) ? recommended : maxGasPrice
37
+ }
38
+
5
39
  export const createCustomFeesApi = ({ baseAsset }) => {
6
40
  const Gwei = baseAsset.currency.units.Gwei
7
41
  return {
8
42
  getRecommendedMinMaxFeeUnitPrices: ({ feeData }) => {
9
- const { gasPriceMinimumRate, gasPriceMaximumRate } = feeData
10
-
11
- const gasPrice = resolveGasPrice({ feeData })
12
-
13
- const calculateFeeUnitPrice = (feeUnitPrice, multiplier = 1) =>
14
- feeUnitPrice.mul(multiplier).toNumber(Gwei)
15
-
43
+ const min = calculateMinCustomFee({ feeData })
44
+ const recommended = calculateRecommendedCustomFee({ feeData, min })
45
+ const max = calculateMaxCustomFee({ feeData, recommended })
16
46
  return {
17
- recommended: calculateFeeUnitPrice(gasPrice),
18
- min: calculateFeeUnitPrice(gasPrice, gasPriceMinimumRate),
19
- max: calculateFeeUnitPrice(gasPrice, gasPriceMaximumRate),
47
+ recommended: recommended.toNumber(Gwei),
48
+ min: min.toNumber(Gwei),
49
+ max: max.toNumber(Gwei),
20
50
  }
21
51
  },
22
52
  unit: 'gwei/gas',
package/src/fee-utils.js CHANGED
@@ -41,9 +41,7 @@ export const rewriteFeeConfig = ({ feeAsset, feeConfig, feeData }) => {
41
41
  export const resolveGasPrice = ({ feeData }) => {
42
42
  assert(feeData, 'feeData is required')
43
43
 
44
- const eip1559Enabled = feeData.eip1559Enabled
45
-
46
- if (eip1559Enabled && feeData.useBaseGasPrice) {
44
+ if (feeData.eip1559Enabled) {
47
45
  return feeData.tipGasPrice
48
46
  ? feeData.baseFeePerGas.add(feeData.tipGasPrice)
49
47
  : feeData.baseFeePerGas
@@ -51,3 +49,19 @@ export const resolveGasPrice = ({ feeData }) => {
51
49
 
52
50
  return feeData.gasPrice
53
51
  }
52
+
53
+ export const ensureSaneEip1559GasPriceForTipGasPrice = ({ gasPrice, tipGasPrice }) => {
54
+ if (!tipGasPrice || tipGasPrice.lt(gasPrice)) return gasPrice
55
+ return tipGasPrice
56
+ }
57
+
58
+ export const getNormalizedFeeDataForCustomFee = ({ customFee, feeData }) => {
59
+ const { eip1559Enabled } = feeData
60
+ // We must ensure maxPriorityFeePerGas <= maxFeePerGas or our transaction library throws an error
61
+ // It's a bit counterintuitive since maxPriorityFeePerGas should only contain the tip,
62
+ // so we should be subtracting the base gas price from the custom gas price to keep just the tip
63
+ // but the fee is also limited by our maxFeePerGas above, so that implicitly captures the max tip.
64
+ // Setting this tipGasPrice to undefined will result in a legacy transaction (not an EIP1559 anymore)
65
+ if (eip1559Enabled && customFee) return { ...feeData, tipGasPrice: customFee }
66
+ return feeData
67
+ }
@@ -1,6 +1,5 @@
1
1
  import { currency2buffer, isEthereumLikeToken } from '@exodus/ethereum-lib'
2
2
  import { bufferToHex, toBuffer } from '@exodus/ethereumjs/util'
3
- import BN from 'bn.js'
4
3
 
5
4
  import { estimateGas, isContractAddressCached } from './eth-like-util.js'
6
5
 
@@ -11,6 +10,11 @@ const GAS_PER_NON_ZERO_BYTE = 16
11
10
 
12
11
  export const DEFAULT_CONTRACT_GAS_LIMIT = 1e6
13
12
 
13
+ // HACK: RPCs generally provide imprecise estimates
14
+ // for `gasUsed` (often these are insufficient).
15
+ export const scaleGasLimitEstimate = ({ estimatedGasLimit, extraPercentage = EXTRA_PERCENTAGE }) =>
16
+ Number((BigInt(estimatedGasLimit) * BigInt(100 + extraPercentage)) / BigInt(100))
17
+
14
18
  // Starting with geth v1.9.14, if gasPrice is set for eth_estimateGas call, the call allowance will
15
19
  // be calculated with account's balance divided by gasPrice. If user's balance is too low,
16
20
  // the gasEstimation will fail. If gasPrice is set to '0x0', the account's balance is not
@@ -22,7 +26,7 @@ export async function estimateGasLimit(
22
26
  amount,
23
27
  data,
24
28
  gasPrice = '0x',
25
- extraPercentage = EXTRA_PERCENTAGE
29
+ extraPercentage
26
30
  ) {
27
31
  const opts = {
28
32
  from: fromAddress,
@@ -33,10 +37,11 @@ export async function estimateGasLimit(
33
37
  }
34
38
 
35
39
  const estimatedGas = await estimateGas({ asset, ...opts })
36
- return new BN(estimatedGas.slice(2), 16)
37
- .imuln(100 + extraPercentage)
38
- .idivn(100)
39
- .toNumber()
40
+
41
+ return scaleGasLimitEstimate({
42
+ estimatedGasLimit: BigInt(estimatedGas),
43
+ extraPercentage,
44
+ })
40
45
  }
41
46
 
42
47
  export function resolveDefaultTxInput({ asset, toAddress, amount }) {
@@ -5,14 +5,6 @@ import { EXTRA_PERCENTAGE, fetchGasLimit, resolveDefaultTxInput } from './gas-es
5
5
  import { getFeeFactory } from './get-fee.js'
6
6
  import { getNftArguments } from './nft-utils.js'
7
7
 
8
- const FIXED_TRANSFER_GAS_LIMIT_ASSETS = new Set([
9
- 'amp',
10
- 'tetherusd',
11
- 'usdcoin',
12
- 'snx',
13
- 'geminidollar',
14
- ])
15
-
16
8
  const UNKNOWN_ADDRESS_EXTRA_PERCENTAGE = {
17
9
  usdt_bsc_ddedf0f8: 80,
18
10
  }
@@ -24,12 +16,6 @@ export async function resolveExtraPercentage({ asset, fromAddress, toAddress })
24
16
 
25
17
  const isToken = asset.name !== asset.baseAsset.name
26
18
 
27
- const isFixedGasLimitToken = isToken && FIXED_TRANSFER_GAS_LIMIT_ASSETS.has(asset.name)
28
-
29
- if (isFixedGasLimitToken) {
30
- return 0
31
- }
32
-
33
19
  // calling forwarder contracts with a bumped gas limit causes 'Out Of Gas' error on chain
34
20
  const hasForwarderContract =
35
21
  !isToken && toAddress ? await isForwarderContractCached({ asset, address: toAddress }) : false
package/src/get-fee.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import { calculateBumpedGasPrice, calculateExtraEth } from '@exodus/ethereum-lib'
2
2
 
3
- import { resolveGasPrice } from './fee-utils.js'
3
+ import {
4
+ ensureSaneEip1559GasPriceForTipGasPrice,
5
+ getNormalizedFeeDataForCustomFee,
6
+ resolveGasPrice,
7
+ } from './fee-utils.js'
4
8
 
5
9
  // Move to meta?
6
10
  const taxes = {
@@ -21,6 +25,20 @@ const getExtraFeeData = ({ asset, amount }) => {
21
25
  }
22
26
  }
23
27
 
28
+ export const getFeeFactoryGasPrices = ({ customFee, feeData }) => {
29
+ feeData = getNormalizedFeeDataForCustomFee({ customFee, feeData })
30
+ const gasPrice = customFee || resolveGasPrice({ feeData })
31
+ const { tipGasPrice, eip1559Enabled } = feeData
32
+
33
+ // The `gasPrice` must be at least the `tipGasPrice`.
34
+ return {
35
+ gasPrice: eip1559Enabled
36
+ ? ensureSaneEip1559GasPriceForTipGasPrice({ gasPrice, tipGasPrice })
37
+ : gasPrice,
38
+ feeData,
39
+ }
40
+ }
41
+
24
42
  export const getFeeFactory =
25
43
  ({ gasLimit: defaultGasLimit }) =>
26
44
  ({
@@ -32,32 +50,19 @@ export const getFeeFactory =
32
50
  amount,
33
51
  calculateEffectiveFee,
34
52
  }) => {
35
- const { eip1559Enabled } = feeData
36
-
37
- // HACK: For EIP-1559 transactions, we forcibly coerce the
38
- // tipGasPrice to be the `customFee`, where defined:
39
- // https://github.com/ExodusMovement/assets/blob/b805d1de0e67013ece2a1460389fb4328445eac1/ethereum/ethereum-api/src/tx-send/tx-send.js#L302C5-L311C6
40
- //
41
- // Therefore to compute the true fee at send time, we
42
- // must also enforce the relationship here!
43
- if (eip1559Enabled && customFee) {
44
- feeData = { ...feeData, tipGasPrice: customFee }
45
- }
46
-
47
- const { baseFeePerGas, tipGasPrice, useBaseGasPrice } = feeData
48
-
49
- let gasPrice = customFee || resolveGasPrice({ feeData })
50
-
51
- // The `gasPrice` must be at least the `tipGasPrice`.
52
- if (eip1559Enabled && tipGasPrice && gasPrice.lt(tipGasPrice)) {
53
- gasPrice = tipGasPrice
54
- }
53
+ const {
54
+ feeData: { baseFeePerGas, tipGasPrice, eip1559Enabled },
55
+ gasPrice,
56
+ } = getFeeFactoryGasPrices({
57
+ customFee,
58
+ feeData,
59
+ })
55
60
 
56
61
  const gasLimit = providedGasLimit || asset.gasLimit || defaultGasLimit
57
62
 
58
63
  // When explicitly opting into EIP-1559 transactions,
59
64
  // lock in the `tipGasPrice` we used to compute the fees.
60
- const maybeReturnTipGasPrice = eip1559Enabled && useBaseGasPrice ? { tipGasPrice } : null
65
+ const maybeReturnTipGasPrice = eip1559Enabled ? { tipGasPrice } : null
61
66
 
62
67
  const extraFeeData = getExtraFeeData({ asset, amount })
63
68
  if (calculateEffectiveFee && eip1559Enabled) {
@@ -78,26 +83,15 @@ export const getFeeFactory =
78
83
  return { ...maybeReturnTipGasPrice, fee, gasPrice, extraFeeData }
79
84
  }
80
85
 
81
- // Used in BE
82
- export const getEffectiveGasPrice = ({ feeData }) => {
83
- const { baseFeePerGas, tipGasPrice, gasPrice: maxFeePerGas, eip1559Enabled } = feeData
84
-
85
- if (!eip1559Enabled) {
86
- return maxFeePerGas
87
- }
88
-
89
- const gasPrice = baseFeePerGas.add(tipGasPrice)
90
- return gasPrice.lt(maxFeePerGas) ? gasPrice : maxFeePerGas
91
- }
92
-
93
86
  // Used in Mobile
94
87
  export const getExtraFeeForBump = ({ tx, feeData, balance, unconfirmedBalance }) => {
95
88
  if (!balance || !unconfirmedBalance) return null
96
- const { gasPrice: currentGasPrice, eip1559Enabled } = feeData
89
+ const { gasPrice: currentGasPrice, eip1559Enabled, baseFeePerGas: currentBaseFee } = feeData
97
90
  const { bumpedGasPrice } = calculateBumpedGasPrice({
98
91
  baseAsset: 'ethereum',
99
92
  tx,
100
93
  currentGasPrice,
94
+ currentBaseFee,
101
95
  eip1559Enabled,
102
96
  })
103
97
  return calculateExtraEth({
package/src/index.js CHANGED
@@ -81,6 +81,8 @@ export {
81
81
 
82
82
  export { reasons as errorReasons, withErrorReason, EthLikeError } from './error-wrapper.js'
83
83
 
84
+ // TODO: `getFeeInfo` is not consumed by third parties and should
85
+ // be considered an internal API.
84
86
  export { txSendFactory, getFeeInfo } from './tx-send/index.js'
85
87
 
86
88
  export { createAssetFactory } from './create-asset.js'
@@ -4,6 +4,7 @@ import { retry } from '@exodus/simple-retry'
4
4
 
5
5
  import { getServerByName } from '../../exodus-eth-server/index.js'
6
6
 
7
+ // TODO: Shouldn't this be a function of `ethereumStakingState.minDelegateAmount`?
7
8
  const MIN_AMOUNT = '0.1'
8
9
  const RETRY_DELAYS = ['10s']
9
10