@exodus/ethereum-api 8.39.0 → 8.40.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,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.40.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.39.0...@exodus/ethereum-api@8.40.0) (2025-06-26)
7
+
8
+
9
+ ### Features
10
+
11
+
12
+ * feat: enable ws reconnection and timeout fallback to rest on clarity (#5900)
13
+
14
+
15
+
6
16
  ## [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
17
 
8
18
 
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.0",
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": "8c81c64076e7092e8a1a0d7c9a51f410a94aeec9"
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)
@@ -231,7 +231,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
231
231
 
232
232
  let { txId, rawTx, nonce, gasLimit, tipGasPrice, feeAmount } = await createTx(createTxParams)
233
233
 
234
- if (isPrivate && !baseAsset.api.hasFeature('transactionPrivacy'))
234
+ if (isPrivate && !baseAsset.api.features.transactionPrivacy)
235
235
  throw new Error(
236
236
  `unable to send private transaction - transactionPrivacy is not enabled for ${baseAsset.name}`
237
237
  )