@exodus/ethereum-api 8.49.0 → 8.50.1

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,24 @@
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.50.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.50.0...@exodus/ethereum-api@8.50.1) (2025-09-22)
7
+
8
+ **Note:** Version bump only for package @exodus/ethereum-api
9
+
10
+
11
+
12
+
13
+
14
+ ## [8.50.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.49.0...@exodus/ethereum-api@8.50.0) (2025-09-17)
15
+
16
+
17
+ ### Features
18
+
19
+
20
+ * feat: replace deprecated apis with exodus/crypto/hash (#6483)
21
+
22
+
23
+
6
24
  ## [8.49.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.48.0...@exodus/ethereum-api@8.49.0) (2025-09-16)
7
25
 
8
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.49.0",
3
+ "version": "8.50.1",
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",
@@ -26,17 +26,17 @@
26
26
  "@exodus/assets": "^11.4.0",
27
27
  "@exodus/basic-utils": "^3.0.1",
28
28
  "@exodus/bip44-constants": "^195.0.0",
29
- "@exodus/crypto": "^1.0.0-rc.13",
29
+ "@exodus/crypto": "^1.0.0-rc.26",
30
30
  "@exodus/currency": "^6.0.1",
31
31
  "@exodus/ethereum-lib": "^5.17.0",
32
32
  "@exodus/ethereum-meta": "^2.9.1",
33
33
  "@exodus/ethereumholesky-meta": "^2.0.5",
34
- "@exodus/ethereumjs": "^1.0.0",
34
+ "@exodus/ethereumjs": "^1.8.0",
35
35
  "@exodus/fetch": "^1.3.0",
36
36
  "@exodus/models": "^12.13.0",
37
37
  "@exodus/safe-string": "^1.2.0",
38
38
  "@exodus/simple-retry": "^0.0.6",
39
- "@exodus/solidity-contract": "^1.1.3",
39
+ "@exodus/solidity-contract": "^1.3.0",
40
40
  "@exodus/web3-ethereum-utils": "^4.2.1",
41
41
  "bn.js": "^5.2.1",
42
42
  "delay": "^4.0.1",
@@ -66,5 +66,5 @@
66
66
  "type": "git",
67
67
  "url": "git+https://github.com/ExodusMovement/assets.git"
68
68
  },
69
- "gitHead": "a3e06b6b731dd294513b514786db037f874ced76"
69
+ "gitHead": "e54f136a2e7ff694cd12dc1a7b0ef34eb707ef0e"
70
70
  }
package/src/ens/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { keccak256 } from '@exodus/crypto/keccak'
1
+ import { hashSync } from '@exodus/crypto/hash'
2
2
  import { ABI } from '@exodus/ethereum-lib'
3
3
  import SolidityContract from '@exodus/solidity-contract'
4
4
  import idna from 'idna-uts46-hx'
