@exodus/ethereum-api 8.34.7 → 8.34.8

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,26 +3,6 @@
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
-
26
6
  ## [8.34.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.34.1...@exodus/ethereum-api@8.34.2) (2025-04-18)
27
7
 
28
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.34.7",
3
+ "version": "8.34.8",
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.2",
31
+ "@exodus/ethereum-lib": "^5.10.1",
32
32
  "@exodus/ethereum-meta": "^2.5.0",
33
33
  "@exodus/ethereumholesky-meta": "^2.0.2",
34
34
  "@exodus/ethereumjs": "^1.0.0",
@@ -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 { getFeeFactory } from './get-fee.js'
31
+ import { getEffectiveGasPrice, 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,6 +323,7 @@ export const createAssetFactory = ({
323
323
  server,
324
324
  ...(erc20FuelBuffer && { erc20FuelBuffer }),
325
325
  ...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
326
+ getEffectiveGasPrice,
326
327
  }
327
328
  return overrideCallback({
328
329
  asset: fullAsset,
@@ -2,51 +2,21 @@ 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
-
39
5
  export const createCustomFeesApi = ({ baseAsset }) => {
40
6
  const Gwei = baseAsset.currency.units.Gwei
41
7
  return {
42
8
  getRecommendedMinMaxFeeUnitPrices: ({ feeData }) => {
43
- const min = calculateMinCustomFee({ feeData })
44
- const recommended = calculateRecommendedCustomFee({ feeData, min })
45
- const max = calculateMaxCustomFee({ feeData, recommended })
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
+
46
16
  return {
47
- recommended: recommended.toNumber(Gwei),
48
- min: min.toNumber(Gwei),
49
- max: max.toNumber(Gwei),
17
+ recommended: calculateFeeUnitPrice(gasPrice),
18
+ min: calculateFeeUnitPrice(gasPrice, gasPriceMinimumRate),
19
+ max: calculateFeeUnitPrice(gasPrice, gasPriceMaximumRate),
50
20
  }
51
21
  },
52
22
  unit: 'gwei/gas',
@@ -1,7 +1,8 @@
1
- import WebSocket from '@exodus/fetch/websocket'
2
1
  import EventEmitter from 'events/events.js' // forces it to use the module from node_modules
3
2
  import ms from 'ms'
4
3
 
4
+ import WebSocket from '../websocket/index.js'
5
+
5
6
  const RECONNECT_INTERVAL = ms('10s')
6
7
  const PING_INTERVAL = ms('20s')
7
8
 
@@ -1,7 +1,8 @@
1
- import WebSocket from '@exodus/fetch/websocket'
2
1
  import EventEmitter from 'events/events.js' // forces it to use the module from node_modules
3
2
  import ms from 'ms'
4
3
 
4
+ import WebSocket from '../websocket/index.js'
5
+
5
6
  const RECONNECT_INTERVAL = ms('10s')
6
7
  const PING_INTERVAL = ms('10s')
7
8
  const MAX_RECONNECT_DELAY = ms('15s')
package/src/fee-utils.js CHANGED
@@ -41,7 +41,9 @@ export const rewriteFeeConfig = ({ feeAsset, feeConfig, feeData }) => {
41
41
  export const resolveGasPrice = ({ feeData }) => {
42
42
  assert(feeData, 'feeData is required')
43
43
 
44
- if (feeData.eip1559Enabled) {
44
+ const eip1559Enabled = feeData.eip1559Enabled
45
+
46
+ if (eip1559Enabled && feeData.useBaseGasPrice) {
45
47
  return feeData.tipGasPrice
46
48
  ? feeData.baseFeePerGas.add(feeData.tipGasPrice)
47
49
  : feeData.baseFeePerGas
@@ -49,19 +51,3 @@ export const resolveGasPrice = ({ feeData }) => {
49
51
 
50
52
  return feeData.gasPrice
51
53
  }
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,5 +1,6 @@
1
1
  import { currency2buffer, isEthereumLikeToken } from '@exodus/ethereum-lib'
2
2
  import { bufferToHex, toBuffer } from '@exodus/ethereumjs/util'
3
+ import BN from 'bn.js'
3
4
 
4
5
  import { estimateGas, isContractAddressCached } from './eth-like-util.js'
5
6
 
@@ -10,11 +11,6 @@ const GAS_PER_NON_ZERO_BYTE = 16
10
11
 
11
12
  export const DEFAULT_CONTRACT_GAS_LIMIT = 1e6
12
13
 
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
-
18
14
  const hardcodedGasLimits = new Map([
19
15
  ['amp', 151_000],
20
16
  ['tetherusd', 70_000],
@@ -35,7 +31,7 @@ export async function estimateGasLimit(
35
31
  amount,
36
32
  data,
37
33
  gasPrice = '0x',
38
- extraPercentage
34
+ extraPercentage = EXTRA_PERCENTAGE
39
35
  ) {
40
36
  // move this to remote config
41
37
  if (hardcodedGasLimits.has(asset.name)) {
@@ -51,11 +47,10 @@ export async function estimateGasLimit(
51
47
  }
52
48
 
53
49
  const estimatedGas = await estimateGas({ asset, ...opts })
54
-
55
- return scaleGasLimitEstimate({
56
- estimatedGasLimit: BigInt(estimatedGas),
57
- extraPercentage,
58
- })
50
+ return new BN(estimatedGas.slice(2), 16)
51
+ .imuln(100 + extraPercentage)
52
+ .idivn(100)
53
+ .toNumber()
59
54
  }
60
55
 
61
56
  export function resolveDefaultTxInput({ asset, toAddress, amount }) {
package/src/get-fee.js CHANGED
@@ -1,10 +1,6 @@
1
1
  import { calculateBumpedGasPrice, calculateExtraEth } from '@exodus/ethereum-lib'
2
2
 
3
- import {
4
- ensureSaneEip1559GasPriceForTipGasPrice,
5
- getNormalizedFeeDataForCustomFee,
6
- resolveGasPrice,
7
- } from './fee-utils.js'
3
+ import { resolveGasPrice } from './fee-utils.js'
8
4
 
9
5
  // Move to meta?
10
6
  const taxes = {
@@ -25,20 +21,6 @@ const getExtraFeeData = ({ asset, amount }) => {
25
21
  }
26
22
  }
27
23
 
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
-
42
24
  export const getFeeFactory =
43
25
  ({ gasLimit: defaultGasLimit }) =>
44
26
  ({
@@ -50,19 +32,32 @@ export const getFeeFactory =
50
32
  amount,
51
33
  calculateEffectiveFee,
52
34
  }) => {
53
- const {
54
- feeData: { baseFeePerGas, tipGasPrice, eip1559Enabled },
55
- gasPrice,
56
- } = getFeeFactoryGasPrices({
57
- customFee,
58
- feeData,
59
- })
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
+ }
60
55
 
61
56
  const gasLimit = providedGasLimit || asset.gasLimit || defaultGasLimit
62
57
 
63
58
  // When explicitly opting into EIP-1559 transactions,
64
59
  // lock in the `tipGasPrice` we used to compute the fees.
65
- const maybeReturnTipGasPrice = eip1559Enabled ? { tipGasPrice } : null
60
+ const maybeReturnTipGasPrice = eip1559Enabled && useBaseGasPrice ? { tipGasPrice } : null
66
61
 
67
62
  const extraFeeData = getExtraFeeData({ asset, amount })
68
63
  if (calculateEffectiveFee && eip1559Enabled) {
@@ -83,15 +78,26 @@ export const getFeeFactory =
83
78
  return { ...maybeReturnTipGasPrice, fee, gasPrice, extraFeeData }
84
79
  }
85
80
 
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
+
86
93
  // Used in Mobile
87
94
  export const getExtraFeeForBump = ({ tx, feeData, balance, unconfirmedBalance }) => {
88
95
  if (!balance || !unconfirmedBalance) return null
89
- const { gasPrice: currentGasPrice, eip1559Enabled, baseFeePerGas: currentBaseFee } = feeData
96
+ const { gasPrice: currentGasPrice, eip1559Enabled } = feeData
90
97
  const { bumpedGasPrice } = calculateBumpedGasPrice({
91
98
  baseAsset: 'ethereum',
92
99
  tx,
93
100
  currentGasPrice,
94
- currentBaseFee,
95
101
  eip1559Enabled,
96
102
  })
97
103
  return calculateExtraEth({
package/src/index.js CHANGED
@@ -81,8 +81,6 @@ 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.
86
84
  export { txSendFactory, getFeeInfo } from './tx-send/index.js'
87
85
 
88
86
  export { createAssetFactory } from './create-asset.js'
@@ -4,7 +4,6 @@ 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`?
8
7
  const MIN_AMOUNT = '0.1'
9
8
  const RETRY_DELAYS = ['10s']
10
9