@exodus/ethereum-lib 5.20.0 → 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 +20 -0
- package/package.json +2 -2
- package/src/constants.js +3 -0
- package/src/selectors/get-can-accelerate-tx-factory.js +115 -24
- package/src/selectors/index.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
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
|
+
|
|
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)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
* fix: EIP1559 acceleration gas calculation in (#6997). Update getLinearIncentiveBumpForEip1559Transaction() to properly calculate bump tx logic under eip1559
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
## [5.20.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-lib@5.18.5...@exodus/ethereum-lib@5.20.0) (2025-11-19)
|
|
7
27
|
|
|
8
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-lib",
|
|
3
|
-
"version": "5.20.
|
|
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": "
|
|
54
|
+
"gitHead": "8c3e94c96818b1ce8ce10cb75da29db2c9fe9ab9"
|
|
55
55
|
}
|
package/src/constants.js
CHANGED
|
@@ -136,3 +136,6 @@ export const CONFIRMATIONS_NUMBER = mapValues(
|
|
|
136
136
|
export const ETHEREUM_LIKE_ASSETS = Object.keys(CHAIN_DATA)
|
|
137
137
|
|
|
138
138
|
export const BUMP_RATE = 1.2
|
|
139
|
+
// Node bump requirement (10% as enforced by Geth/Erigon/Nethermind)
|
|
140
|
+
// Client always bumps by 20% (BUMP_RATE) to exceed this threshold
|
|
141
|
+
export const NODE_BUMP_REQUIREMENT = 1.1
|
|
@@ -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()')
|
|
@@ -103,37 +154,63 @@ const calculateBumpedGasPriceNonEip1559 = ({ currentGasPrice, prevMaxFeePerGas }
|
|
|
103
154
|
}
|
|
104
155
|
}
|
|
105
156
|
|
|
106
|
-
|
|
157
|
+
/*
|
|
158
|
+
Node Rules:
|
|
159
|
+
GETH:
|
|
160
|
+
The EIP1559 transaction bump logic requires that a replacement transaction must have BOTH:
|
|
161
|
+
1. GasFeeCap >= oldGasFeeCap × (100 + priceBump) / 100
|
|
162
|
+
2. GasTipCap >= oldGasTipCap × (100 + priceBump) / 100
|
|
163
|
+
ERIGON:
|
|
164
|
+
The Erigon EIP1559 transaction bump logic requires that a replacement transaction must have BOTH:
|
|
165
|
+
1. `Tip >= oldTip × (100 + priceBump) / 100`
|
|
166
|
+
2. `FeeCap >= oldFeeCap × (100 + priceBump) / 100`
|
|
167
|
+
NETHERMIND:
|
|
168
|
+
Nethermind's EIP1559 transaction bump logic requires that a replacement transaction must have BOTH:
|
|
169
|
+
### Regular Transactions (10% bump)
|
|
170
|
+
1. `newMaxFeePerGas > oldMaxFeePerGas × 1.1`
|
|
171
|
+
2. `newMaxPriorityFeePerGas > oldMaxPriorityFeePerGas × 1.1`
|
|
172
|
+
|
|
173
|
+
In short, all these 3 nodes require that the bump is applied to both the tip and the cap.
|
|
174
|
+
This won't work for besu nodes.
|
|
175
|
+
*/
|
|
176
|
+
export const getLinearIncentiveBumpForEip1559Transaction = ({
|
|
107
177
|
currentBaseFeePerGas,
|
|
108
|
-
prevMaxFeePerGas,
|
|
109
|
-
prevMaxPriorityFeePerGas,
|
|
178
|
+
prevMaxFeePerGas, // Previous maxFeePerGas (includes the old tip)
|
|
179
|
+
prevMaxPriorityFeePerGas, // Previous tip (maxPriorityFeePerGas)
|
|
110
180
|
}) => {
|
|
111
181
|
assert(currentBaseFeePerGas, 'currentBaseFeePerGas is required')
|
|
112
182
|
assert(prevMaxFeePerGas, 'prevMaxFeePerGas is required')
|
|
113
183
|
assert(prevMaxPriorityFeePerGas, 'prevMaxPriorityFeePerGas is required')
|
|
114
184
|
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
//
|
|
131
|
-
//
|
|
185
|
+
//
|
|
186
|
+
// ---- STEP 1: Geth / Erigon / Nethermind "pricebump" policy ----
|
|
187
|
+
//
|
|
188
|
+
// These clients require that BOTH the tip AND the cap are bumped by
|
|
189
|
+
// the configured percentage (BUMP_RATE), compared to the previous tx:
|
|
190
|
+
//
|
|
191
|
+
// newTip >= prevTip * BUMP_RATE AND
|
|
192
|
+
// newMaxFee >= prevMaxFee * BUMP_RATE
|
|
193
|
+
//
|
|
194
|
+
// Both conditions must be satisfied independently for the replacement
|
|
195
|
+
// transaction to be accepted by the node's mempool.
|
|
196
|
+
//
|
|
197
|
+
const newTip = prevMaxPriorityFeePerGas.mul(BUMP_RATE)
|
|
198
|
+
let newMaxFee = prevMaxFeePerGas.mul(BUMP_RATE)
|
|
199
|
+
|
|
200
|
+
//
|
|
201
|
+
// ---- STEP 2: EIP-1559 validity "now" ----
|
|
202
|
+
//
|
|
203
|
+
// EIP-1559 requires maxFeePerGas >= baseFee + tip for the tx
|
|
204
|
+
// to be able to actually pay the full priority fee at the current baseFee.
|
|
205
|
+
//
|
|
206
|
+
const minCapRequiredNow = currentBaseFeePerGas.add(newTip)
|
|
207
|
+
if (newMaxFee.lt(minCapRequiredNow)) {
|
|
208
|
+
newMaxFee = minCapRequiredNow
|
|
209
|
+
}
|
|
210
|
+
|
|
132
211
|
return {
|
|
133
|
-
bumpedGasPrice:
|
|
134
|
-
|
|
135
|
-
.add(bumpedTipGasPrice),
|
|
136
|
-
bumpedTipGasPrice,
|
|
212
|
+
bumpedGasPrice: newMaxFee,
|
|
213
|
+
bumpedTipGasPrice: newTip,
|
|
137
214
|
}
|
|
138
215
|
}
|
|
139
216
|
|
|
@@ -322,6 +399,20 @@ export const canAccelerateTx = ({
|
|
|
322
399
|
return wrapResponseToObject({ errorMessage: 'there is a stuck TX with lower nonce' })
|
|
323
400
|
}
|
|
324
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
|
+
|
|
325
416
|
const {
|
|
326
417
|
gasPrice: currentGasPrice,
|
|
327
418
|
eip1559Enabled,
|
package/src/selectors/index.js
CHANGED
|
@@ -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'
|