@exodus/ethereum-api 8.45.1 → 8.45.2

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,18 @@
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.45.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.45.1...@exodus/ethereum-api@8.45.2) (2025-08-11)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: enable gas estimation for fixedGasLimit tokens (#6253)
13
+
14
+ * fix: maintain eip-1559 invariant for gasPrice on eip1559Enabled nework (#6158)
15
+
16
+
17
+
6
18
  ## [8.45.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.45.0...@exodus/ethereum-api@8.45.1) (2025-08-06)
7
19
 
8
20
  **Note:** Version bump only for package @exodus/ethereum-api
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.45.1",
3
+ "version": "8.45.2",
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",
@@ -63,5 +63,5 @@
63
63
  "type": "git",
64
64
  "url": "git+https://github.com/ExodusMovement/assets.git"
65
65
  },
66
- "gitHead": "ef612bef233c1c654ef543610dbd5ab76214c8fc"
66
+ "gitHead": "44e5b51ad5bc9cc5a7ec8a0a685da6979acc6369"
67
67
  }
@@ -60,8 +60,9 @@ export const buildApproveTx = async ({
60
60
 
61
61
  if (!txInput) txInput = asset.contract.approve.build(spenderAddress, approveAmount.toBaseString())
62
62
 
63
- if (typeof nonce !== 'number')
63
+ if (typeof nonce !== 'number') {
64
64
  nonce = await baseAsset.getNonce({ asset, fromAddress, walletAccount })
65
+ }
65
66
 
66
67
  if (!gasLimit) {
67
68
  gasLimit = await fetchGasLimit({
@@ -163,8 +164,9 @@ export const createApprove =
163
164
 
164
165
  const txData = await sendTx({ walletAccount, ...approveTx, ...extras })
165
166
 
166
- if (!txData || !txData.txId)
167
+ if (!txData || !txData.txId) {
167
168
  throw new Error(`Failed to approve ${asset.displayTicker} - ${spenderAddress}`)
169
+ }
168
170
 
169
171
  return txData
170
172
  }
@@ -42,12 +42,14 @@ const getTextFromResponse = async (response) => {
42
42
  const fetchJson = async (url, fetchOptions) => {
43
43
  const response = await fetch(url, fetchOptions)
44
44
 
45
- if (!response.ok)
45
+ if (!response.ok) {
46
46
  throw new Error(
47
47
  `${url} returned ${response.status}: ${
48
48
  response.statusText || 'Unknown Status Text'
49
49
  }. Body: ${await getTextFromResponse(response)}`
50
50
  )
51
+ }
52
+
51
53
  return response.json()
52
54
  }
53
55
 
package/src/fee-utils.js CHANGED
@@ -137,6 +137,7 @@ export const calculateEthLikeFeeMonitorUpdateEip1559 = async ({
137
137
 
138
138
  return {
139
139
  ...defaultFeeConfig,
140
+ gasPrice: `${(baseFeePerGas + maxPriorityFeePerGas50Percentile).toString()} wei`,
140
141
  tipGasPrice: `${maxPriorityFeePerGas50Percentile.toString()} wei`,
141
142
  }
142
143
  }
@@ -118,12 +118,9 @@ export async function fetchGasLimit({
118
118
  bip70,
119
119
  throwOnError = true,
120
120
  }) {
121
- if (bip70?.bitpay?.data && bip70?.bitpay?.gasPrice)
122
- return asset.name === 'ethereum' ? 65_000 : 130_000 // from on chain stats https://dune.xyz/queries/189123
123
-
124
- const fixedGasLimit = feeData?.gasLimits?.[asset.name]?.fixedGasLimit
125
- if (fixedGasLimit) {
126
- return fixedGasLimit
121
+ if (bip70?.bitpay?.data && bip70?.bitpay?.gasPrice) {
122
+ // from on chain stats https://dune.xyz/queries/189123
123
+ return asset.name === 'ethereum' ? 65_000 : 130_000
127
124
  }
128
125
 
129
126
  const amount = providedAmount ?? asset.currency.ZERO
@@ -159,7 +156,17 @@ export async function fetchGasLimit({
159
156
  data: txInput,
160
157
  })
161
158
 
162
- return scaleGasLimitEstimate({ estimatedGasLimit, gasLimitMultiplier })
159
+ // NOTE: Although we'll return the `fixedGasLimit` for known
160
+ // tokens, we'll still want to execute `estimateGasLimit`
161
+ // to verify their transaction actually succeeds (i.e.
162
+ // they aren't trying to transfer more than their balance).
163
+ //
164
+ // This prevents users from submitting `fixedGasLimit`
165
+ // token transactions which result in a `revert`.
166
+ return (
167
+ feeData?.gasLimits?.[asset.name]?.fixedGasLimit ??
168
+ scaleGasLimitEstimate({ estimatedGasLimit, gasLimitMultiplier })
169
+ )
163
170
  } catch (err) {
164
171
  if (throwOnError) throw err
165
172
 
@@ -78,13 +78,14 @@ export function createEthereumStakingService({
78
78
  })
79
79
 
80
80
  // Goerli is not supported
81
- if (asset.name === 'ethereum')
81
+ if (asset.name === 'ethereum') {
82
82
  await stakingProvider.notifyStaking({
83
83
  txId,
84
84
  asset: asset.name,
85
85
  delegator: delegatorAddress,
86
86
  amount: amount.toBaseString(),
87
87
  })
88
+ }
88
89
 
89
90
  return txId
90
91
  }
@@ -98,8 +99,9 @@ export function createEthereumStakingService({
98
99
  }) {
99
100
  const leftOver = pendingAmount.sub(resquestedAmount)
100
101
 
101
- if (leftOver.isPositive && leftOver.lt(minAmount))
102
+ if (leftOver.isPositive && leftOver.lt(minAmount)) {
102
103
  throw new Error(`Pending balance less than min stake amount ${minAmount}`)
104
+ }
103
105
 
104
106
  const inactiveAmountToUnstake = pendingAmount.lte(resquestedAmount)
105
107
  ? pendingAmount
@@ -258,13 +260,14 @@ export function createEthereumStakingService({
258
260
  }
259
261
 
260
262
  // Testnet assets do not support delegations tracking
261
- if (txId && asset.name === 'ethereum')
263
+ if (txId && asset.name === 'ethereum') {
262
264
  await stakingProvider.notifyUnstaking({
263
265
  txId,
264
266
  asset: asset.name,
265
267
  delegator: delegatorAddress,
266
268
  amount: resquestedAmount.toBaseString(),
267
269
  })
270
+ }
268
271
 
269
272
  return txId
270
273
  }
@@ -308,8 +311,9 @@ export function createEthereumStakingService({
308
311
  let delegatorAddress
309
312
  ;({ delegatorAddress, feeData } = await getTransactionProps({ feeData, walletAccount }))
310
313
 
311
- if (operation === 'undelegate')
314
+ if (operation === 'undelegate') {
312
315
  return estimateUndelegate({ walletAccount, amount: requestedAmount, feeData })
316
+ }
313
317
 
314
318
  const NAMING_MAP = {
315
319
  delegate: 'stake',
@@ -428,11 +432,12 @@ export function createEthereumStakingService({
428
432
  // If the `spendableForStaking` is insufficient to cover both the
429
433
  // transaction fee and the minimum stake, then there is no reasonable
430
434
  // value we can recommend.
431
- if (calculatedFee.add(minAmount).gt(spendableForStaking))
435
+ if (calculatedFee.add(minAmount).gt(spendableForStaking)) {
432
436
  return {
433
437
  calculatedFee,
434
438
  selectAllAmount: asset.currency.ZERO,
435
439
  }
440
+ }
436
441
 
437
442
  // At this stage, we've confirmed that the remaining `spendableForStaking`
438
443
  // after transactions is sufficient to cover the minimum stake, so any
package/src/tx-create.js CHANGED
@@ -157,12 +157,13 @@ const createBumpUnsignedTx = async ({
157
157
  // If we have evaluated a bump transaction and the `providedNonce` differs
158
158
  // from the `bumpNonce`, we've encountered a conflict and cannot respect
159
159
  // the caller's request.
160
- if (typeof nonce === 'number' && typeof providedNonce === 'number' && nonce !== providedNonce)
160
+ if (typeof nonce === 'number' && typeof providedNonce === 'number' && nonce !== providedNonce) {
161
161
  throw new ErrorWrapper.EthLikeError({
162
162
  message: new Error('incorrect nonce for replacement transaction'),
163
163
  reason: ErrorWrapper.reasons.bumpTxFailed,
164
164
  hint: 'providedNonce',
165
165
  })
166
+ }
166
167
 
167
168
  return createUnsignedTxWithFees({
168
169
  asset,
@@ -129,11 +129,7 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
129
129
  const txsToRemove = []
130
130
  const now = SynchronizedTime.now()
131
131
 
132
- if (isEmpty(pendingTransactions))
133
- return {
134
- txsToUpdate,
135
- txsToRemove,
136
- }
132
+ if (isEmpty(pendingTransactions)) return { txsToUpdate, txsToRemove }
137
133
 
138
134
  for (const { tx, assetName } of pendingTransactions) {
139
135
  const txFromNode = pendingTxsFromNode[tx.txId]
@@ -21,8 +21,9 @@ const getEthereumStakingTxData = ({ tx, currency }) => {
21
21
  (stakeTx) => tx.data?.[stakeTx]
22
22
  ) &&
23
23
  tx.coinAmount.isZero
24
- )
24
+ ) {
25
25
  return
26
+ }
26
27
 
27
28
  const txAmount = tx.coinAmount.toDefaultString()
28
29
 
@@ -58,8 +59,9 @@ const getPolygonStakingTxData = ({ tx, currency }) => {
58
59
  if (
59
60
  ['delegate', 'undelegate', 'claimUndelegate'].some((stakeTx) => tx.data?.[stakeTx]) &&
60
61
  tx.coinAmount.isZero
61
- )
62
+ ) {
62
63
  return
64
+ }
63
65
 
64
66
  const txAmount = tx.coinAmount.toDefaultString()
65
67
 
@@ -93,10 +93,11 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
93
93
 
94
94
  const isPrivate = Boolean(legacyParams?.options?.isPrivate)
95
95
 
96
- if (isPrivate && typeof baseAsset.broadcastPrivateTx !== 'function')
96
+ if (isPrivate && typeof baseAsset.broadcastPrivateTx !== 'function') {
97
97
  throw new Error(
98
98
  `unable to send private transaction - private mempools are not enabled for ${baseAsset.name}`
99
99
  )
100
+ }
100
101
 
101
102
  const broadcastTx = isPrivate ? baseAsset.broadcastPrivateTx : baseAsset.api.broadcastTx
102
103