@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 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.39.0",
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": "dab2f29bb10eb8486ce39445f451439215d450cd"
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 sendHttpRequest({ path, method, body }) {
171
- const url = new URL(`${this.baseApiPath}${path}`)
172
- const fetchOptions = {
173
- method,
174
- headers: {
175
- 'Content-Type': 'application/json',
176
- },
177
- }
178
-
179
- if (body) {
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.sendHttpRequest({
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.sendHttpRequest({
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')), 30_000)
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('Rpc Timeout')), 30_000)
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)
@@ -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
  }
@@ -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 { gasPrice: currentGasPrice, baseFeePerGas: currentBaseFee } = feeData
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.hasFeature('transactionPrivacy'))
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
  )