@exodus/ethereum-lib 5.20.1 → 5.20.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,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
+ ## [5.20.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-lib@5.20.1...@exodus/ethereum-lib@5.20.2) (2025-12-17)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: prevent acceleration of replaced evm transactions (#6964)
13
+
14
+
15
+
6
16
  ## [5.20.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-lib@5.20.0...@exodus/ethereum-lib@5.20.1) (2025-12-09)
7
17
 
8
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-lib",
3
- "version": "5.20.1",
3
+ "version": "5.20.2",
4
4
  "description": "Ethereum utils, such as for cryptography, address encoding/decoding, transaction building, etc.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -51,5 +51,5 @@
51
51
  "type": "git",
52
52
  "url": "git+https://github.com/ExodusMovement/assets.git"
53
53
  },
54
- "gitHead": "727e0bc0ae5bde63d522d618fc14ff7701fdb156"
54
+ "gitHead": "8c3e94c96818b1ce8ce10cb75da29db2c9fe9ab9"
55
55
  }
@@ -12,6 +12,57 @@ const BumpType = {
12
12
  RBF: 2,
13
13
  }
14
14
 
15
+ // NOTE: If a transaction was successfully included,
16
+ // it is the de-facto highest incentive
17
+ // transaction - irrespective of other
18
+ // attempts for that nonce.
19
+ const getHighestIncentiveTxByNonceForTxLog = ({ nonce, txLog }) => {
20
+ assert(txLog, 'expected txLog')
21
+
22
+ if (txLog.size === 0 || !Number.isInteger(nonce)) return
23
+
24
+ const txLogSendsByFeeAmountDesc = [...txLog]
25
+ .filter((tx) => tx.data.nonce === nonce && tx.sent)
26
+ .sort((a, b) => (a.feeAmount.gt(b.feeAmount) ? -1 : b.feeAmount.gt(a.feeAmount) ? 1 : 0))
27
+
28
+ // If any of the transactions competing for this `nonce`
29
+ // were successful, then we can return this transaction
30
+ // as it effectively had the highest game-theoretical
31
+ // incentive regardless of other (potentially higher fee)
32
+ // transactions that were sent.
33
+ //
34
+ // https://github.com/ExodusMovement/exodus-hydra/blob/e59004097f15974a975d14e1823de5d7b1c28308/features/activity-txs/redux/utils/activity-formatters/format-tx-activity.js#L13
35
+ const maybeConfirmedTx = txLogSendsByFeeAmountDesc.find((tx) => !tx.failed && !tx.pending)
36
+ if (maybeConfirmedTx) return maybeConfirmedTx
37
+
38
+ // https://github.com/ExodusMovement/assets/blob/fbe3702861cba3b21885a65b15f038fcd8541891/shield/asset-lib/src/balances-utils.js#L26
39
+ const isUnconfirmed = (tx) => !tx.failed && tx.pending
40
+
41
+ // NOTE: When trying to find the highest incentive of a
42
+ // transaction, consider those which are still
43
+ // pending.
44
+ return txLogSendsByFeeAmountDesc.find(isUnconfirmed)
45
+ }
46
+
47
+ // Returns the most competitively priced pending
48
+ // transaction from the `TxLog` for a given `nonce`.
49
+ export const getHighestIncentiveTxByNonce = async ({
50
+ assetClientInterface,
51
+ asset,
52
+ nonce,
53
+ walletAccount,
54
+ }) => {
55
+ assert(assetClientInterface, 'expected assetClientInterface')
56
+ assert(asset, 'expected asset')
57
+ assert(Number.isInteger(nonce), 'expected integer nonce')
58
+ assert(walletAccount, 'expected walletAccount')
59
+
60
+ return getHighestIncentiveTxByNonceForTxLog({
61
+ txLog: await assetClientInterface.getTxLog({ assetName: asset.name, walletAccount }),
62
+ nonce,
63
+ })
64
+ }
65
+
15
66
  // Legacy compatibility with wallets
16
67
  export function getPendingNonExchangeTxs(activeWalletAccount, getTxLog, getIsExchangeTx) {
17
68
  console.log('Using deprecated function getPendingNonExchangeTxs()')
@@ -348,6 +399,20 @@ export const canAccelerateTx = ({
348
399
  return wrapResponseToObject({ errorMessage: 'there is a stuck TX with lower nonce' })
349
400
  }
350
401
 
402
+ // Check to see if this is the highest incentive transaction for this nonce.
403
+ const maybeHighestIncentiveTxByNonce = getHighestIncentiveTxByNonceForTxLog({
404
+ // TODO: is this the correct way to determine a nonce?
405
+ nonce: tx.data.nonce,
406
+ txLog: getTxLog(baseAssetName, activeWalletAccount),
407
+ })
408
+
409
+ // NOTE: We should only be able to accelerate the highest
410
+ // incentive transaction which is competing for a
411
+ // given `nonce`.
412
+ if (maybeHighestIncentiveTxByNonce && maybeHighestIncentiveTxByNonce.txId !== tx.txId) {
413
+ return wrapResponseToObject({ errorMessage: 'TX was replaced' })
414
+ }
415
+
351
416
  const {
352
417
  gasPrice: currentGasPrice,
353
418
  eip1559Enabled,
@@ -4,6 +4,7 @@ export {
4
4
  calculateBumpedGasPriceForFeeData,
5
5
  getPendingNonExchangeTxs,
6
6
  getAssetPendingNonExchangeTxs,
7
+ getHighestIncentiveTxByNonce,
7
8
  } from './get-can-accelerate-tx-factory.js'
8
9
 
9
10
  export { default as getIsEnoughBalanceToAccelerateSelectorFactory } from './get-is-enough-balance-to-accelerate-factory.js'