@exodus/ethereum-api 8.34.2 → 8.34.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 +10 -0
- package/package.json +2 -2
- package/src/create-asset.js +1 -2
- package/src/custom-fees.js +40 -10
- package/src/fee-utils.js +6 -3
- package/src/get-fee.js +7 -18
- package/src/staking/ethereum/service.js +2 -12
- package/src/staking/matic/service.js +0 -2
- package/src/staking/utils/index.js +8 -38
- package/src/tx-send/get-fee-info.js +13 -1
- package/src/tx-send/tx-send.js +16 -3
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.34.3](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.34.2...@exodus/ethereum-api@8.34.3) (2025-04-29)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* fix: prevent insufficient evm custom fees on eip1559Enabled networks (#5519)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
6
16
|
## [8.34.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.34.1...@exodus/ethereum-api@8.34.2) (2025-04-18)
|
|
7
17
|
|
|
8
18
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.34.
|
|
3
|
+
"version": "8.34.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",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"type": "git",
|
|
65
65
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "5513269ef920bfdd91701a8108fe73336d105866"
|
|
68
68
|
}
|
package/src/create-asset.js
CHANGED
|
@@ -28,7 +28,7 @@ import { createEvmServer, ValidMonitorTypes } from './exodus-eth-server/index.js
|
|
|
28
28
|
import { createFeeData } from './fee-data-factory.js'
|
|
29
29
|
import { createGetBalanceForAddress } from './get-balance-for-address.js'
|
|
30
30
|
import { getBalancesFactory } from './get-balances.js'
|
|
31
|
-
import {
|
|
31
|
+
import { getFeeFactory } from './get-fee.js'
|
|
32
32
|
import getFeeAsyncFactory from './get-fee-async.js'
|
|
33
33
|
import { createEthereumHooks } from './hooks/index.js'
|
|
34
34
|
import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas/index.js'
|
|
@@ -323,7 +323,6 @@ export const createAssetFactory = ({
|
|
|
323
323
|
server,
|
|
324
324
|
...(erc20FuelBuffer && { erc20FuelBuffer }),
|
|
325
325
|
...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
|
|
326
|
-
getEffectiveGasPrice,
|
|
327
326
|
}
|
|
328
327
|
return overrideCallback({
|
|
329
328
|
asset: fullAsset,
|
package/src/custom-fees.js
CHANGED
|
@@ -2,21 +2,51 @@ import NumberUnit from '@exodus/currency'
|
|
|
2
2
|
|
|
3
3
|
import { resolveGasPrice } from './fee-utils.js'
|
|
4
4
|
|
|
5
|
+
const calculateMinCustomFee = ({ feeData }) => {
|
|
6
|
+
const { eip1559Enabled, baseFeePerGas, gasPriceMinimumRate } = feeData
|
|
7
|
+
|
|
8
|
+
// On `eip1559Enabled` networks, the `baseFeePerGas` is
|
|
9
|
+
// the minimum a transaction should be sent using, else
|
|
10
|
+
// get rejected by the RPC.
|
|
11
|
+
if (eip1559Enabled && baseFeePerGas) return baseFeePerGas.mul(Math.max(gasPriceMinimumRate, 1))
|
|
12
|
+
|
|
13
|
+
// On legacy networks, senders are permitted to send
|
|
14
|
+
// transactions below the `baseFeePerGas` (since these
|
|
15
|
+
// would not be rejected by the RPC - in fact, it was
|
|
16
|
+
// even possible to mine transactions with a `gasPrice`
|
|
17
|
+
// of `0`!).
|
|
18
|
+
return resolveGasPrice({ feeData }).mul(gasPriceMinimumRate)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const calculateRecommendedCustomFee = ({ min, feeData }) => {
|
|
22
|
+
const recommended = resolveGasPrice({ feeData })
|
|
23
|
+
// The `recommended` fee must be at least the `min`.
|
|
24
|
+
return recommended.gt(min) ? recommended : min
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const calculateMaxCustomFee = ({ feeData, recommended }) => {
|
|
28
|
+
const { gasPriceMinimumRate, gasPriceMaximumRate } = feeData
|
|
29
|
+
|
|
30
|
+
const maxGasPrice = resolveGasPrice({ feeData }).mul(
|
|
31
|
+
Math.max(gasPriceMaximumRate, gasPriceMinimumRate)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// When calculating the `max` custom fee, the returned fee
|
|
35
|
+
// must be at least the `recommended` price.
|
|
36
|
+
return recommended.gt(maxGasPrice) ? recommended : maxGasPrice
|
|
37
|
+
}
|
|
38
|
+
|
|
5
39
|
export const createCustomFeesApi = ({ baseAsset }) => {
|
|
6
40
|
const Gwei = baseAsset.currency.units.Gwei
|
|
7
41
|
return {
|
|
8
42
|
getRecommendedMinMaxFeeUnitPrices: ({ feeData }) => {
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const calculateFeeUnitPrice = (feeUnitPrice, multiplier = 1) =>
|
|
14
|
-
feeUnitPrice.mul(multiplier).toNumber(Gwei)
|
|
15
|
-
|
|
43
|
+
const min = calculateMinCustomFee({ feeData })
|
|
44
|
+
const recommended = calculateRecommendedCustomFee({ feeData, min })
|
|
45
|
+
const max = calculateMaxCustomFee({ feeData, recommended })
|
|
16
46
|
return {
|
|
17
|
-
recommended:
|
|
18
|
-
min:
|
|
19
|
-
max:
|
|
47
|
+
recommended: recommended.toNumber(Gwei),
|
|
48
|
+
min: min.toNumber(Gwei),
|
|
49
|
+
max: max.toNumber(Gwei),
|
|
20
50
|
}
|
|
21
51
|
},
|
|
22
52
|
unit: 'gwei/gas',
|
package/src/fee-utils.js
CHANGED
|
@@ -41,9 +41,7 @@ export const rewriteFeeConfig = ({ feeAsset, feeConfig, feeData }) => {
|
|
|
41
41
|
export const resolveGasPrice = ({ feeData }) => {
|
|
42
42
|
assert(feeData, 'feeData is required')
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (eip1559Enabled && feeData.useBaseGasPrice) {
|
|
44
|
+
if (feeData.eip1559Enabled) {
|
|
47
45
|
return feeData.tipGasPrice
|
|
48
46
|
? feeData.baseFeePerGas.add(feeData.tipGasPrice)
|
|
49
47
|
: feeData.baseFeePerGas
|
|
@@ -51,3 +49,8 @@ export const resolveGasPrice = ({ feeData }) => {
|
|
|
51
49
|
|
|
52
50
|
return feeData.gasPrice
|
|
53
51
|
}
|
|
52
|
+
|
|
53
|
+
export const ensureSaneEip1559GasPriceForTipGasPrice = ({ gasPrice, tipGasPrice }) => {
|
|
54
|
+
if (!tipGasPrice || tipGasPrice.lt(gasPrice)) return gasPrice
|
|
55
|
+
return tipGasPrice
|
|
56
|
+
}
|
package/src/get-fee.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { calculateBumpedGasPrice, calculateExtraEth } from '@exodus/ethereum-lib'
|
|
2
2
|
|
|
3
|
-
import { resolveGasPrice } from './fee-utils.js'
|
|
3
|
+
import { ensureSaneEip1559GasPriceForTipGasPrice, resolveGasPrice } from './fee-utils.js'
|
|
4
4
|
|
|
5
5
|
// Move to meta?
|
|
6
6
|
const taxes = {
|
|
@@ -44,20 +44,20 @@ export const getFeeFactory =
|
|
|
44
44
|
feeData = { ...feeData, tipGasPrice: customFee }
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const { baseFeePerGas, tipGasPrice
|
|
47
|
+
const { baseFeePerGas, tipGasPrice } = feeData
|
|
48
48
|
|
|
49
49
|
let gasPrice = customFee || resolveGasPrice({ feeData })
|
|
50
50
|
|
|
51
51
|
// The `gasPrice` must be at least the `tipGasPrice`.
|
|
52
|
-
if (eip1559Enabled
|
|
53
|
-
gasPrice = tipGasPrice
|
|
52
|
+
if (eip1559Enabled) {
|
|
53
|
+
gasPrice = ensureSaneEip1559GasPriceForTipGasPrice({ gasPrice, tipGasPrice })
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const gasLimit = providedGasLimit || asset.gasLimit || defaultGasLimit
|
|
57
57
|
|
|
58
58
|
// When explicitly opting into EIP-1559 transactions,
|
|
59
59
|
// lock in the `tipGasPrice` we used to compute the fees.
|
|
60
|
-
const maybeReturnTipGasPrice = eip1559Enabled
|
|
60
|
+
const maybeReturnTipGasPrice = eip1559Enabled ? { tipGasPrice } : null
|
|
61
61
|
|
|
62
62
|
const extraFeeData = getExtraFeeData({ asset, amount })
|
|
63
63
|
if (calculateEffectiveFee && eip1559Enabled) {
|
|
@@ -78,26 +78,15 @@ export const getFeeFactory =
|
|
|
78
78
|
return { ...maybeReturnTipGasPrice, fee, gasPrice, extraFeeData }
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// Used in BE
|
|
82
|
-
export const getEffectiveGasPrice = ({ feeData }) => {
|
|
83
|
-
const { baseFeePerGas, tipGasPrice, gasPrice: maxFeePerGas, eip1559Enabled } = feeData
|
|
84
|
-
|
|
85
|
-
if (!eip1559Enabled) {
|
|
86
|
-
return maxFeePerGas
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const gasPrice = baseFeePerGas.add(tipGasPrice)
|
|
90
|
-
return gasPrice.lt(maxFeePerGas) ? gasPrice : maxFeePerGas
|
|
91
|
-
}
|
|
92
|
-
|
|
93
81
|
// Used in Mobile
|
|
94
82
|
export const getExtraFeeForBump = ({ tx, feeData, balance, unconfirmedBalance }) => {
|
|
95
83
|
if (!balance || !unconfirmedBalance) return null
|
|
96
|
-
const { gasPrice: currentGasPrice, eip1559Enabled } = feeData
|
|
84
|
+
const { gasPrice: currentGasPrice, eip1559Enabled, baseFeePerGas: currentBaseFee } = feeData
|
|
97
85
|
const { bumpedGasPrice } = calculateBumpedGasPrice({
|
|
98
86
|
baseAsset: 'ethereum',
|
|
99
87
|
tx,
|
|
100
88
|
currentGasPrice,
|
|
89
|
+
currentBaseFee,
|
|
101
90
|
eip1559Enabled,
|
|
102
91
|
})
|
|
103
92
|
return calculateExtraEth({
|
|
@@ -18,15 +18,6 @@ const SLOT_ACTIVATION_PROXIMITY_ETH = '1.0'
|
|
|
18
18
|
// ratio commonly fall under this limit.
|
|
19
19
|
const MINIMUM_DELEGATION_GAS_LIMIT = 180_000
|
|
20
20
|
|
|
21
|
-
// Use a custom `maxPriorityFeePerGas` so we can
|
|
22
|
-
// respect the default EIP-1559 relationship:
|
|
23
|
-
// maxFeePerGas = baseFeePerGas + maxPriorityFeePerGas.
|
|
24
|
-
//
|
|
25
|
-
// Currently, the backend will return an excessive
|
|
26
|
-
// `tipGasPrice` which hinders the configuration ofrational
|
|
27
|
-
// inclusion incentives when defined using remote config.
|
|
28
|
-
const MAX_PRIORITY_FEE_PER_GAS = '0.06 Gwei' // semi-urgent
|
|
29
|
-
|
|
30
21
|
const EXTRA_GAS_LIMIT = 20_000 // extra gas Limit to prevent tx failing if something change on pool state (till tx is in mempool)
|
|
31
22
|
|
|
32
23
|
const DISABLE_BALANCE_CHECKS = '0x0'
|
|
@@ -35,9 +26,9 @@ export function createEthereumStakingService({
|
|
|
35
26
|
asset,
|
|
36
27
|
assetClientInterface,
|
|
37
28
|
createWatchTx = defaultCreateWatch,
|
|
29
|
+
stakingProvider = stakingProviderClientFactory(),
|
|
38
30
|
}) {
|
|
39
|
-
const staking = new EthereumStaking(asset)
|
|
40
|
-
const stakingProvider = stakingProviderClientFactory()
|
|
31
|
+
const staking = new EthereumStaking(asset, undefined, asset.server)
|
|
41
32
|
const minAmount = staking.minAmount
|
|
42
33
|
|
|
43
34
|
async function delegate({ walletAccount, amount } = Object.create(null)) {
|
|
@@ -360,7 +351,6 @@ export function createEthereumStakingService({
|
|
|
360
351
|
amount,
|
|
361
352
|
asset,
|
|
362
353
|
assetClientInterface,
|
|
363
|
-
maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS,
|
|
364
354
|
gasLimit,
|
|
365
355
|
})
|
|
366
356
|
|
|
@@ -8,7 +8,6 @@ import { amountToCurrency, getEvmStakingServiceFee } from '../utils/index.js'
|
|
|
8
8
|
import { MaticStakingApi } from './api.js'
|
|
9
9
|
|
|
10
10
|
const DISABLE_BALANCE_CHECKS = '0x0'
|
|
11
|
-
const MAX_PRIORITY_FEE_PER_GAS = '0.06 Gwei' // semi-urgent
|
|
12
11
|
|
|
13
12
|
export function createPolygonStakingService({
|
|
14
13
|
assetClientInterface,
|
|
@@ -258,7 +257,6 @@ export function createPolygonStakingService({
|
|
|
258
257
|
amount,
|
|
259
258
|
asset: ethereum,
|
|
260
259
|
assetClientInterface,
|
|
261
|
-
maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS,
|
|
262
260
|
gasLimit,
|
|
263
261
|
})
|
|
264
262
|
}
|
|
@@ -29,45 +29,15 @@ function maybeNormalizeFeeData({ asset, feeData }) {
|
|
|
29
29
|
* to specify a custom `maxPriorityFeePerGas` whilst the current
|
|
30
30
|
* `tipGasPrice` is incompatible with EIP-1559 pricing.
|
|
31
31
|
*/
|
|
32
|
-
export async
|
|
33
|
-
|
|
34
|
-
asset,
|
|
35
|
-
assetClientInterface,
|
|
36
|
-
maxPriorityFeePerGas: maybeMaxPriorityFeePerGas,
|
|
37
|
-
gasLimit,
|
|
38
|
-
}) {
|
|
39
|
-
const defaultFeeData = await assetClientInterface.getFeeData({
|
|
40
|
-
assetName: asset.name,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
// HACK: We wish to explicitly enable `useBaseGasPrice`
|
|
44
|
-
// since we're using a custom `tipGasPrice`. This
|
|
45
|
-
// will be compatible during the transition from
|
|
46
|
-
// Magnifier to Clarity, and can be safely removed
|
|
47
|
-
// once `useBaseGasPrice` is the default behaviour
|
|
48
|
-
// (alongside the custom tip).
|
|
49
|
-
const { eip1559Enabled } = defaultFeeData
|
|
50
|
-
const useBaseGasPrice = Boolean(eip1559Enabled) && Boolean(maybeMaxPriorityFeePerGas)
|
|
51
|
-
|
|
52
|
-
const normalizedFeeData = maybeNormalizeFeeData({
|
|
32
|
+
export const getEvmStakingServiceFee = async ({ amount, asset, assetClientInterface, gasLimit }) =>
|
|
33
|
+
asset.api.getFee({
|
|
53
34
|
asset,
|
|
54
|
-
feeData: {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...(useBaseGasPrice ? { tipGasPrice: maybeMaxPriorityFeePerGas } : null),
|
|
61
|
-
useBaseGasPrice,
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// Returns `fee`, `gasPrice`, `extraFeeData`, and `tipGasPrice`
|
|
66
|
-
// if the config defines we should `useBaseGasPrice`.
|
|
67
|
-
return asset.api.getFee({
|
|
68
|
-
asset,
|
|
69
|
-
feeData: normalizedFeeData,
|
|
35
|
+
feeData: maybeNormalizeFeeData({
|
|
36
|
+
asset,
|
|
37
|
+
feeData: await assetClientInterface.getFeeData({
|
|
38
|
+
assetName: asset.name,
|
|
39
|
+
}),
|
|
40
|
+
}),
|
|
70
41
|
gasLimit,
|
|
71
42
|
amount,
|
|
72
43
|
})
|
|
73
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ensureSaneEip1559GasPriceForTipGasPrice } from '../fee-utils.js'
|
|
1
2
|
import { fetchGasLimit } from '../gas-estimation.js'
|
|
2
3
|
|
|
3
4
|
const getFeeInfo = async function getFeeInfo({
|
|
@@ -9,7 +10,11 @@ const getFeeInfo = async function getFeeInfo({
|
|
|
9
10
|
txInput,
|
|
10
11
|
feeOpts = {},
|
|
11
12
|
}) {
|
|
12
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
gasPrice: gasPrice_,
|
|
15
|
+
tipGasPrice: tipGasPrice_,
|
|
16
|
+
eip1559Enabled,
|
|
17
|
+
} = await assetClientInterface.getFeeData({
|
|
13
18
|
assetName: asset.name,
|
|
14
19
|
})
|
|
15
20
|
|
|
@@ -26,6 +31,13 @@ const getFeeInfo = async function getFeeInfo({
|
|
|
26
31
|
})
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
// HACK: If we've received an invalid combination of `tipGasPrice`
|
|
35
|
+
// (maxPriorityFeePerGas) and `gasPrice` (maxFeePerGas), then
|
|
36
|
+
// we must normalize these before returning.
|
|
37
|
+
if (eip1559Enabled) {
|
|
38
|
+
gasPrice = ensureSaneEip1559GasPriceForTipGasPrice({ gasPrice, tipGasPrice })
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
return { gasPrice, gasLimit, tipGasPrice }
|
|
30
42
|
}
|
|
31
43
|
|
package/src/tx-send/tx-send.js
CHANGED
|
@@ -117,31 +117,43 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
117
117
|
let replacedTx, replacedTokenTx
|
|
118
118
|
if (bumpTxId) {
|
|
119
119
|
replacedTx = baseAssetTxLog.get(bumpTxId)
|
|
120
|
+
|
|
120
121
|
if (!replacedTx || !replacedTx.pending) {
|
|
121
122
|
throw new Error(`Cannot bump transaction ${bumpTxId}: not found or confirmed`)
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
if (replacedTx.tokens.length > 0) {
|
|
126
|
+
const [tokenAssetName] = replacedTx.tokens
|
|
125
127
|
const tokenTxSet = await assetClientInterface.getTxLog({
|
|
126
|
-
assetName:
|
|
128
|
+
assetName: tokenAssetName,
|
|
127
129
|
walletAccount,
|
|
128
130
|
})
|
|
129
131
|
replacedTokenTx = tokenTxSet.get(bumpTxId)
|
|
130
132
|
|
|
131
133
|
if (replacedTokenTx) {
|
|
132
|
-
asset
|
|
134
|
+
// Attempt to overwrite the asset to reflect the fact that
|
|
135
|
+
// we're performing a token transaction.
|
|
136
|
+
asset = assets[tokenAssetName]
|
|
137
|
+
if (!asset) {
|
|
138
|
+
console.warn(
|
|
139
|
+
`unable to find ${tokenAssetName} during token bump transaction: asset was not available in assetsForNetwork`
|
|
140
|
+
)
|
|
141
|
+
}
|
|
133
142
|
}
|
|
143
|
+
|
|
144
|
+
// TODO: Should we `throw` if we can't find the asset?
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
address = (replacedTokenTx || replacedTx).to
|
|
137
148
|
amount = (replacedTokenTx || replacedTx).coinAmount.negate()
|
|
138
149
|
feeOpts.gasLimit = replacedTx.data.gasLimit
|
|
139
150
|
|
|
140
|
-
const { gasPrice: currentGasPrice } = feeData
|
|
151
|
+
const { gasPrice: currentGasPrice, baseFeePerGas: currentBaseFee } = feeData
|
|
141
152
|
const { bumpedGasPrice, bumpedTipGasPrice } = calculateBumpedGasPrice({
|
|
142
153
|
baseAsset,
|
|
143
154
|
tx: replacedTx,
|
|
144
155
|
currentGasPrice,
|
|
156
|
+
currentBaseFee,
|
|
145
157
|
eip1559Enabled,
|
|
146
158
|
})
|
|
147
159
|
feeOpts.gasPrice = bumpedGasPrice
|
|
@@ -174,6 +186,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
174
186
|
isSendAll,
|
|
175
187
|
createUnsignedTx,
|
|
176
188
|
}
|
|
189
|
+
|
|
177
190
|
let { txId, rawTx, nonce, gasLimit, tipGasPrice, gasPrice } = await createTx(createTxParams)
|
|
178
191
|
|
|
179
192
|
const feeAmount = gasPrice.mul(gasLimit)
|