@exodus/ethereum-lib 5.10.1 → 5.11.0

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,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.11.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-lib@5.10.2...@exodus/ethereum-lib@5.11.0) (2025-05-29)
7
+
8
+
9
+ ### Features
10
+
11
+
12
+ * feat: import eth-sig-util sign-typed-data, with fixes (#5716)
13
+
14
+ * feat: unify ethereum signMessage with signMessageWithSigner (#5709)
15
+
16
+
17
+
18
+ ## [5.10.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-lib@5.10.1...@exodus/ethereum-lib@5.10.2) (2025-04-29)
19
+
20
+ **Note:** Version bump only for package @exodus/ethereum-lib
21
+
22
+
23
+
24
+
25
+
6
26
  ## [5.10.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-lib@5.10.0...@exodus/ethereum-lib@5.10.1) (2025-04-15)
7
27
 
8
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-lib",
3
- "version": "5.10.1",
3
+ "version": "5.11.0",
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",
@@ -22,12 +22,12 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@exodus/basic-utils": "^3.0.1",
25
+ "@exodus/crypto": "^1.0.0-rc.18",
25
26
  "@exodus/currency": "^6.0.1",
26
- "@exodus/ethereumjs": "^1.2.0",
27
+ "@exodus/ethereumjs": "^1.5.0",
27
28
  "@exodus/key-utils": "^3.7.0",
28
29
  "@exodus/models": "^12.0.1",
29
30
  "@exodus/solidity-contract": "^1.1.3",
30
- "@metamask/eth-sig-util": "^4.0.1",
31
31
  "base-x": "^3.0.2",
32
32
  "lodash": "^4.17.15",
33
33
  "minimalistic-assert": "^1.0.1",
@@ -38,7 +38,6 @@
38
38
  "@exodus/assets": "^11.0.0",
39
39
  "@exodus/bitcoin-meta": "^2.0.0",
40
40
  "@exodus/bsc-meta": "^2.0.0",
41
- "@exodus/crypto": "^1.0.0-rc.13",
42
41
  "@exodus/ethereum-meta": "^2.5.0",
43
42
  "@exodus/ethereumclassic-meta": "^2.0.0",
44
43
  "@exodus/fantommainnet-meta": "^2.0.0",
@@ -51,5 +50,5 @@
51
50
  "type": "git",
52
51
  "url": "git+https://github.com/ExodusMovement/assets.git"
53
52
  },
54
- "gitHead": "ecb4bcc6088186f382a07550df989646486b1ca6"
53
+ "gitHead": "45be970be70f9211b8a6489566c352f4caeb82dc"
55
54
  }
@@ -11,5 +11,6 @@ export const ethStakeAccountState = ({ currency }) => {
11
11
  pendingDepositedBalance: currency.ZERO,
12
12
  totalRewardsReceived: currency.ZERO,
13
13
  lastStakingTxProcessed: '',
14
+ activeStakedBalance: currency.ZERO,
14
15
  }
15
16
  }
@@ -1,3 +1,4 @@
1
+ import assert from 'minimalistic-assert'
1
2
  import ms from 'ms'
2
3
  import { createSelector } from 'reselect'
3
4
 
