@exodus/ethereum-api 8.62.2 → 8.62.3

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,16 @@
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.62.3](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.62.2...@exodus/ethereum-api@8.62.3) (2026-01-13)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: enfore sanity checking about clarity string numerics for canonical tx props (#7262)
13
+
14
+
15
+
6
16
  ## [8.62.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.62.1...@exodus/ethereum-api@8.62.2) (2026-01-07)
7
17
 
8
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.62.2",
3
+ "version": "8.62.3",
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",
@@ -67,5 +67,5 @@
67
67
  "type": "git",
68
68
  "url": "git+https://github.com/ExodusMovement/assets.git"
69
69
  },
70
- "gitHead": "be4898d14d842c79dfcc327501c390dfaf56157b"
70
+ "gitHead": "d1618b4c41ccde490c3e4aaa8dd239c0c5393a7b"
71
71
  }
@@ -23,6 +23,14 @@ const MINIMUM_DELEGATION_GAS_LIMIT = 180_000
23
23
 
24
24
  const EXTRA_GAS_LIMIT = 20_000 // extra gas Limit to prevent tx failing if something change on pool state (till tx is in mempool)
25
25
 
26
+ // Estimates based on real life transaction averages.
27
+ // Overestimated averages by 4x.
28
+ const STAKING_OPERATION_FALLBACK_FEE_ESTIMATES = {
29
+ delegate: '0.00081512',
30
+ undelegate: '0.000554',
31
+ claimUndelegatedBalance: '0.0001800912',
32
+ }
33
+
26
34
  const getStakingApi = memoize(
27
35
  (asset) => new EthereumStaking(asset, undefined, asset.server),
28
36
  (asset) => asset.name
@@ -346,61 +354,76 @@ export function createEthereumStakingService({
346
354
 
347
355
  async function estimateDelegateOperation({ walletAccount, operation, args, feeData }) {
348
356
  const asset = await getAsset(assetName)
349
- const staking = getStakingApi(asset)
350
-
351
- const requestedAmount = args.amount
352
- ? amountToCurrency({ asset, amount: args.amount })
353
- : asset.currency.ZERO
354
357
 
355
- let delegatorAddress
356
- ;({ delegatorAddress, feeData } = await getTransactionProps({ feeData, walletAccount }))
357
-
358
- if (operation === 'undelegate') {
359
- return estimateUndelegate({ walletAccount, amount: requestedAmount, feeData })
360
- }
358
+ try {
359
+ const staking = getStakingApi(asset)
361
360
 
362
- const NAMING_MAP = {
363
- delegate: 'stake',
364
- undelegate: 'unstake',
365
- claimUndelegatedBalance: 'claimWithdrawRequest',
366
- }
361
+ const requestedAmount = args.amount
362
+ ? amountToCurrency({ asset, amount: args.amount })
363
+ : asset.currency.ZERO
367
364
 
368
- const delegateOperation = staking[NAMING_MAP[operation]].bind(staking)
365
+ let delegatorAddress
366
+ ;({ delegatorAddress, feeData } = await getTransactionProps({ feeData, walletAccount }))
369
367
 
370
- if (!delegateOperation) throw new Error('Invalid staking operation')
368
+ if (operation === 'undelegate') {
369
+ return estimateUndelegate({ walletAccount, amount: requestedAmount, feeData })
370
+ }
371
371
 
372
- const { amount, data } = await delegateOperation({ ...args, amount: requestedAmount })
372
+ const NAMING_MAP = {
373
+ delegate: 'stake',
374
+ undelegate: 'unstake',
375
+ claimUndelegatedBalance: 'claimWithdrawRequest',
376
+ }
373
377
 
374
- const { fee } = await (operation === 'claimUndelegatedBalance'
375
- ? estimateTxFee({
376
- from: delegatorAddress,
377
- to: staking.accountingAddress,
378
- amount,
379
- txInput: data,
380
- feeData,
381
- })
382
- : // The `gasUsed` of a delegation transaction can vary
383
- // significantly depending upon whether it will result
384
- // in the activation of new slots (i.e. validator creation).
385
- //
386
- // This can result in transaction `revert` due to slippage
387
- // from incompatible transaction ordering.
388
- //
389
- // To mitigate this, we:
390
- // 1. Simulate the delegation at an amplified deposit amount
391
- // to show enhanced fees close to a proximity buffer.
392
- // 2. Originate the transaction from the WETH contract,
393
- // which guarantees deep native ether liquidity which
394
- // exceeds any rational user deposit.
395
- estimateTxFee({
396
- from: WETH9_ADDRESS,
397
- to: staking.poolAddress,
398
- amount: amount.add(asset.currency.defaultUnit(SLOT_ACTIVATION_PROXIMITY_ETH)),
399
- txInput: data,
400
- feeData,
401
- }))
378
+ const delegateOperation = staking[NAMING_MAP[operation]].bind(staking)
379
+
380
+ if (!delegateOperation) throw new Error('Invalid staking operation')
381
+
382
+ const { amount, data } = await delegateOperation({ ...args, amount: requestedAmount })
383
+
384
+ const { fee } = await (operation === 'claimUndelegatedBalance'
385
+ ? estimateTxFee({
386
+ from: delegatorAddress,
387
+ to: staking.accountingAddress,
388
+ amount,
389
+ txInput: data,
390
+ feeData,
391
+ })
392
+ : // The `gasUsed` of a delegation transaction can vary
393
+ // significantly depending upon whether it will result
394
+ // in the activation of new slots (i.e. validator creation).
395
+ //
396
+ // This can result in transaction `revert` due to slippage
397
+ // from incompatible transaction ordering.
398
+ //
399
+ // To mitigate this, we:
400
+ // 1. Simulate the delegation at an amplified deposit amount
401
+ // to show enhanced fees close to a proximity buffer.
402
+ // 2. Originate the transaction from the WETH contract,
403
+ // which guarantees deep native ether liquidity which
404
+ // exceeds any rational user deposit.
405
+ estimateTxFee({
406
+ from: WETH9_ADDRESS,
407
+ to: staking.poolAddress,
408
+ amount: amount.add(asset.currency.defaultUnit(SLOT_ACTIVATION_PROXIMITY_ETH)),
409
+ txInput: data,
410
+ feeData,
411
+ }))
412
+
413
+ return fee
414
+ } catch (error) {
415
+ const fallbackFee = STAKING_OPERATION_FALLBACK_FEE_ESTIMATES[operation]
416
+ if (fallbackFee) {
417
+ console.warn(
418
+ `ETH staking fee estimation failed for '${operation}', using fallback:`,
419
+ error.message
420
+ )
421
+ return asset.currency.defaultUnit(fallbackFee)
422
+ }
402
423
 
403
- return fee
424
+ error.message = `StakingFeeEstimationError: Failed to estimate fee for '${operation}' (and no fallback configured). Cause: ${error.message}`
425
+ throw error
426
+ }
404
427
  }
405
428
 
406
429
  async function estimateTxFee({ from, to, amount, txInput, feeData }) {
@@ -9,9 +9,10 @@ import isConsideredSentTokenTx from '../monitor-utils/is-considered-sent-token-t
9
9
  import filterEffects from './filter-effects.js'
10
10
  import getNamesOfTokensTransferredByServerTx from './get-names-of-tokens-transferred-by-server-tx.js'
11
11
 
12
- // This function takes a server transaction object fetched from magnifier,
13
- // and transforms it into Tx models to update the exodus state.
12
+ const isFiniteInteger = (n) => Number.isFinite(n) && Number.isInteger(n)
14
13
 
14
+ // This function takes a server transaction object fetched from clarity,
15
+ // and transforms it into Tx models to update the exodus state.
15
16
  export default function getLogItemsFromServerTx({
16
17
  serverTx,
17
18
  asset,
@@ -93,8 +94,8 @@ export default function getLogItemsFromServerTx({
93
94
  nonceChange: nonceUpdate,
94
95
  ...methodId,
95
96
  ...(sent?.length > 0 ? { sent } : undefined),
96
- blockNumber,
97
- transactionIndex,
97
+ ...(isFiniteInteger(blockNumber) ? { blockNumber } : null),
98
+ ...(isFiniteInteger(transactionIndex) ? { transactionIndex } : null),
98
99
  },
99
100
  ...(ourWalletWasSender
100
101
  ? { from: [], to: toAddress, feeAmount, feeCoinName: asset.feeAsset.name }
@@ -159,8 +160,8 @@ export default function getLogItemsFromServerTx({
159
160
  gasLimit,
160
161
  balanceChange,
161
162
  ...methodId,
162
- blockNumber,
163
- transactionIndex,
163
+ ...(isFiniteInteger(blockNumber) ? { blockNumber } : null),
164
+ ...(isFiniteInteger(transactionIndex) ? { transactionIndex } : null),
164
165
  },
165
166
  ...(isConsideredSent
166
167
  ? { from: [], to: tokenTransferToAddress, feeAmount, feeCoinName: token.feeAsset.name }
@@ -4,19 +4,17 @@
4
4
  // array is a transfer for that asset. If we sent more than we received, the value is negative.
5
5
 
6
6
  export default function getValueOfTransfers(ourWalletAddress, asset, transfers) {
7
- return transfers
8
- .reduce((balanceDifference, { from, to, value }) => {
9
- const transferAmount = asset.currency.baseUnit(value)
7
+ return transfers.reduce((balanceDifference, { from, to, value }) => {
8
+ const transferAmount = asset.currency.baseUnit(value)
10
9
 
11
- if (from === ourWalletAddress) {
12
- balanceDifference = balanceDifference.sub(transferAmount)
13
- }
10
+ if (from === ourWalletAddress) {
11
+ balanceDifference = balanceDifference.sub(transferAmount)
12
+ }
14
13
 
15
- if (to === ourWalletAddress) {
16
- balanceDifference = balanceDifference.add(transferAmount)
17
- }
14
+ if (to === ourWalletAddress) {
15
+ balanceDifference = balanceDifference.add(transferAmount)
16
+ }
18
17
 
19
- return balanceDifference
20
- }, asset.currency.ZERO)
21
- .to(asset.ticker)
18
+ return balanceDifference
19
+ }, asset.currency.ZERO)
22
20
  }