@exodus/ethereum-api 8.64.5 → 8.64.7
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 +5 -4
- package/src/ens/index.js +1 -0
- package/src/error-wrapper.js +298 -16
- package/src/eth-like-util.js +74 -31
- package/src/exodus-eth-server/api-coin-nodes.js +7 -1
- package/src/exodus-eth-server/api.js +5 -0
- package/src/exodus-eth-server/clarity-v2.js +25 -7
- package/src/exodus-eth-server/clarity.js +54 -2
- package/src/exodus-eth-server/index.js +1 -1
- package/src/exodus-eth-server/utils.js +31 -0
- package/src/fee-utils.js +0 -9
- package/src/index.js +7 -1
- package/src/nft-utils.js +6 -4
- package/src/optimism-gas/index.js +8 -1
- package/src/server-based-fee-monitor.js +2 -10
- package/src/staking/ethereum/api.js +8 -1
- package/src/staking/matic/api.js +8 -1
- package/src/tx-send/broadcast-error-handler.js +58 -0
- package/src/tx-send/tx-send.js +22 -61
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { retry } from '@exodus/simple-retry'
|
|
2
|
+
import { TraceId } from '@exodus/traceparent'
|
|
2
3
|
import assert from 'minimalistic-assert'
|
|
3
4
|
|
|
4
5
|
import ClarityServer, { RPC_REQUEST_TIMEOUT } from './clarity.js'
|
|
@@ -43,14 +44,31 @@ const fetchJson = async (url, fetchOptions) => {
|
|
|
43
44
|
const response = await fetch(url, fetchOptions)
|
|
44
45
|
|
|
45
46
|
if (!response.ok) {
|
|
46
|
-
|
|
47
|
+
const traceId = TraceId.fromResponse(response)
|
|
48
|
+
const error = new Error(
|
|
47
49
|
`${url} returned ${response.status}: ${
|
|
48
50
|
response.statusText || 'Unknown Status Text'
|
|
49
51
|
}. Body: ${await getTextFromResponse(response)}`
|
|
50
52
|
)
|
|
53
|
+
if (traceId) {
|
|
54
|
+
error.traceId = traceId
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw error
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const json = await response.json()
|
|
61
|
+
|
|
62
|
+
// Only capture trace ID if there's an RPC error in the response
|
|
63
|
+
// (handleJsonRPCResponse will extract it when throwing the error)
|
|
64
|
+
if (json.error) {
|
|
65
|
+
const traceId = TraceId.fromResponse(response)
|
|
66
|
+
if (traceId) {
|
|
67
|
+
json.__traceId = traceId
|
|
68
|
+
}
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
return
|
|
71
|
+
return json
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
async function fetchJsonRetry(url, fetchOptions) {
|
|
@@ -126,9 +144,6 @@ export default class ClarityServerV2 extends ClarityServer {
|
|
|
126
144
|
// See: https://github.com/ExodusMovement/clarity/blob/d3c2a7f501a4391da630592bca3bf57c3ddd5e89/src/modules/ethereum-like/gas-price/index.js#L192C5-L219C6
|
|
127
145
|
return await this.getGasPriceEstimation()
|
|
128
146
|
} catch {
|
|
129
|
-
console.log(
|
|
130
|
-
`failed to query ${this.baseAssetName} gas-price-estimation endpoint, falling back to websocket`
|
|
131
|
-
)
|
|
132
147
|
// HACK: The `getGasPriceEstimation` endpoint is not guaranteed
|
|
133
148
|
// to exist for all assets. In this case, we'll fallback
|
|
134
149
|
// to legacy behaviour, which is to query via the WebSocket.
|
|
@@ -185,8 +200,9 @@ export default class ClarityServerV2 extends ClarityServer {
|
|
|
185
200
|
}
|
|
186
201
|
}
|
|
187
202
|
|
|
188
|
-
fetchRpcHttpRequest = ({ baseApiPath, body }) =>
|
|
189
|
-
fetchHttpRequest({ baseApiPath, path: '/rpc', method: 'POST', body })
|
|
203
|
+
fetchRpcHttpRequest = ({ baseApiPath, body }) => {
|
|
204
|
+
return fetchHttpRequest({ baseApiPath, path: '/rpc', method: 'POST', body })
|
|
205
|
+
}
|
|
190
206
|
|
|
191
207
|
async sendRpcRequest(rpcRequest) {
|
|
192
208
|
try {
|
|
@@ -201,6 +217,8 @@ export default class ClarityServerV2 extends ClarityServer {
|
|
|
201
217
|
}
|
|
202
218
|
}
|
|
203
219
|
|
|
220
|
+
// Maybe some functions from clarity should be overriden to have handleJsonRPCResponse as well
|
|
221
|
+
|
|
204
222
|
async sendRawTransaction(...params) {
|
|
205
223
|
const { baseApiPath } = this
|
|
206
224
|
const request = this.sendRawTransactionRequest(...params)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
2
2
|
import { safeString } from '@exodus/safe-string'
|
|
3
3
|
import SolidityContract from '@exodus/solidity-contract'
|
|
4
|
+
import { TraceId } from '@exodus/traceparent'
|
|
4
5
|
import EventEmitter from 'events/events.js'
|
|
5
6
|
import io from 'socket.io-client'
|
|
6
7
|
|
|
7
8
|
import { fromHexToString } from '../number-utils.js'
|
|
8
9
|
import { errorMessageToSafeHint } from './errors.js'
|
|
10
|
+
import { getFallbackGasPriceEstimation } from './utils.js'
|
|
9
11
|
|
|
10
12
|
export const RPC_REQUEST_TIMEOUT = 'RPC_REQUEST_TIMEOUT'
|
|
11
13
|
|
|
@@ -103,12 +105,20 @@ export default class ClarityServer extends EventEmitter {
|
|
|
103
105
|
|
|
104
106
|
const revisedError = new Error(`Bad rpc response: ${message}`)
|
|
105
107
|
revisedError.hint = safeString`Bad rpc response: ${errorMessageToSafeHint(message)}`
|
|
108
|
+
|
|
109
|
+
// Preserve trace ID: from HTTP (__traceId) or WebSocket (traceparent in JSON body)
|
|
110
|
+
const traceId = response?.__traceId || response?.traceparent
|
|
111
|
+
if (traceId) {
|
|
112
|
+
revisedError.traceId = traceId
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
throw revisedError
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
return result
|
|
110
119
|
}
|
|
111
120
|
|
|
121
|
+
// Transport: WS only in ClarityServer, HTTP only in ClarityServerV2 (overridden)
|
|
112
122
|
async getAllTransactions(params) {
|
|
113
123
|
const transactions = { pending: [], confirmed: [] }
|
|
114
124
|
const cursor = await this.getTransactions({
|
|
@@ -128,6 +138,7 @@ export default class ClarityServer extends EventEmitter {
|
|
|
128
138
|
return { cursor, transactions }
|
|
129
139
|
}
|
|
130
140
|
|
|
141
|
+
// Transport: WS only (uses transactions socket, not RPC socket)
|
|
131
142
|
async getTransactions({ walletAccount, address, cursor, onChunk }) {
|
|
132
143
|
const socket = this.connectTransactions({ walletAccount, address })
|
|
133
144
|
const listener = (isPending, chunk, callback) => {
|
|
@@ -148,6 +159,7 @@ export default class ClarityServer extends EventEmitter {
|
|
|
148
159
|
.finally(() => socket.off('transactionsChunk', listener))
|
|
149
160
|
}
|
|
150
161
|
|
|
162
|
+
// Transport: WS only (uses fee socket)
|
|
151
163
|
getFeeFromWebSocket() {
|
|
152
164
|
const socket = this.connectFee()
|
|
153
165
|
return new Promise((resolve, reject) => {
|
|
@@ -164,27 +176,40 @@ export default class ClarityServer extends EventEmitter {
|
|
|
164
176
|
})
|
|
165
177
|
}
|
|
166
178
|
|
|
179
|
+
// Transport: WS only in ClarityServer, HTTP first → WS fallback in ClarityServerV2 (overridden)
|
|
167
180
|
async getFee() {
|
|
168
181
|
return this.getFeeFromWebSocket()
|
|
169
182
|
}
|
|
170
183
|
|
|
184
|
+
// Transport: Depends on getFee() - WS only in ClarityServer, HTTP first → WS fallback in ClarityServerV2
|
|
171
185
|
// for fee monitors
|
|
172
186
|
async getGasPrice() {
|
|
173
187
|
const fee = await this.getFee()
|
|
174
188
|
return fee?.gasPrice
|
|
175
189
|
}
|
|
176
190
|
|
|
191
|
+
async getGasPriceEstimation() {
|
|
192
|
+
return getFallbackGasPriceEstimation({ server: this })
|
|
193
|
+
}
|
|
194
|
+
|
|
177
195
|
async sendRpcRequest(rpcRequest) {
|
|
178
196
|
const rpcSocket = this.connectRpc()
|
|
179
197
|
return new Promise((resolve, reject) => {
|
|
180
198
|
const timeout = setTimeout(() => reject(new Error(RPC_REQUEST_TIMEOUT)), 3000)
|
|
181
199
|
rpcSocket.emit('request', rpcRequest, (response) => {
|
|
182
200
|
clearTimeout(timeout)
|
|
201
|
+
const rawTraceparent = response?.traceparent || response?.[0]?.traceparent
|
|
202
|
+
if (rawTraceparent) {
|
|
203
|
+
// eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
|
|
204
|
+
response.traceparent = TraceId.fromRawTraceparent(rawTraceparent)
|
|
205
|
+
}
|
|
206
|
+
|
|
183
207
|
resolve(response)
|
|
184
208
|
})
|
|
185
209
|
})
|
|
186
210
|
}
|
|
187
211
|
|
|
212
|
+
// Transport: WS only in ClarityServer, WS first → HTTP fallback in ClarityServerV2
|
|
188
213
|
async sendBatchRequest(batch) {
|
|
189
214
|
const responses = await this.sendRpcRequest(batch)
|
|
190
215
|
// FIXME: this falls apart if responses is not an array
|
|
@@ -201,12 +226,14 @@ export default class ClarityServer extends EventEmitter {
|
|
|
201
226
|
return batch.map((request) => keyed[`${request.id}`])
|
|
202
227
|
}
|
|
203
228
|
|
|
229
|
+
// Transport: Uses sendRpcRequest - WS only in ClarityServer, WS first → HTTP fallback in ClarityServerV2
|
|
204
230
|
async sendRequest(request) {
|
|
205
231
|
const response = await this.sendRpcRequest(request)
|
|
206
232
|
|
|
207
233
|
return this.handleJsonRPCResponse(response)
|
|
208
234
|
}
|
|
209
235
|
|
|
236
|
+
// Transport: Via getCode → sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
210
237
|
async isContract(address) {
|
|
211
238
|
const code = await this.getCode(address)
|
|
212
239
|
return code.length > 2
|
|
@@ -322,11 +349,13 @@ export default class ClarityServer extends EventEmitter {
|
|
|
322
349
|
})
|
|
323
350
|
}
|
|
324
351
|
|
|
352
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
325
353
|
async proxyToCoinNode(params) {
|
|
326
354
|
const request = this.buildRequest(params)
|
|
327
355
|
return this.sendRequest(request)
|
|
328
356
|
}
|
|
329
357
|
|
|
358
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
330
359
|
async balanceOf(address, tokenAddress, tag = 'latest') {
|
|
331
360
|
const request = this.balanceOfRequest(address, tokenAddress, tag)
|
|
332
361
|
const result = await this.sendRequest(request)
|
|
@@ -338,69 +367,83 @@ export default class ClarityServer extends EventEmitter {
|
|
|
338
367
|
}
|
|
339
368
|
}
|
|
340
369
|
|
|
370
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
341
371
|
async getBalance(...params) {
|
|
342
|
-
const request = this.getBalanceRequest(...params)
|
|
372
|
+
const request = this.getBalanceRequest(...params) // eth_getBalance
|
|
343
373
|
return this.sendRequest(request)
|
|
344
374
|
}
|
|
345
375
|
|
|
376
|
+
// Transport: Via getBalance → sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
346
377
|
async getBalanceProxied(...params) {
|
|
347
|
-
return this.getBalance(...params)
|
|
378
|
+
return this.getBalance(...params) // eth_getBalance
|
|
348
379
|
}
|
|
349
380
|
|
|
381
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
350
382
|
async gasPrice(...params) {
|
|
351
383
|
const request = this.gasPriceRequest(...params)
|
|
352
384
|
return this.sendRequest(request)
|
|
353
385
|
}
|
|
354
386
|
|
|
387
|
+
// Transport: WS only in ClarityServer, HTTP only in ClarityServerV2 (overridden)
|
|
355
388
|
async estimateGas(...params) {
|
|
356
389
|
const request = this.estimateGasRequest(...params)
|
|
357
390
|
return this.sendRequest(request)
|
|
358
391
|
}
|
|
359
392
|
|
|
393
|
+
// Transport: WS only in ClarityServer, HTTP only in ClarityServerV2 (overridden)
|
|
360
394
|
async sendRawTransaction(...params) {
|
|
361
395
|
const request = this.sendRawTransactionRequest(...params)
|
|
362
396
|
return this.sendRequest(request)
|
|
363
397
|
}
|
|
364
398
|
|
|
399
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
365
400
|
async getCode(...params) {
|
|
366
401
|
const request = this.getCodeRequest(...params)
|
|
367
402
|
return this.sendRequest(request)
|
|
368
403
|
}
|
|
369
404
|
|
|
405
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
370
406
|
async getStorageAt(...params) {
|
|
371
407
|
const request = this.getStorageAtRequest(...params)
|
|
372
408
|
return this.sendRequest(request)
|
|
373
409
|
}
|
|
374
410
|
|
|
411
|
+
// Transport: WS only in ClarityServer, HTTP only in ClarityServerV2 (overridden)
|
|
375
412
|
async getTransactionCount(...params) {
|
|
376
413
|
const request = this.getTransactionCountRequest(...params)
|
|
377
414
|
return this.sendRequest(request)
|
|
378
415
|
}
|
|
379
416
|
|
|
417
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
380
418
|
async getTransactionByHash(...params) {
|
|
381
419
|
const request = this.getTransactionByHashRequest(...params)
|
|
382
420
|
return this.sendRequest(request)
|
|
383
421
|
}
|
|
384
422
|
|
|
423
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
385
424
|
async getTransactionReceipt(...params) {
|
|
386
425
|
const request = this.getTransactionReceiptRequest(...params)
|
|
387
426
|
return this.sendRequest(request)
|
|
388
427
|
}
|
|
389
428
|
|
|
429
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
390
430
|
async ethCall(...params) {
|
|
391
431
|
const request = this.ethCallRequest(...params)
|
|
392
432
|
return this.sendRequest(request)
|
|
393
433
|
}
|
|
394
434
|
|
|
435
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
395
436
|
async blockNumber(...params) {
|
|
396
437
|
const request = this.blockNumberRequest(...params)
|
|
397
438
|
return this.sendRequest(request)
|
|
398
439
|
}
|
|
399
440
|
|
|
441
|
+
// Transport: Via getBlockByNumber → sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
400
442
|
async getLatestBlock() {
|
|
401
443
|
return this.getBlockByNumber('latest')
|
|
402
444
|
}
|
|
403
445
|
|
|
446
|
+
// Transport: Via getLatestBlock → getBlockByNumber → sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
404
447
|
async getBaseFeePerGas() {
|
|
405
448
|
const response = await this.getLatestBlock()
|
|
406
449
|
if (response.baseFeePerGas) {
|
|
@@ -408,46 +451,55 @@ export default class ClarityServer extends EventEmitter {
|
|
|
408
451
|
}
|
|
409
452
|
}
|
|
410
453
|
|
|
454
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
411
455
|
async getBlockByHash(...params) {
|
|
412
456
|
const request = this.getBlockByHashRequest(...params)
|
|
413
457
|
return this.sendRequest(request)
|
|
414
458
|
}
|
|
415
459
|
|
|
460
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
416
461
|
async getBlockTransactionCountByNumber(...params) {
|
|
417
462
|
const request = this.getBlockTransactionCountByNumberRequest(...params)
|
|
418
463
|
return this.sendRequest(request)
|
|
419
464
|
}
|
|
420
465
|
|
|
466
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
421
467
|
async getBlockByNumber(...params) {
|
|
422
468
|
const request = this.getBlockByNumberRequest(...params)
|
|
423
469
|
return this.sendRequest(request)
|
|
424
470
|
}
|
|
425
471
|
|
|
472
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
426
473
|
async simulateV1(...params) {
|
|
427
474
|
const request = this.simulateV1Request(...params)
|
|
428
475
|
return this.sendRequest(request)
|
|
429
476
|
}
|
|
430
477
|
|
|
478
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
431
479
|
async simulateRawTransaction(...params) {
|
|
432
480
|
const request = this.simulateRawTransactionRequest(...params)
|
|
433
481
|
return this.sendRequest(request)
|
|
434
482
|
}
|
|
435
483
|
|
|
484
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
436
485
|
async getCoinbase() {
|
|
437
486
|
const request = this.coinbaseRequest()
|
|
438
487
|
return this.sendRequest(request)
|
|
439
488
|
}
|
|
440
489
|
|
|
490
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
441
491
|
async getCompilers() {
|
|
442
492
|
const request = this.getCompilersRequest()
|
|
443
493
|
return this.sendRequest(request)
|
|
444
494
|
}
|
|
445
495
|
|
|
496
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
446
497
|
async getNetVersion() {
|
|
447
498
|
const request = this.getNetVersion()
|
|
448
499
|
return this.sendRequest(request)
|
|
449
500
|
}
|
|
450
501
|
|
|
502
|
+
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
451
503
|
async getLogs(...params) {
|
|
452
504
|
const request = this.getLogsRequest(...params)
|
|
453
505
|
return this.sendRequest(request)
|
|
@@ -22,7 +22,7 @@ export function createEvmServer({ assetName, serverUrl, monitorType }) {
|
|
|
22
22
|
assert(monitorType, 'monitorType is required')
|
|
23
23
|
switch (monitorType) {
|
|
24
24
|
case 'no-history':
|
|
25
|
-
return new ApiCoinNodesServer({ uri: serverUrl })
|
|
25
|
+
return new ApiCoinNodesServer({ baseAssetName: assetName, uri: serverUrl })
|
|
26
26
|
case 'clarity':
|
|
27
27
|
return new ClarityServer({ baseAssetName: assetName, uri: serverUrl })
|
|
28
28
|
case 'clarity-v2':
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
|
+
|
|
3
|
+
const desc = (a, b) => (a > b ? -1 : b > a ? 1 : 0)
|
|
4
|
+
|
|
5
|
+
export const getFallbackGasPriceEstimation = async ({ server }) => {
|
|
6
|
+
const [latestBlock, gasPrice] = await Promise.all([server.getLatestBlock(), server.getGasPrice()])
|
|
7
|
+
|
|
8
|
+
assert(latestBlock, 'expected latestBlock')
|
|
9
|
+
assert(gasPrice, 'expected gasPrice')
|
|
10
|
+
|
|
11
|
+
const baseFeePerGas = latestBlock.baseFeePerGas
|
|
12
|
+
if (!baseFeePerGas) return { gasPrice }
|
|
13
|
+
|
|
14
|
+
const [max, min] = [BigInt(gasPrice), BigInt(baseFeePerGas)].sort(desc)
|
|
15
|
+
|
|
16
|
+
const toHex = (b) => `0x${b.toString(16)}`
|
|
17
|
+
|
|
18
|
+
// TODO: Use `eth_feeHistory` or `eth_maxPriorityFeePerGas`
|
|
19
|
+
// instead (requires allowlist at the RPC).
|
|
20
|
+
// HACK: Infer the RPC's implicit `tipGasPrice`:
|
|
21
|
+
// https://github.com/ethereum/go-ethereum/blob/d3dd48e59db28ea04bd92e4337cdd488ccb8fbec/internal/ethapi/api.go#L69C1-L79C2
|
|
22
|
+
const maxPriorityFeePerGas50Percentile = max - min
|
|
23
|
+
|
|
24
|
+
const rewardPercentiles = {
|
|
25
|
+
25: toHex(maxPriorityFeePerGas50Percentile / BigInt(2)),
|
|
26
|
+
50: toHex(maxPriorityFeePerGas50Percentile),
|
|
27
|
+
75: toHex((maxPriorityFeePerGas50Percentile * BigInt(3)) / BigInt(2)),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { gasPrice, baseFeePerGas, rewardPercentiles }
|
|
31
|
+
}
|
package/src/fee-utils.js
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import assert from 'minimalistic-assert'
|
|
2
2
|
|
|
3
|
-
export const shouldFetchEthLikeFallbackGasPrices = async ({ eip1559Enabled, server }) => {
|
|
4
|
-
const [gasPrice, baseFeePerGas] = await Promise.all([
|
|
5
|
-
server.getGasPrice(),
|
|
6
|
-
eip1559Enabled ? server.getBaseFeePerGas() : undefined,
|
|
7
|
-
])
|
|
8
|
-
|
|
9
|
-
return { gasPrice, baseFeePerGas }
|
|
10
|
-
}
|
|
11
|
-
|
|
12
3
|
export const applyMultiplierToPrice = ({ feeAsset, gasPriceMultiplier, price }) => {
|
|
13
4
|
assert(typeof price === 'string', 'price should be a string')
|
|
14
5
|
return feeAsset.currency
|
package/src/index.js
CHANGED
|
@@ -88,7 +88,13 @@ export {
|
|
|
88
88
|
fromHexToBN,
|
|
89
89
|
} from './number-utils.js'
|
|
90
90
|
|
|
91
|
-
export {
|
|
91
|
+
export {
|
|
92
|
+
EVM_ERROR_REASONS,
|
|
93
|
+
EVM_ERROR_TYPES,
|
|
94
|
+
getEvmErrorReason,
|
|
95
|
+
withErrorReason,
|
|
96
|
+
EthLikeError,
|
|
97
|
+
} from './error-wrapper.js'
|
|
92
98
|
|
|
93
99
|
export { txSendFactory } from './tx-send/index.js'
|
|
94
100
|
|
package/src/nft-utils.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { safeString } from '@exodus/safe-string'
|
|
1
2
|
import SolidityContract from '@exodus/solidity-contract'
|
|
2
3
|
import assert from 'minimalistic-assert'
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
+
import { EthLikeError, EVM_ERROR_REASONS } from './error-wrapper.js'
|
|
5
6
|
import { fetchGasLimit } from './gas-estimation.js'
|
|
6
7
|
|
|
7
8
|
export const getNftArguments = async ({ asset, nft, fromAddress, toAddress }) => {
|
|
@@ -44,10 +45,11 @@ export const getNftArguments = async ({ asset, nft, fromAddress, toAddress }) =>
|
|
|
44
45
|
}
|
|
45
46
|
})
|
|
46
47
|
).catch((e) => {
|
|
47
|
-
throw new
|
|
48
|
+
throw new EthLikeError({
|
|
48
49
|
message: errors.join('\n'),
|
|
49
|
-
|
|
50
|
-
hint:
|
|
50
|
+
errorReasonInfo: EVM_ERROR_REASONS.fetchGasLimitFailed,
|
|
51
|
+
hint: safeString`getNftArguments`,
|
|
52
|
+
traceId: e.traceId,
|
|
51
53
|
})
|
|
52
54
|
})
|
|
53
55
|
return {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createContract, createEthereumJsTx } from '@exodus/ethereum-lib'
|
|
2
2
|
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
3
|
+
import { safeString } from '@exodus/safe-string'
|
|
3
4
|
import assert from 'minimalistic-assert'
|
|
4
5
|
|
|
6
|
+
import { EVM_ERROR_REASONS, withErrorReason } from '../error-wrapper.js'
|
|
5
7
|
import { fromHexToBigInt } from '../number-utils.js'
|
|
6
8
|
|
|
7
9
|
export const estimateL1DataFeeFactory = ({ l1GasOracleAddress, server }) => {
|
|
@@ -14,7 +16,12 @@ export const estimateL1DataFeeFactory = ({ l1GasOracleAddress, server }) => {
|
|
|
14
16
|
const callData = gasContract.getL1Fee.build(serialized)
|
|
15
17
|
const buffer = Buffer.from(callData)
|
|
16
18
|
const data = bufferToHex(buffer)
|
|
17
|
-
const hex = await
|
|
19
|
+
const hex = await withErrorReason({
|
|
20
|
+
promise: server.ethCall({ to: l1GasOracleAddress, data }, 'latest'),
|
|
21
|
+
errorReasonInfo: EVM_ERROR_REASONS.ethCallFailed,
|
|
22
|
+
hint: safeString`estimateL1DataFee`,
|
|
23
|
+
baseAssetName: server.baseAssetName,
|
|
24
|
+
})
|
|
18
25
|
const l1DataFee = fromHexToBigInt(hex)
|
|
19
26
|
const padFee = l1DataFee / BigInt(4)
|
|
20
27
|
const maxL1DataFee = l1DataFee + padFee
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { FeeMonitor } from '@exodus/asset-lib'
|
|
2
2
|
import assert from 'minimalistic-assert'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
calculateEthLikeFeeMonitorUpdate,
|
|
6
|
-
shouldFetchEthLikeFallbackGasPrices,
|
|
7
|
-
} from './fee-utils.js'
|
|
4
|
+
import { calculateEthLikeFeeMonitorUpdate } from './fee-utils.js'
|
|
8
5
|
|
|
9
6
|
/**
|
|
10
7
|
* Generic eth server based fee monitor.
|
|
@@ -25,11 +22,6 @@ export const serverBasedFeeMonitorFactoryFactory = ({ asset, interval, server, a
|
|
|
25
22
|
assert(server, 'server is required')
|
|
26
23
|
assert(aci, 'aci is required')
|
|
27
24
|
|
|
28
|
-
const shouldFetchGasPrices = async () => {
|
|
29
|
-
const { eip1559Enabled } = await aci.getFeeConfig({ assetName: asset.name })
|
|
30
|
-
return shouldFetchEthLikeFallbackGasPrices({ eip1559Enabled, server })
|
|
31
|
-
}
|
|
32
|
-
|
|
33
25
|
const FeeMonitorClass = class ServerBaseEthereumFeeMonitor extends FeeMonitor {
|
|
34
26
|
constructor({ updateFee }) {
|
|
35
27
|
assert(updateFee, 'updateFee is required')
|
|
@@ -44,7 +36,7 @@ export const serverBasedFeeMonitorFactoryFactory = ({ asset, interval, server, a
|
|
|
44
36
|
return calculateEthLikeFeeMonitorUpdate({
|
|
45
37
|
assetClientInterface: aci,
|
|
46
38
|
feeAsset: asset,
|
|
47
|
-
fetchedGasPrices: await
|
|
39
|
+
fetchedGasPrices: await server.getGasPriceEstimation(),
|
|
48
40
|
})
|
|
49
41
|
}
|
|
50
42
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createContract } from '@exodus/ethereum-lib'
|
|
2
2
|
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
3
|
+
import { safeString } from '@exodus/safe-string'
|
|
3
4
|
import { retry } from '@exodus/simple-retry'
|
|
4
5
|
|
|
6
|
+
import { EVM_ERROR_REASONS, withErrorReason } from '../../error-wrapper.js'
|
|
5
7
|
import { getServerByName } from '../../exodus-eth-server/index.js'
|
|
6
8
|
|
|
7
9
|
// TODO: Shouldn't this be a function of `ethereumStakingState.minDelegateAmount`?
|
|
@@ -78,7 +80,12 @@ export class EthereumStaking {
|
|
|
78
80
|
if (typeof from === 'string' && from.length > 0) data.from = from
|
|
79
81
|
|
|
80
82
|
const eth = this.server || getServerByName(this.asset.name)
|
|
81
|
-
return
|
|
83
|
+
return withErrorReason({
|
|
84
|
+
promise: retry((...args) => eth.ethCall(...args), { delayTimesMs: RETRY_DELAYS })(data),
|
|
85
|
+
errorReasonInfo: EVM_ERROR_REASONS.ethCallFailed,
|
|
86
|
+
hint: safeString`stakingEthReadFunctionContract`,
|
|
87
|
+
baseAssetName: eth.baseAssetName,
|
|
88
|
+
})
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
// === ACCOUNTING ===
|
package/src/staking/matic/api.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createContract } from '@exodus/ethereum-lib'
|
|
2
2
|
import ethAssets from '@exodus/ethereum-meta'
|
|
3
3
|
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
4
|
+
import { safeString } from '@exodus/safe-string'
|
|
4
5
|
import { retry } from '@exodus/simple-retry'
|
|
5
6
|
import BN from 'bn.js'
|
|
6
7
|
|
|
8
|
+
import { EVM_ERROR_REASONS, withErrorReason } from '../../error-wrapper.js'
|
|
7
9
|
import { getServerByName } from '../../exodus-eth-server/index.js'
|
|
8
10
|
import { fromHexToBN, fromHexToString, splitIn32BytesArray } from '../../number-utils.js'
|
|
9
11
|
|
|
@@ -43,7 +45,12 @@ export class MaticStakingApi {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
const eth = this.server || getServerByName('ethereum')
|
|
46
|
-
return
|
|
48
|
+
return withErrorReason({
|
|
49
|
+
promise: retry((...args) => eth.ethCall(...args), { delayTimesMs: RETRY_DELAYS })(data),
|
|
50
|
+
errorReasonInfo: EVM_ERROR_REASONS.ethCallFailed,
|
|
51
|
+
hint: safeString`stakingMaticReadFunctionContract`,
|
|
52
|
+
baseAssetName: eth.baseAssetName,
|
|
53
|
+
})
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
getWithdrawalDelay = async () => {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { EthLikeError, EVM_ERROR_REASONS, getEvmErrorReason } from '../error-wrapper.js'
|
|
2
|
+
import { transactionExists } from '../eth-like-util.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handles broadcast errors by parsing the error message and either throwing
|
|
6
|
+
* an appropriate EthLikeError or returning info for retry logic.
|
|
7
|
+
*
|
|
8
|
+
* @param {Error} err - The error from broadcastTx.
|
|
9
|
+
* @param {Object} options - Context for error handling.
|
|
10
|
+
* @param {Object} options.asset - The asset.
|
|
11
|
+
* @param {string} options.txId - The transaction ID.
|
|
12
|
+
* @param {boolean} options.isHardware - Whether this is a hardware wallet.
|
|
13
|
+
* @param {string} options.hint - Hint for the error.
|
|
14
|
+
* @returns {Promise<{ shouldRetry: boolean }>} - Returns if nonce too low and can retry.
|
|
15
|
+
* @throws {EthLikeError} - Throws for all other error cases.
|
|
16
|
+
*/
|
|
17
|
+
export const handleBroadcastError = async (err, { asset, txId, isHardware, hint, isBumpTx }) => {
|
|
18
|
+
const message = err.message
|
|
19
|
+
|
|
20
|
+
const errorInfo = getEvmErrorReason(message) || EVM_ERROR_REASONS.broadcastTxFailed
|
|
21
|
+
|
|
22
|
+
const isNonceTooLow = errorInfo.reason === EVM_ERROR_REASONS.nonceTooLow.reason
|
|
23
|
+
const isAmbiguousError =
|
|
24
|
+
isNonceTooLow || errorInfo.reason === EVM_ERROR_REASONS.transactionUnderpriced.reason
|
|
25
|
+
|
|
26
|
+
if (errorInfo.reason === EVM_ERROR_REASONS.alreadyKnown.reason) {
|
|
27
|
+
console.info('tx already broadcast')
|
|
28
|
+
return { shouldRetry: false }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let txAlreadyExists = false
|
|
32
|
+
if (isAmbiguousError) {
|
|
33
|
+
try {
|
|
34
|
+
txAlreadyExists = await transactionExists({ asset, txId })
|
|
35
|
+
} catch (verifyErr) {
|
|
36
|
+
// Can't verify - fall through to original error handling
|
|
37
|
+
console.warn('Could not verify tx existence:', verifyErr.message)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (txAlreadyExists) {
|
|
42
|
+
console.info('tx already broadcast')
|
|
43
|
+
return { shouldRetry: false }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// NOTE: Don't auto-retry nonce repair for bump/replacement txs.
|
|
47
|
+
// A replacement must keep the *same nonce* as the tx it's replacing.
|
|
48
|
+
// If we "fix" a bump tx by advancing the nonce, we create a brand-new tx instead of replacing the pending one.
|
|
49
|
+
if (isNonceTooLow && !isHardware && !isBumpTx) return { shouldRetry: true }
|
|
50
|
+
|
|
51
|
+
throw new EthLikeError({
|
|
52
|
+
message: err.message,
|
|
53
|
+
errorReasonInfo: errorInfo,
|
|
54
|
+
hint,
|
|
55
|
+
traceId: err.traceId,
|
|
56
|
+
baseAssetName: asset.baseAsset.name,
|
|
57
|
+
})
|
|
58
|
+
}
|