@@ -85,20 +86,136 @@ const wrapResponseToObject = ({ bumpType = BumpType.NONE, errorMessage = null }
85
86
 
86
87
  const calculateTxGasPrice = (tx) => tx.feeAmount.div(tx.data.gasLimit)
87
88
 
88
- export const calculateBumpedGasPrice = ({ baseAsset, tx, currentGasPrice, eip1559Enabled }) => {
89
- const usedGasPrice = calculateTxGasPrice(tx)
90
- const gasPrice = currentGasPrice.gt(usedGasPrice) ? currentGasPrice : usedGasPrice
91
- const bumpedGasPrice = gasPrice.mul(BUMP_RATE)
92
-
93
- return eip1559Enabled && tx.data?.tipGasPrice
94
- ? {
95
- bumpedTipGasPrice: baseAsset.currency
96
- .baseUnit(tx.data.tipGasPrice)
97
- .to('Gwei')
98
- .mul(BUMP_RATE),
99
- bumpedGasPrice,
100
- }
101
- : { bumpedGasPrice }
89
+ const calculateBumpedGasPriceNonEip1559 = ({ currentGasPrice, prevMaxFeePerGas }) => {
90
+ assert(currentGasPrice, 'currentGasPrice is required')
91
+ assert(prevMaxFeePerGas, 'prevMaxFeePerGas is required')
92
+
93
+ // Pick the largest between the network's `currentGasPrice`
94
+ // and the maximum `gasPrice` the previous transaction was
95
+ // willing to send at.
96
+ const nextGasPrice = currentGasPrice.gt(prevMaxFeePerGas) ? currentGasPrice : prevMaxFeePerGas
97
+
98
+ // Suggest using an amplified `gasPrice`.
99
+ return {
100
+ bumpedGasPrice: nextGasPrice.mul(BUMP_RATE),
101
+ }
102
+ }
103
+
104
+ const getLinearIncentiveBumpForEip1559Transaction = ({
105
+ currentBaseFeePerGas,
106
+ prevMaxFeePerGas,
107
+ prevMaxPriorityFeePerGas,
108
+ }) => {
109
+ assert(currentBaseFeePerGas, 'currentBaseFeePerGas is required')
110
+ assert(prevMaxFeePerGas, 'prevMaxFeePerGas is required')
111
+ assert(prevMaxPriorityFeePerGas, 'prevMaxPriorityFeePerGas is required')
112
+
113
+ // First, let's determine if there's a bump required just to bring
114
+ // the transaction up to the `baseFeePerGas`. If the current base
115
+ // fee is larger, we'll need to see how much to adjust the overall
116
+ // transaction by.
117
+ const increaseToBaseFeePerGasTipGasPrice = currentBaseFeePerGas.gt(prevMaxFeePerGas)
118
+ ? currentBaseFeePerGas.sub(prevMaxFeePerGas)
119
+ : currentBaseFeePerGas.sub(currentBaseFeePerGas) // TODO: What is a nice way to get ZERO without an `asset`? Is it `currentBaseFeePerGas.ZERO`?
120
+
121
+ // The new `bumpedTipGasPrice`.
122
+ const bumpedTipGasPrice = prevMaxPriorityFeePerGas
123
+ .mul(BUMP_RATE)
124
+ .add(increaseToBaseFeePerGasTipGasPrice)
125
+
126
+ // We'll make attempt a linear increase of the transaction pricing. By
127
+ // increasing the `tipGasPrice` and the `gasPrice` by the same amount,
128
+ // we ensuret he any additional amount we spend (over the basefee) goes
129
+ // directly to the miner, thereby increasing transaction incentive.
130
+ return {
131
+ bumpedGasPrice: prevMaxFeePerGas
132
+ .sub(prevMaxPriorityFeePerGas) // Removes the previous `tipGasPrice` before adding the next one (`bumpedTipGasPrice` is inclusive).
133
+ .add(bumpedTipGasPrice),
134
+ bumpedTipGasPrice,
135
+ }
136
+ }
137
+
138
+ const calculateBumpedGasPriceEip1559 = ({
139
+ currentBaseFeePerGas,
140
+ prevMaxFeePerGas,
141
+ prevMaxPriorityFeePerGas,
142
+ }) => {
143
+ assert(currentBaseFeePerGas, 'currentBaseFeePerGas is required')
144
+ assert(prevMaxFeePerGas, 'prevMaxFeePerGas is required')
145
+
146
+ // If the previous transaction didn't define a `tipGasPrice`, we
147
+ // can use this value directly.
148
+ if (!prevMaxPriorityFeePerGas || prevMaxPriorityFeePerGas.isZero) {
149
+ // We can determine the intended transaction bump using
150
+ // pre EIP-1559 logic, where we compare the `currentBaseFeePerGas`
151
+ // (i.e. the network gas price without a miner incentive) versus
152
+ // the `currentMaxFeePerGas` (which we can the previous `gasPrice`
153
+ // for the transaction). In this regard, the logic of finding the
154
+ // `bumpedGasPrice` to increase to is identical.
155
+ const { bumpedGasPrice } = calculateBumpedGasPriceNonEip1559({
156
+ currentGasPrice: currentBaseFeePerGas,
157
+ prevMaxFeePerGas,
158
+ })
159
+
160
+ assert(bumpedGasPrice >= currentBaseFeePerGas, 'bumpedGasPrice must be >= currentBaseFeePerGas')
161
+
162
+ // Since `calculateBumpedGasPriceNonEip1559` will guarantee that
163
+ // the returned `bumpedGasPrice` will be greater than the
164
+ // `currentBaseFeePerGas` (and the `currentMaxFeePerGas`), we can
165
+ // use their difference to deterime the equivalent `bumpTipGasPrice`.
166
+ const bumpedTipGasPrice = bumpedGasPrice.sub(currentBaseFeePerGas)
167
+
168
+ return {
169
+ bumpedGasPrice: currentBaseFeePerGas.add(bumpedTipGasPrice),
170
+ bumpedTipGasPrice,
171
+ }
172
+ }
173
+
174
+ // Else, let's make a linear increase to the transaction tip. This
175
+ // increase should ensure that:
176
+ // 1. The `prevMaxPriorityFeePerGas` is multiplied by the `BUMP_RATE`.
177
+ // 2. The returned `bumpedGasPrice` is sufficient for the
178
+ // `currentBaseFeePerGas`.
179
+ return getLinearIncentiveBumpForEip1559Transaction({
180
+ currentBaseFeePerGas,
181
+ prevMaxFeePerGas,
182
+ prevMaxPriorityFeePerGas,
183
+ })
184
+ }
185
+
186
+ export const calculateBumpedGasPrice = ({
187
+ baseAsset,
188
+ tx,
189
+ currentBaseFee,
190
+ currentGasPrice,
191
+ eip1559Enabled,
192
+ }) => {
193
+ // Determine the transaction we intend to override's
194
+ // `maxFeePerGas` (for pre-EIP-1559 networks, this is the
195
+ // `gasPrice` of the previous transaction).
196
+ const prevMaxFeePerGas = calculateTxGasPrice(tx)
197
+
198
+ if (!eip1559Enabled)
199
+ return calculateBumpedGasPriceNonEip1559({
200
+ currentGasPrice,
201
+ prevMaxFeePerGas,
202
+ })
203
+
204
+ if (!currentBaseFee) {
205
+ // NOTE: This can result in differences between predicted and realized fees.
206
+ currentBaseFee = currentGasPrice
207
+ }
208
+
209
+ const prevMaxPriorityFeePerGas = tx.data?.tipGasPrice
210
+ ? baseAsset.currency.baseUnit(tx.data.tipGasPrice)
211
+ : null
212
+
213
+ return calculateBumpedGasPriceEip1559({
214
+ baseAsset,
215
+ currentBaseFeePerGas: currentBaseFee,
216
+ prevMaxPriorityFeePerGas,
217
+ prevMaxFeePerGas,
218
+ })
102
219
  }
103
220
 
104
221
  export const canAccelerateTx = ({
@@ -130,19 +247,23 @@ export const canAccelerateTx = ({
130
247
  if (isQueuedPendingTx(tx, baseAssetName, activeWalletAccount, getTxLog))
131
248
  return wrapResponseToObject({ errorMessage: 'there is a stuck TX with lower nonce' })
132
249
 
133
- const { gasPrice: currentGasPrice, eip1559Enabled } = getFeeData(assetName)
134
-
135
- if (calculateTxGasPrice(tx).gte(currentGasPrice))
136
- return wrapResponseToObject({ errorMessage: 'the used gas price is still high enough' })
250
+ const {
251
+ gasPrice: currentGasPrice,
252
+ eip1559Enabled,
253
+ baseFeePerGas: currentBaseFee,
254
+ } = getFeeData(assetName)
137
255
 
138
256
  const { bumpedGasPrice: gasPriceToUse } = calculateBumpedGasPrice({
139
257
  baseAsset,
140
258
  tx,
141
259
  currentGasPrice,
260
+ currentBaseFee,
142
261
  eip1559Enabled,
143
262
  })
144
263
  const replacementFee = gasPriceToUse.mul(tx.data.gasLimit)
264
+
145
265
  const extraEthNeeded = replacementFee.sub(tx.feeAmount)
266
+
146
267
  if (!getIsEnoughBalanceToAccelerate(activeWalletAccount, baseAssetName, extraEthNeeded))
147
268
  return wrapResponseToObject({ errorMessage: 'insufficient funds' })
148
269
 
@@ -1,11 +1,10 @@
1
- import { hashPersonalMessage } from '@exodus/ethereumjs/util'
1
+ import { ecdsaSignHash } from '@exodus/crypto/secp256k1'
2
2
  import {
3
- personalSign,
4
- signTypedData,
5
3
  SignTypedDataVersion,
6
4
  TypedDataUtils,
7
5
  typedSignatureHash,
8
- } from '@metamask/eth-sig-util'
6
+ } from '@exodus/ethereumjs/eth-sig-util'
7
+ import { hashPersonalMessage } from '@exodus/ethereumjs/util'
9
8
  import assert from 'minimalistic-assert'
10
9
 
11
10
  import { normalizeRecoveryParam } from './utils/ecdsa.js'
@@ -19,41 +18,19 @@ function hex0xStringToBuffer(hex) {
19
18
  return Buffer.from(hexWithLeadingZeros, 'hex')
20
19
  }
21
20
 
22
- /**
23
- * Converts a buffer to the 0xhex encoded value
24
- * @param {Buffer} buf the buffer to convert to 0xHEXSTRING
25
- * @returns the hex-encoded value prepended with 0x
26
- */
27
- function bufferToHex0xString(buf) {
28
- if (!Buffer.isBuffer(buf)) {
29
- throw new TypeError('expected a buffer')
30
- }
31
-
32
- const hex = buf.toString('hex')
33
-
34
- if (typeof hex !== 'string') {
35
- throw new TypeError('expected hex string')
36
- }
37
-
38
- return '0x' + hex
39
- }
40
-
41
- export const signMessage = async ({ privateKey, message }) => {
42
- const { rawMessage, EIP712Message } = message
43
-
44
- // Exclusive OR (XOR)
45
- assert(!!rawMessage !== !!EIP712Message, 'Need either rawMessage or EIP712Message')
46
-
47
- if (rawMessage) {
48
- return hex0xStringToBuffer(personalSign({ privateKey, data: bufferToHex0xString(rawMessage) }))
49
- }
50
-
51
- if (EIP712Message) {
52
- const version = Array.isArray(EIP712Message) ? SignTypedDataVersion.V1 : SignTypedDataVersion.V4
53
-
54
- return hex0xStringToBuffer(signTypedData({ privateKey, data: EIP712Message, version }))
55
- }
56
- }
21
+ export const signMessage = async ({ privateKey, message }) =>
22
+ signMessageWithSigner({
23
+ message,
24
+ signer: {
25
+ sign: async ({ data }) =>
26
+ ecdsaSignHash({
27
+ hash: data,
28
+ privateKey,
29
+ recovery: true,
30
+ extraEntropy: null, // TODO: can we flip this to true?
31
+ }),
32
+ },
33
+ })
57
34
 
58
35
  /**
59
36
  * @param {object} params