@exodus/ethereum-api 8.45.0 → 8.45.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/allowance/index.js +4 -2
- package/src/exodus-eth-server/clarity-v2.js +3 -1
- package/src/fee-utils.js +1 -0
- package/src/gas-estimation.js +14 -7
- package/src/staking/ethereum/api.js +26 -1
- package/src/staking/ethereum/service.js +11 -6
- package/src/tx-create.js +2 -1
- package/src/tx-log/ethereum-no-history-monitor.js +1 -5
- package/src/tx-log-staking-processor/asset-staking-tx-data.js +4 -2
- package/src/tx-send/tx-send.js +2 -1
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
|
+
## [8.45.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.45.1...@exodus/ethereum-api@8.45.2) (2025-08-11)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* fix: enable gas estimation for fixedGasLimit tokens (#6253)
|
|
13
|
+
|
|
14
|
+
* fix: maintain eip-1559 invariant for gasPrice on eip1559Enabled nework (#6158)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## [8.45.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.45.0...@exodus/ethereum-api@8.45.1) (2025-08-06)
|
|
19
|
+
|
|
20
|
+
**Note:** Version bump only for package @exodus/ethereum-api
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
## [8.45.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.44.0...@exodus/ethereum-api@8.45.0) (2025-08-04)
|
|
7
27
|
|
|
8
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.45.
|
|
3
|
+
"version": "8.45.2",
|
|
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",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"type": "git",
|
|
64
64
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "44e5b51ad5bc9cc5a7ec8a0a685da6979acc6369"
|
|
67
67
|
}
|
package/src/allowance/index.js
CHANGED
|
@@ -60,8 +60,9 @@ export const buildApproveTx = async ({
|
|
|
60
60
|
|
|
61
61
|
if (!txInput) txInput = asset.contract.approve.build(spenderAddress, approveAmount.toBaseString())
|
|
62
62
|
|
|
63
|
-
if (typeof nonce !== 'number')
|
|
63
|
+
if (typeof nonce !== 'number') {
|
|
64
64
|
nonce = await baseAsset.getNonce({ asset, fromAddress, walletAccount })
|
|
65
|
+
}
|
|
65
66
|
|
|
66
67
|
if (!gasLimit) {
|
|
67
68
|
gasLimit = await fetchGasLimit({
|
|
@@ -163,8 +164,9 @@ export const createApprove =
|
|
|
163
164
|
|
|
164
165
|
const txData = await sendTx({ walletAccount, ...approveTx, ...extras })
|
|
165
166
|
|
|
166
|
-
if (!txData || !txData.txId)
|
|
167
|
+
if (!txData || !txData.txId) {
|
|
167
168
|
throw new Error(`Failed to approve ${asset.displayTicker} - ${spenderAddress}`)
|
|
169
|
+
}
|
|
168
170
|
|
|
169
171
|
return txData
|
|
170
172
|
}
|
|
@@ -42,12 +42,14 @@ const getTextFromResponse = async (response) => {
|
|
|
42
42
|
const fetchJson = async (url, fetchOptions) => {
|
|
43
43
|
const response = await fetch(url, fetchOptions)
|
|
44
44
|
|
|
45
|
-
if (!response.ok)
|
|
45
|
+
if (!response.ok) {
|
|
46
46
|
throw new Error(
|
|
47
47
|
`${url} returned ${response.status}: ${
|
|
48
48
|
response.statusText || 'Unknown Status Text'
|
|
49
49
|
}. Body: ${await getTextFromResponse(response)}`
|
|
50
50
|
)
|
|
51
|
+
}
|
|
52
|
+
|
|
51
53
|
return response.json()
|
|
52
54
|
}
|
|
53
55
|
|
package/src/fee-utils.js
CHANGED
|
@@ -137,6 +137,7 @@ export const calculateEthLikeFeeMonitorUpdateEip1559 = async ({
|
|
|
137
137
|
|
|
138
138
|
return {
|
|
139
139
|
...defaultFeeConfig,
|
|
140
|
+
gasPrice: `${(baseFeePerGas + maxPriorityFeePerGas50Percentile).toString()} wei`,
|
|
140
141
|
tipGasPrice: `${maxPriorityFeePerGas50Percentile.toString()} wei`,
|
|
141
142
|
}
|
|
142
143
|
}
|
package/src/gas-estimation.js
CHANGED
|
@@ -118,12 +118,9 @@ export async function fetchGasLimit({
|
|
|
118
118
|
bip70,
|
|
119
119
|
throwOnError = true,
|
|
120
120
|
}) {
|
|
121
|
-
if (bip70?.bitpay?.data && bip70?.bitpay?.gasPrice)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const fixedGasLimit = feeData?.gasLimits?.[asset.name]?.fixedGasLimit
|
|
125
|
-
if (fixedGasLimit) {
|
|
126
|
-
return fixedGasLimit
|
|
121
|
+
if (bip70?.bitpay?.data && bip70?.bitpay?.gasPrice) {
|
|
122
|
+
// from on chain stats https://dune.xyz/queries/189123
|
|
123
|
+
return asset.name === 'ethereum' ? 65_000 : 130_000
|
|
127
124
|
}
|
|
128
125
|
|
|
129
126
|
const amount = providedAmount ?? asset.currency.ZERO
|
|
@@ -159,7 +156,17 @@ export async function fetchGasLimit({
|
|
|
159
156
|
data: txInput,
|
|
160
157
|
})
|
|
161
158
|
|
|
162
|
-
return
|
|
159
|
+
// NOTE: Although we'll return the `fixedGasLimit` for known
|
|
160
|
+
// tokens, we'll still want to execute `estimateGasLimit`
|
|
161
|
+
// to verify their transaction actually succeeds (i.e.
|
|
162
|
+
// they aren't trying to transfer more than their balance).
|
|
163
|
+
//
|
|
164
|
+
// This prevents users from submitting `fixedGasLimit`
|
|
165
|
+
// token transactions which result in a `revert`.
|
|
166
|
+
return (
|
|
167
|
+
feeData?.gasLimits?.[asset.name]?.fixedGasLimit ??
|
|
168
|
+
scaleGasLimitEstimate({ estimatedGasLimit, gasLimitMultiplier })
|
|
169
|
+
)
|
|
163
170
|
} catch (err) {
|
|
164
171
|
if (throwOnError) throw err
|
|
165
172
|
|
|
@@ -8,6 +8,8 @@ import { getServerByName } from '../../exodus-eth-server/index.js'
|
|
|
8
8
|
const MIN_AMOUNT = '0.1'
|
|
9
9
|
const RETRY_DELAYS = ['10s']
|
|
10
10
|
|
|
11
|
+
const EVERSTAKE_API_URL = 'https://eth.a.exodus.io/everstake-rewards'
|
|
12
|
+
|
|
11
13
|
export class EthereumStaking {
|
|
12
14
|
static addresses = {
|
|
13
15
|
ethereum: {
|
|
@@ -139,8 +141,8 @@ export class EthereumStaking {
|
|
|
139
141
|
return this.asset.currency.baseUnit(depositedBalance)
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
/* Get earned Rewards */
|
|
143
144
|
async getLiquidRewards(address) {
|
|
145
|
+
// latest accrued rewards
|
|
144
146
|
const [compoundBalance, depositedBalance] = await Promise.all([
|
|
145
147
|
this.autocompoundBalanceOf(address),
|
|
146
148
|
this.depositedBalanceOf(address),
|
|
@@ -148,6 +150,29 @@ export class EthereumStaking {
|
|
|
148
150
|
return compoundBalance.sub(depositedBalance)
|
|
149
151
|
}
|
|
150
152
|
|
|
153
|
+
async getTotalRewards(address) {
|
|
154
|
+
// use external everstake API to get total rewards
|
|
155
|
+
const url = new URL(EVERSTAKE_API_URL)
|
|
156
|
+
|
|
157
|
+
const options = {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
address,
|
|
162
|
+
}),
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const response = await fetch(url.toString(), options)
|
|
167
|
+
const rewards = await response.json()
|
|
168
|
+
if (rewards === 'address') return this.asset.currency.ZERO // address not found
|
|
169
|
+
return this.asset.currency.baseUnit(rewards)
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.warn('Error fetching ETH total rewards:', error)
|
|
172
|
+
return this.asset.currency.ZERO
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
151
176
|
/** Return withdrawable unstaked amount */
|
|
152
177
|
async withdrawRequest(address) {
|
|
153
178
|
let amounts = await this.#callReadFunctionContract(
|
|
@@ -78,13 +78,14 @@ export function createEthereumStakingService({
|
|
|
78
78
|
})
|
|
79
79
|
|
|
80
80
|
// Goerli is not supported
|
|
81
|
-
if (asset.name === 'ethereum')
|
|
81
|
+
if (asset.name === 'ethereum') {
|
|
82
82
|
await stakingProvider.notifyStaking({
|
|
83
83
|
txId,
|
|
84
84
|
asset: asset.name,
|
|
85
85
|
delegator: delegatorAddress,
|
|
86
86
|
amount: amount.toBaseString(),
|
|
87
87
|
})
|
|
88
|
+
}
|
|
88
89
|
|
|
89
90
|
return txId
|
|
90
91
|
}
|
|
@@ -98,8 +99,9 @@ export function createEthereumStakingService({
|
|
|
98
99
|
}) {
|
|
99
100
|
const leftOver = pendingAmount.sub(resquestedAmount)
|
|
100
101
|
|
|
101
|
-
if (leftOver.isPositive && leftOver.lt(minAmount))
|
|
102
|
+
if (leftOver.isPositive && leftOver.lt(minAmount)) {
|
|
102
103
|
throw new Error(`Pending balance less than min stake amount ${minAmount}`)
|
|
104
|
+
}
|
|
103
105
|
|
|
104
106
|
const inactiveAmountToUnstake = pendingAmount.lte(resquestedAmount)
|
|
105
107
|
? pendingAmount
|
|
@@ -258,13 +260,14 @@ export function createEthereumStakingService({
|
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
// Testnet assets do not support delegations tracking
|
|
261
|
-
if (txId && asset.name === 'ethereum')
|
|
263
|
+
if (txId && asset.name === 'ethereum') {
|
|
262
264
|
await stakingProvider.notifyUnstaking({
|
|
263
265
|
txId,
|
|
264
266
|
asset: asset.name,
|
|
265
267
|
delegator: delegatorAddress,
|
|
266
268
|
amount: resquestedAmount.toBaseString(),
|
|
267
269
|
})
|
|
270
|
+
}
|
|
268
271
|
|
|
269
272
|
return txId
|
|
270
273
|
}
|
|
@@ -308,8 +311,9 @@ export function createEthereumStakingService({
|
|
|
308
311
|
let delegatorAddress
|
|
309
312
|
;({ delegatorAddress, feeData } = await getTransactionProps({ feeData, walletAccount }))
|
|
310
313
|
|
|
311
|
-
if (operation === 'undelegate')
|
|
314
|
+
if (operation === 'undelegate') {
|
|
312
315
|
return estimateUndelegate({ walletAccount, amount: requestedAmount, feeData })
|
|
316
|
+
}
|
|
313
317
|
|
|
314
318
|
const NAMING_MAP = {
|
|
315
319
|
delegate: 'stake',
|
|
@@ -428,11 +432,12 @@ export function createEthereumStakingService({
|
|
|
428
432
|
// If the `spendableForStaking` is insufficient to cover both the
|
|
429
433
|
// transaction fee and the minimum stake, then there is no reasonable
|
|
430
434
|
// value we can recommend.
|
|
431
|
-
if (calculatedFee.add(minAmount).gt(spendableForStaking))
|
|
435
|
+
if (calculatedFee.add(minAmount).gt(spendableForStaking)) {
|
|
432
436
|
return {
|
|
433
437
|
calculatedFee,
|
|
434
438
|
selectAllAmount: asset.currency.ZERO,
|
|
435
439
|
}
|
|
440
|
+
}
|
|
436
441
|
|
|
437
442
|
// At this stage, we've confirmed that the remaining `spendableForStaking`
|
|
438
443
|
// after transactions is sufficient to cover the minimum stake, so any
|
|
@@ -517,7 +522,7 @@ export async function getEthereumStakingInfo({ address, asset, server }) {
|
|
|
517
522
|
staking.pendingBalanceOf(delegator),
|
|
518
523
|
staking.pendingDepositedBalanceOf(delegator),
|
|
519
524
|
staking.withdrawRequest(delegator),
|
|
520
|
-
staking.
|
|
525
|
+
staking.getTotalRewards(delegator),
|
|
521
526
|
])
|
|
522
527
|
|
|
523
528
|
const delegatedBalance = activeStakedBalance.add(pendingBalance).add(pendingDepositedBalance)
|
package/src/tx-create.js
CHANGED
|
@@ -157,12 +157,13 @@ const createBumpUnsignedTx = async ({
|
|
|
157
157
|
// If we have evaluated a bump transaction and the `providedNonce` differs
|
|
158
158
|
// from the `bumpNonce`, we've encountered a conflict and cannot respect
|
|
159
159
|
// the caller's request.
|
|
160
|
-
if (typeof nonce === 'number' && typeof providedNonce === 'number' && nonce !== providedNonce)
|
|
160
|
+
if (typeof nonce === 'number' && typeof providedNonce === 'number' && nonce !== providedNonce) {
|
|
161
161
|
throw new ErrorWrapper.EthLikeError({
|
|
162
162
|
message: new Error('incorrect nonce for replacement transaction'),
|
|
163
163
|
reason: ErrorWrapper.reasons.bumpTxFailed,
|
|
164
164
|
hint: 'providedNonce',
|
|
165
165
|
})
|
|
166
|
+
}
|
|
166
167
|
|
|
167
168
|
return createUnsignedTxWithFees({
|
|
168
169
|
asset,
|
|
@@ -129,11 +129,7 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
|
|
|
129
129
|
const txsToRemove = []
|
|
130
130
|
const now = SynchronizedTime.now()
|
|
131
131
|
|
|
132
|
-
if (isEmpty(pendingTransactions))
|
|
133
|
-
return {
|
|
134
|
-
txsToUpdate,
|
|
135
|
-
txsToRemove,
|
|
136
|
-
}
|
|
132
|
+
if (isEmpty(pendingTransactions)) return { txsToUpdate, txsToRemove }
|
|
137
133
|
|
|
138
134
|
for (const { tx, assetName } of pendingTransactions) {
|
|
139
135
|
const txFromNode = pendingTxsFromNode[tx.txId]
|
|
@@ -21,8 +21,9 @@ const getEthereumStakingTxData = ({ tx, currency }) => {
|
|
|
21
21
|
(stakeTx) => tx.data?.[stakeTx]
|
|
22
22
|
) &&
|
|
23
23
|
tx.coinAmount.isZero
|
|
24
|
-
)
|
|
24
|
+
) {
|
|
25
25
|
return
|
|
26
|
+
}
|
|
26
27
|
|
|
27
28
|
const txAmount = tx.coinAmount.toDefaultString()
|
|
28
29
|
|
|
@@ -58,8 +59,9 @@ const getPolygonStakingTxData = ({ tx, currency }) => {
|
|
|
58
59
|
if (
|
|
59
60
|
['delegate', 'undelegate', 'claimUndelegate'].some((stakeTx) => tx.data?.[stakeTx]) &&
|
|
60
61
|
tx.coinAmount.isZero
|
|
61
|
-
)
|
|
62
|
+
) {
|
|
62
63
|
return
|
|
64
|
+
}
|
|
63
65
|
|
|
64
66
|
const txAmount = tx.coinAmount.toDefaultString()
|
|
65
67
|
|
package/src/tx-send/tx-send.js
CHANGED
|
@@ -93,10 +93,11 @@ const txSendFactory = ({ assetClientInterface, createTx }) => {
|
|
|
93
93
|
|
|
94
94
|
const isPrivate = Boolean(legacyParams?.options?.isPrivate)
|
|
95
95
|
|
|
96
|
-
if (isPrivate && typeof baseAsset.broadcastPrivateTx !== 'function')
|
|
96
|
+
if (isPrivate && typeof baseAsset.broadcastPrivateTx !== 'function') {
|
|
97
97
|
throw new Error(
|
|
98
98
|
`unable to send private transaction - private mempools are not enabled for ${baseAsset.name}`
|
|
99
99
|
)
|
|
100
|
+
}
|
|
100
101
|
|
|
101
102
|
const broadcastTx = isPrivate ? baseAsset.broadcastPrivateTx : baseAsset.api.broadcastTx
|
|
102
103
|
|