@exodus/ethereum-api 8.39.0 → 8.40.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 +20 -0
- package/package.json +2 -2
- package/src/exodus-eth-server/clarity-v2.js +33 -27
- package/src/exodus-eth-server/clarity.js +5 -2
- package/src/gas-estimation.js +9 -9
- package/src/staking/ethereum/service.js +6 -6
- package/src/staking/matic/service.js +7 -7
- package/src/tx-send/tx-send.js +7 -2
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.40.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.40.0...@exodus/ethereum-api@8.40.1) (2025-06-26)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* fix: activate `currentTipGasPrice` for bumped transactions in `tx-send` (#5950)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## [8.40.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.39.0...@exodus/ethereum-api@8.40.0) (2025-06-26)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
* feat: enable ws reconnection and timeout fallback to rest on clarity (#5900)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
## [8.39.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.38.1...@exodus/ethereum-api@8.39.0) (2025-06-20)
|
|
7
27
|
|
|
8
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.40.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",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"type": "git",
|
|
64
64
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "48a7c711842748ab188eb6bbbb575d1a13b8a6ab"
|
|
67
67
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { retry } from '@exodus/simple-retry'
|
|
2
|
+
import assert from 'minimalistic-assert'
|
|
2
3
|
|
|
3
|
-
import ClarityServer from './clarity.js'
|
|
4
|
+
import ClarityServer, { RPC_REQUEST_TIMEOUT } from './clarity.js'
|
|
4
5
|
|
|
5
6
|
export const encodeCursor = (blockNumberBigInt, isLegacy = false) => {
|
|
6
7
|
if (typeof blockNumberBigInt !== 'bigint') throw new Error('expected bigint')
|
|
@@ -50,6 +51,22 @@ const fetchJson = async (url, fetchOptions) => {
|
|
|
50
51
|
return response.json()
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
const fetchHttpRequest = ({ baseApiPath, path, method, body }) => {
|
|
55
|
+
assert(typeof baseApiPath === 'string', 'expected string baseApiPath')
|
|
56
|
+
|
|
57
|
+
const url = new URL(`${baseApiPath}${path}`)
|
|
58
|
+
const fetchOptions = {
|
|
59
|
+
method,
|
|
60
|
+
headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (body) fetchOptions.body = JSON.stringify(body)
|
|
64
|
+
return fetchJson(url, fetchOptions)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const fetchRpcHttpRequest = ({ baseApiPath, body }) =>
|
|
68
|
+
fetchHttpRequest({ baseApiPath, path: '/rpc', method: 'POST', body })
|
|
69
|
+
|
|
53
70
|
async function fetchJsonRetry(url, fetchOptions) {
|
|
54
71
|
const waitTimes = ['3s']
|
|
55
72
|
const fetchWithRetry = retry(fetchJson, { delayTimesMs: waitTimes })
|
|
@@ -105,7 +122,7 @@ export default class ClarityServerV2 extends ClarityServer {
|
|
|
105
122
|
// In addition, when `eip1559Enabled` on Clarity:
|
|
106
123
|
// + baseFeePerGas
|
|
107
124
|
// + nextBaseFeePerGas
|
|
108
|
-
// + rewardPercentiles
|
|
125
|
+
// + rewardPercentiles
|
|
109
126
|
//
|
|
110
127
|
// See: https://github.com/ExodusMovement/clarity/blob/d3c2a7f501a4391da630592bca3bf57c3ddd5e89/src/modules/ethereum-like/gas-price/index.js#L192C5-L219C6
|
|
111
128
|
return await this.getGasPriceEstimation()
|
|
@@ -167,40 +184,29 @@ export default class ClarityServerV2 extends ClarityServer {
|
|
|
167
184
|
}
|
|
168
185
|
}
|
|
169
186
|
|
|
170
|
-
async
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
fetchOptions.body = JSON.stringify(body)
|
|
187
|
+
async sendRpcRequest(rpcRequest) {
|
|
188
|
+
try {
|
|
189
|
+
return await super.sendRpcRequest(rpcRequest)
|
|
190
|
+
} catch (err) {
|
|
191
|
+
// If we encounter an error which isn't associated
|
|
192
|
+
// with transport timeouts, then bubble up.
|
|
193
|
+
if (err.message !== RPC_REQUEST_TIMEOUT) throw err
|
|
194
|
+
|
|
195
|
+
const { baseApiPath } = this
|
|
196
|
+
return fetchRpcHttpRequest({ baseApiPath, body: rpcRequest })
|
|
181
197
|
}
|
|
182
|
-
|
|
183
|
-
const response = await fetchJson(url, fetchOptions)
|
|
184
|
-
|
|
185
|
-
return this.handleJsonRPCResponse(response)
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
async sendRawTransaction(...params) {
|
|
201
|
+
const { baseApiPath } = this
|
|
189
202
|
const request = this.sendRawTransactionRequest(...params)
|
|
190
|
-
return this.
|
|
191
|
-
path: '/rpc',
|
|
192
|
-
method: 'POST',
|
|
193
|
-
body: request,
|
|
194
|
-
})
|
|
203
|
+
return this.handleJsonRPCResponse(await fetchRpcHttpRequest({ baseApiPath, body: request }))
|
|
195
204
|
}
|
|
196
205
|
|
|
197
206
|
async getTransactionCount(...params) {
|
|
198
207
|
// nonce is called during tx send, use it in rest api
|
|
208
|
+
const { baseApiPath } = this
|
|
199
209
|
const request = this.getTransactionCountRequest(...params)
|
|
200
|
-
return this.
|
|
201
|
-
path: '/rpc',
|
|
202
|
-
method: 'POST',
|
|
203
|
-
body: request,
|
|
204
|
-
})
|
|
210
|
+
return this.handleJsonRPCResponse(await fetchRpcHttpRequest({ baseApiPath, body: request }))
|
|
205
211
|
}
|
|
206
212
|
}
|
|
@@ -5,6 +5,8 @@ import io from 'socket.io-client'
|
|
|
5
5
|
|
|
6
6
|
import { fromHexToString } from '../number-utils.js'
|
|
7
7
|
|
|
8
|
+
export const RPC_REQUEST_TIMEOUT = 'RPC_REQUEST_TIMEOUT'
|
|
9
|
+
|
|
8
10
|
export default class ClarityServer extends EventEmitter {
|
|
9
11
|
constructor({ baseAssetName, uri }) {
|
|
10
12
|
super()
|
|
@@ -56,6 +58,7 @@ export default class ClarityServer extends EventEmitter {
|
|
|
56
58
|
return io(`${this.uri}${namespace}`, {
|
|
57
59
|
transports: ['websocket', 'polling'],
|
|
58
60
|
extraHeaders: { 'User-Agent': 'exodus' },
|
|
61
|
+
reconnection: true,
|
|
59
62
|
})
|
|
60
63
|
}
|
|
61
64
|
|
|
@@ -140,7 +143,7 @@ export default class ClarityServer extends EventEmitter {
|
|
|
140
143
|
getFeeFromWebSocket() {
|
|
141
144
|
const socket = this.connectFee()
|
|
142
145
|
return new Promise((resolve, reject) => {
|
|
143
|
-
const timeout = setTimeout(() => reject(new Error('Fee Timeout')),
|
|
146
|
+
const timeout = setTimeout(() => reject(new Error('Fee Timeout')), 3000)
|
|
144
147
|
socket.emit('getFee', (fee) => {
|
|
145
148
|
clearTimeout(timeout)
|
|
146
149
|
if (!fee) {
|
|
@@ -166,7 +169,7 @@ export default class ClarityServer extends EventEmitter {
|
|
|
166
169
|
async sendRpcRequest(rpcRequest) {
|
|
167
170
|
const rpcSocket = this.connectRpc()
|
|
168
171
|
return new Promise((resolve, reject) => {
|
|
169
|
-
const timeout = setTimeout(() => reject(new Error(
|
|
172
|
+
const timeout = setTimeout(() => reject(new Error(RPC_REQUEST_TIMEOUT)), 3000)
|
|
170
173
|
rpcSocket.emit('request', rpcRequest, (response) => {
|
|
171
174
|
clearTimeout(timeout)
|
|
172
175
|
resolve(response)
|
package/src/gas-estimation.js
CHANGED
|
@@ -50,14 +50,14 @@ export const scaleGasLimitEstimate = ({
|
|
|
50
50
|
// be calculated with account's balance divided by gasPrice. If user's balance is too low,
|
|
51
51
|
// the gasEstimation will fail. If gasPrice is set to '0x0', the account's balance is not
|
|
52
52
|
// used to estimate gas.
|
|
53
|
-
export async function estimateGasLimit(
|
|
53
|
+
export async function estimateGasLimit({
|
|
54
54
|
asset,
|
|
55
55
|
fromAddress,
|
|
56
56
|
toAddress,
|
|
57
|
-
amount,
|
|
57
|
+
amount = asset.currency.ZERO,
|
|
58
58
|
data,
|
|
59
|
-
gasPrice = '0x'
|
|
60
|
-
) {
|
|
59
|
+
gasPrice = '0x',
|
|
60
|
+
}) {
|
|
61
61
|
const opts = {
|
|
62
62
|
from: fromAddress,
|
|
63
63
|
to: toAddress,
|
|
@@ -150,13 +150,13 @@ export async function fetchGasLimit({
|
|
|
150
150
|
const txAmount = isToken ? asset.baseAsset.currency.ZERO : amount
|
|
151
151
|
|
|
152
152
|
try {
|
|
153
|
-
const estimatedGasLimit = await estimateGasLimit(
|
|
153
|
+
const estimatedGasLimit = await estimateGasLimit({
|
|
154
154
|
asset,
|
|
155
155
|
fromAddress,
|
|
156
|
-
txToAddress,
|
|
157
|
-
txAmount,
|
|
158
|
-
txInput
|
|
159
|
-
)
|
|
156
|
+
toAddress: txToAddress,
|
|
157
|
+
amount: txAmount,
|
|
158
|
+
data: txInput,
|
|
159
|
+
})
|
|
160
160
|
|
|
161
161
|
return scaleGasLimitEstimate({ estimatedGasLimit, gasLimitMultiplier })
|
|
162
162
|
} catch (err) {
|
|
@@ -359,14 +359,14 @@ export function createEthereumStakingService({
|
|
|
359
359
|
amount = amount || asset.currency.ZERO
|
|
360
360
|
from = from.toLowerCase()
|
|
361
361
|
|
|
362
|
-
const estimatedGasLimit = await estimateGasLimit(
|
|
362
|
+
const estimatedGasLimit = await estimateGasLimit({
|
|
363
363
|
asset,
|
|
364
|
-
from,
|
|
365
|
-
to.toLowerCase(),
|
|
364
|
+
fromAddress: from,
|
|
365
|
+
toAddress: to.toLowerCase(),
|
|
366
366
|
amount, // staking contracts does not always require ETH amount to interact with
|
|
367
|
-
txInput,
|
|
368
|
-
DISABLE_BALANCE_CHECKS
|
|
369
|
-
)
|
|
367
|
+
data: txInput,
|
|
368
|
+
gasPrice: DISABLE_BALANCE_CHECKS,
|
|
369
|
+
})
|
|
370
370
|
|
|
371
371
|
const scaledGasLimit = scaleGasLimitEstimate({ estimatedGasLimit })
|
|
372
372
|
|
|
@@ -337,14 +337,14 @@ export function createPolygonStakingService({
|
|
|
337
337
|
|
|
338
338
|
const amount = ethereum.currency.ZERO
|
|
339
339
|
|
|
340
|
-
const gasLimit = await estimateGasLimit(
|
|
341
|
-
ethereum,
|
|
342
|
-
from,
|
|
343
|
-
to,
|
|
340
|
+
const gasLimit = await estimateGasLimit({
|
|
341
|
+
asset: ethereum,
|
|
342
|
+
fromAddress: from,
|
|
343
|
+
toAddress: to,
|
|
344
344
|
amount, // staking contracts does not require ETH amount to interact with
|
|
345
|
-
txInput,
|
|
346
|
-
DISABLE_BALANCE_CHECKS
|
|
347
|
-
)
|
|
345
|
+
data: txInput,
|
|
346
|
+
gasPrice: DISABLE_BALANCE_CHECKS,
|
|
347
|
+
})
|
|
348
348
|
|
|
349
349
|
return ethereum.api.getFee({ asset: ethereum, feeData, gasLimit, amount })
|
|
350
350
|
}
|
package/src/tx-send/tx-send.js
CHANGED
|
@@ -158,12 +158,17 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
|
|
|
158
158
|
amount = (replacedTokenTx || replacedTx).coinAmount.negate()
|
|
159
159
|
feeOpts.gasLimit = replacedTx.data.gasLimit
|
|
160
160
|
|
|
161
|
-
const {
|
|
161
|
+
const {
|
|
162
|
+
gasPrice: currentGasPrice,
|
|
163
|
+
baseFeePerGas: currentBaseFee,
|
|
164
|
+
tipGasPrice: currentTipGasPrice,
|
|
165
|
+
} = feeData
|
|
162
166
|
const { bumpedGasPrice, bumpedTipGasPrice } = calculateBumpedGasPrice({
|
|
163
167
|
baseAsset,
|
|
164
168
|
tx: replacedTx,
|
|
165
169
|
currentGasPrice,
|
|
166
170
|
currentBaseFee,
|
|
171
|
+
currentTipGasPrice,
|
|
167
172
|
eip1559Enabled,
|
|
168
173
|
})
|
|
169
174
|
feeOpts.gasPrice = bumpedGasPrice
|
|
@@ -231,7 +236,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
|
|
|
231
236
|
|
|
232
237
|
let { txId, rawTx, nonce, gasLimit, tipGasPrice, feeAmount } = await createTx(createTxParams)
|
|
233
238
|
|
|
234
|
-
if (isPrivate && !baseAsset.api.
|
|
239
|
+
if (isPrivate && !baseAsset.api.features.transactionPrivacy)
|
|
235
240
|
throw new Error(
|
|
236
241
|
`unable to send private transaction - transactionPrivacy is not enabled for ${baseAsset.name}`
|
|
237
242
|
)
|