@@ -24,7 +24,7 @@ function namehash(inputName) {
24
24
  const name = normalize(inputName)
25
25
  if (name) {
26
26
  for (const label of name.split('.').reverse()) {
27
- node = keccak256([node, keccak256(label)])
27
+ node = hashSync('keccak256', [node, hashSync('keccak256', label, 'uint8')], 'buffer')
28
28
  }
29
29
  }
30
30
 
package/src/tx-create.js CHANGED
@@ -8,6 +8,7 @@ import { ensureSaneEip1559GasPriceForTipGasPrice } from './fee-utils.js'
8
8
  import { ARBITRARY_ADDRESS, fetchGasLimit, resolveDefaultTxInput } from './gas-estimation.js'
9
9
  import { getExtraFeeData, getFeeFactoryGasPrices } from './get-fee.js'
10
10
  import { getNftArguments } from './nft-utils.js'
11
+ import { getHighestIncentivePendingTxByNonce } from './tx-log/index.js'
11
12
 
12
13
  async function createUnsignedTxWithFees({
13
14
  asset,
@@ -134,6 +135,7 @@ const createBumpUnsignedTx = async ({
134
135
  const txToAddress = isToken ? asset.contract.address : toAddress
135
136
  const coinAmount = (replacedTokenTx || replacedTx).coinAmount.negate()
136
137
  const gasLimit = replacedTx.data.gasLimit
138
+ const nonce = replacedTx.data.nonce
137
139
 
138
140
  const value = isToken ? baseAsset.currency.ZERO : coinAmount
139
141
 
@@ -143,9 +145,27 @@ const createBumpUnsignedTx = async ({
143
145
  eip1559Enabled,
144
146
  tipGasPrice: currentTipGasPrice,
145
147
  } = feeData
148
+
149
+ const maybeHighestIncentivePendingTxForNonce = await getHighestIncentivePendingTxByNonce({
150
+ assetClientInterface,
151
+ asset,
152
+ nonce,
153
+ walletAccount,
154
+ })
155
+
156
+ assert(
157
+ maybeHighestIncentivePendingTxForNonce,
158
+ `unable to resolve pending transaction for nonce ${nonce}`
159
+ )
160
+
146
161
  const { bumpedGasPrice, bumpedTipGasPrice } = calculateBumpedGasPrice({
147
162
  baseAsset,
148
- tx: replacedTx,
163
+ // HACK: Although the `bumpTxId` defines the characteristics of
164
+ // of a transaction the user would like to accelerate, for
165
+ // acceleration to be successful, the transaction must be
166
+ // priced to exceed the miner incentive of whichever
167
+ // transaction is currently pending at the specified nonce.
168
+ tx: maybeHighestIncentivePendingTxForNonce,
149
169
  currentGasPrice,
150
170
  currentBaseFee,
151
171
  currentTipGasPrice,
@@ -153,7 +173,6 @@ const createBumpUnsignedTx = async ({
153
173
  })
154
174
  const gasPrice = bumpedGasPrice
155
175
  const tipGasPrice = bumpedTipGasPrice
156
- const nonce = replacedTx.data.nonce
157
176
  const data = replacedTokenTx
158
177
  ? asset.contract.transfer.build(toAddress.toLowerCase(), coinAmount.toBaseString())
159
178
  : replacedTx.data.data || '0x'
@@ -0,0 +1,130 @@
1
+ import { isNumberUnit } from '@exodus/currency'
2
+ import { isEthereumLikeToken } from '@exodus/ethereum-lib'
3
+ import assert from 'minimalistic-assert'
4
+
5
+ // Returns the most competitively priced pending
6
+ // transaction from the `TxLog` for a given `nonce`.
7
+ export const getHighestIncentivePendingTxByNonce = async ({
8
+ assetClientInterface,
9
+ asset,
10
+ nonce,
11
+ walletAccount,
12
+ }) => {
13
+ assert(assetClientInterface, 'expected assetClientInterface')
14
+ assert(asset, 'expected asset')
15
+ assert(Number.isInteger(nonce), 'expected integer nonce')
16
+ assert(walletAccount, 'expected walletAccount')
17
+
18
+ // https://github.com/ExodusMovement/assets/blob/fbe3702861cba3b21885a65b15f038fcd8541891/shield/asset-lib/src/balances-utils.js#L26
19
+ const isUnconfirmed = (tx) => !tx.failed && tx.pending
20
+
21
+ const [maybeHighestIncentiveTx] = [
22
+ ...(await assetClientInterface.getTxLog({ assetName: asset.name, walletAccount })),
23
+ ]
24
+ .filter(isUnconfirmed)
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
+ return maybeHighestIncentiveTx
29
+ }
30
+
31
+ export const getOptimisticTxLogEffects = async ({
32
+ asset,
33
+ amount = asset.currency.ZERO,
34
+ assetClientInterface,
35
+ confirmations = 0,
36
+ feeAmount,
37
+ fromAddress,
38
+ gasLimit,
39
+ nonce,
40
+ txId,
41
+ toAddress,
42
+ tipGasPrice: maybeTipGasPrice,
43
+ walletAccount,
44
+ }) => {
45
+ assert(isNumberUnit(amount), 'expected NumberUnit amount')
46
+ assert(asset, 'expected asset')
47
+ assert(assetClientInterface, 'expected assetClientInterface')
48
+ assert(Number.isInteger(confirmations), 'expected integer confirmations')
49
+ assert(isNumberUnit(feeAmount), 'expected feeAmount')
50
+ assert(typeof fromAddress === 'string', 'expected string fromAddress')
51
+ assert(Number.isInteger(gasLimit), 'expected integer gasLimit')
52
+ assert(Number.isInteger(nonce), 'expected integer nonce')
53
+ assert(typeof toAddress === 'string', 'expected string toAddress')
54
+ assert(txId, 'expected txId')
55
+ assert(walletAccount, 'expected walletAccount')
56
+
57
+ if (maybeTipGasPrice) assert(isNumberUnit(maybeTipGasPrice), 'expected NumberUnit tipGasPrice')
58
+
59
+ const baseAsset = asset.baseAsset
60
+
61
+ // TODO: This is incorrect for token transfers.
62
+ const selfSend = fromAddress.toLowerCase() === toAddress.toLowerCase()
63
+
64
+ assert(asset.feeAsset.name === baseAsset.name, 'inconsistent feeAsset')
65
+
66
+ const maybeTxToReplace = await getHighestIncentivePendingTxByNonce({
67
+ asset,
68
+ assetClientInterface,
69
+ nonce,
70
+ walletAccount,
71
+ })
72
+
73
+ const replacedTxId =
74
+ maybeTxToReplace && maybeTxToReplace.feeAmount.lt(feeAmount) ? maybeTxToReplace.txId : undefined
75
+
76
+ if (!replacedTxId && maybeTxToReplace) {
77
+ console.log('Attempting to replace a transaction using an insufficient fee!')
78
+ }
79
+
80
+ const sharedProps = {
81
+ confirmations,
82
+ feeAmount,
83
+ feeCoinName: asset.feeAsset.name,
84
+ selfSend,
85
+ to: toAddress,
86
+ txId,
87
+ data: {
88
+ gasLimit,
89
+ replacedTxId,
90
+ nonce,
91
+ ...(maybeTipGasPrice ? { tipGasPrice: maybeTipGasPrice.toBaseString() } : null),
92
+ },
93
+ }
94
+
95
+ const optimisticTxLogEffects = [
96
+ {
97
+ assetName: asset.name,
98
+ walletAccount,
99
+ txs: [
100
+ {
101
+ ...sharedProps,
102
+ coinAmount: selfSend ? asset.currency.ZERO : amount.abs().negate(),
103
+ coinName: asset.name,
104
+ currencies: {
105
+ [asset.name]: asset.currency,
106
+ [asset.feeAsset.name]: asset.feeAsset.currency,
107
+ },
108
+ },
109
+ ],
110
+ },
111
+ isEthereumLikeToken(asset) && {
112
+ assetName: baseAsset.name,
113
+ walletAccount,
114
+ txs: [
115
+ {
116
+ ...sharedProps,
117
+ coinAmount: baseAsset.currency.ZERO,
118
+ coinName: baseAsset.name,
119
+ currencies: {
120
+ [baseAsset.name]: baseAsset.currency,
121
+ [asset.feeAsset.name]: asset.feeAsset.currency,
122
+ },
123
+ tokens: [asset.name],
124
+ },
125
+ ],
126
+ },
127
+ ].filter(Boolean)
128
+
129
+ return { optimisticTxLogEffects }
130
+ }
@@ -1,3 +1,7 @@
1
1
  export { EthereumMonitor } from './ethereum-monitor.js'
2
2
  export { EthereumNoHistoryMonitor } from './ethereum-no-history-monitor.js'
3
3
  export { ClarityMonitor } from './clarity-monitor.js'
4
+ export {
5
+ getOptimisticTxLogEffects,
6
+ getHighestIncentivePendingTxByNonce,
7
+ } from './get-optimistic-txlog-effects.js'
@@ -1,14 +1,10 @@
1
- import {
2
- isEthereumLikeToken,
3
- normalizeTxId,
4
- parseUnsignedTx,
5
- updateNonce,
6
- } from '@exodus/ethereum-lib'
1
+ import { normalizeTxId, parseUnsignedTx, updateNonce } from '@exodus/ethereum-lib'
7
2
  import assert from 'minimalistic-assert'
8
3
 
9
4
  import * as ErrorWrapper from '../error-wrapper.js'
10
5
  import { transactionExists } from '../eth-like-util.js'
11
6
  import { ARBITRARY_ADDRESS } from '../gas-estimation.js'
7
+ import { getOptimisticTxLogEffects } from '../tx-log/index.js'
12
8
 
13
9
  const txSendFactory = ({ assetClientInterface, createTx }) => {
14
10
  assert(assetClientInterface, 'assetClientInterface is required')
@@ -25,7 +21,6 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
25
21
  }
26
22
 
27
23
  return async ({ asset, walletAccount, unsignedTx: providedUnsignedTx, ...legacyParams }) => {
28
- const assetName = asset.name
29
24
  const baseAsset = asset.baseAsset
30
25
 
31
26
  const resolveUnsignedTx = async () => {
@@ -57,16 +52,13 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
57
52
  const tipGasPrice = parsedTx.tipGasPrice
58
53
  const gasLimit = parsedTx.gasLimit
59
54
  const amount = parsedTx.amount
60
- const to = parsedTx.to
61
- const eip1559Enabled = parsedTx.eip1559Enabled
55
+ const toAddress = parsedTx.to
62
56
 
63
57
  // unknown data from buffer...
64
58
  const fromAddress = unsignedTx.txMeta.fromAddress
65
- const selfSend = fromAddress === to
66
- const replacedTxId = unsignedTx.txMeta.bumpTxId
67
59
 
68
60
  assert(
69
- to.toLowerCase() !== ARBITRARY_ADDRESS,
61
+ toAddress.toLowerCase() !== ARBITRARY_ADDRESS,
70
62
  `The receiving wallet address must not be ${ARBITRARY_ADDRESS}`
71
63
  )
72
64
 
@@ -154,64 +146,23 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
154
146
  }
155
147
  }
156
148
 
157
- const txData = eip1559Enabled
158
- ? {
159
- gasLimit,
160
- replacedTxId,
161
- nonce,
162
- ...(tipGasPrice ? { tipGasPrice: tipGasPrice.toBaseString() } : Object.create(null)),
163
- }
164
- : {
165
- gasLimit,
166
- replacedTxId,
167
- nonce,
168
- }
169
-
170
- await assetClientInterface.updateTxLogAndNotify({
171
- assetName: asset.name,
149
+ const { optimisticTxLogEffects } = await getOptimisticTxLogEffects({
150
+ amount,
151
+ asset,
152
+ assetClientInterface,
153
+ feeAmount,
154
+ fromAddress,
155
+ gasLimit,
156
+ nonce,
157
+ txId,
158
+ toAddress,
159
+ tipGasPrice,
172
160
  walletAccount,
173
- txs: [
174
- {
175
- txId,
176
- confirmations: 0,
177
- coinAmount: selfSend ? asset.currency.ZERO : amount.abs().negate(),
178
- coinName: asset.name,
179
- feeAmount,
180
- feeCoinName: asset.feeAsset.name,
181
- selfSend,
182
- to,
183
- currencies: {
184
- [assetName]: asset.currency,
185
- [asset.feeAsset.name]: asset.feeAsset.currency,
186
- },
187
- data: txData,
188
- },
189
- ],
190
161
  })
191
162
 
192
- const isToken = isEthereumLikeToken(asset)
193
- if (isToken) {
194
- await assetClientInterface.updateTxLogAndNotify({
195
- assetName: baseAsset.name,
196
- walletAccount,
197
- txs: [
198
- {
199
- txId,
200
- coinAmount: baseAsset.currency.ZERO,
201
- coinName: baseAsset.name,
202
- feeAmount,
203
- feeCoinName: baseAsset.name,
204
- selfSend,
205
- to,
206
- token: asset.name,
207
- currencies: {
208
- [baseAsset.name]: baseAsset.currency,
209
- [asset.feeAsset.name]: asset.feeAsset.currency,
210
- },
211
- data: txData,
212
- },
213
- ],
214
- })
163
+ // NOTE: `optimisticTxLogEffects` **must** be written sequentially.
164
+ for (const optimisticTxLogEffect of optimisticTxLogEffects) {
165
+ await assetClientInterface.updateTxLogAndNotify(optimisticTxLogEffect)
215
166
  }
216
167
 
217
168
  return { txId, nonce }