@exodus/ethereum-api 8.64.6 → 8.65.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,28 @@
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.65.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.64.5...@exodus/ethereum-api@8.65.0) (2026-03-03)
7
+
8
+
9
+ ### Features
10
+
11
+
12
+ * feat: encode per-network support flag, fix stale delegation state, eip7702 whitelist (#7477)
13
+
14
+ * feat: gas price bump for evm (#7500)
15
+
16
+
17
+
18
+ ## [8.64.7](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.64.5...@exodus/ethereum-api@8.64.7) (2026-02-24)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+
24
+ * fix: harden EthlikeError and improve error handling in tx-send (#7308)
25
+
26
+
27
+
6
28
  ## [8.64.6](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.64.5...@exodus/ethereum-api@8.64.6) (2026-02-23)
7
29
 
8
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.64.6",
3
+ "version": "8.65.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",
@@ -35,9 +35,10 @@
35
35
  "@exodus/ethereumjs": "^1.8.0",
36
36
  "@exodus/fetch": "^1.3.0",
37
37
  "@exodus/models": "^12.13.0",
38
- "@exodus/safe-string": "^1.2.1",
38
+ "@exodus/safe-string": "^1.4.0",
39
39
  "@exodus/simple-retry": "^0.0.6",
40
40
  "@exodus/solidity-contract": "^1.3.0",
41
+ "@exodus/traceparent": "^3.0.1",
41
42
  "@exodus/web3-ethereum-utils": "^4.6.0",
42
43
  "bn.js": "^5.2.1",
43
44
  "delay": "^4.0.1",
@@ -54,7 +55,7 @@
54
55
  "devDependencies": {
55
56
  "@exodus/assets-testing": "^1.0.0",
56
57
  "@exodus/bsc-meta": "^2.5.1",
57
- "@exodus/errors": "^3.3.0",
58
+ "@exodus/errors": "^3.8.0",
58
59
  "@exodus/ethereumarbone-meta": "^2.1.2",
59
60
  "@exodus/fantommainnet-meta": "^2.0.5",
60
61
  "@exodus/matic-meta": "^2.2.7",
@@ -67,5 +68,5 @@
67
68
  "type": "git",
68
69
  "url": "git+https://github.com/ExodusMovement/assets.git"
69
70
  },
70
- "gitHead": "05a14c42a3963199e2b0b11d0a643884ebae8e46"
71
+ "gitHead": "98c4dc4e2cd52781dadc2af4fae2b80ecc276626"
71
72
  }
@@ -141,6 +141,7 @@ export const createHistoryMonitorFactory = ({
141
141
  stakingAssetNames,
142
142
  rpcBalanceAssetNames,
143
143
  wsGatewayUri,
144
+ eip7702Supported,
144
145
  }) => {
145
146
  assert(assetName, 'expected assetName')
146
147
  assert(assetClientInterface, 'expected assetClientInterface')
@@ -158,6 +159,7 @@ export const createHistoryMonitorFactory = ({
158
159
  interval: ms(monitorInterval || '5m'),
159
160
  server,
160
161
  rpcBalanceAssetNames,
162
+ eip7702Supported,
161
163
  ...args,
162
164
  })
163
165
  break
@@ -168,6 +170,7 @@ export const createHistoryMonitorFactory = ({
168
170
  server,
169
171
  rpcBalanceAssetNames,
170
172
  wsGatewayClient: createWsGateway({ uri: wsGatewayUri }),
173
+ eip7702Supported,
171
174
  ...args,
172
175
  })
173
176
  break
@@ -176,6 +179,7 @@ export const createHistoryMonitorFactory = ({
176
179
  assetClientInterface,
177
180
  interval: ms(monitorInterval || '15s'),
178
181
  server,
182
+ eip7702Supported,
179
183
  ...args,
180
184
  })
181
185
  break
@@ -73,6 +73,7 @@ export const createAssetFactory = ({
73
73
  delisted = false,
74
74
  privacyRpcUrl: defaultPrivacyRpcUrl,
75
75
  wsGatewayUri: defaultWsGatewayUri,
76
+ eip7702Supported,
76
77
  }) => {
77
78
  assert(assetsList, 'assetsList is required')
78
79
  assert(providedFeeData || feeDataConfig, 'feeData or feeDataConfig is required')
@@ -233,6 +234,7 @@ export const createAssetFactory = ({
233
234
  stakingAssetNames,
234
235
  rpcBalanceAssetNames,
235
236
  wsGatewayUri,
237
+ eip7702Supported,
236
238
  })
237
239
 
238
240
  const defaultAddressPath = 'm/0/0'
@@ -326,6 +328,7 @@ export const createAssetFactory = ({
326
328
  broadcastPrivateBundle,
327
329
  broadcastPrivateTx,
328
330
  forceGasLimitEstimation,
331
+ eip7702Supported,
329
332
  getEIP7702Delegation: (addr) => getEIP7702Delegation({ address: addr, server }),
330
333
  getNonce,
331
334
  privacyServer,
package/src/ens/index.js CHANGED
@@ -64,4 +64,5 @@ const resolveEnsAddress = async (address, server) => {
64
64
  return resolver.decodeOutput({ data, method: 'name' })[0]
65
65
  }
66
66
 
67
+ // This doesn't seem to be used anywhere.
67
68
  export { resolveEnsAddress, resolveEnsName }
@@ -1,13 +1,277 @@
1
- export const reasons = {
2
- fetchGasLimitFailed: 'Fetch gas limit failed',
3
- nonceFetchFailed: 'Nonce fetch failed',
4
- balanceFetchFailed: 'Balance fetch failed',
5
- broadcastTxFailed: 'Broadcast tx failed',
6
- getTransactionByHashFailed: 'Get transaction by hash failed',
7
- ethCallErc20Failed: 'Eth call erc20 failed',
8
- insufficientFunds: 'Insufficient funds',
9
- bumpTxFailed: 'Bump tx failed',
10
- transactionUnderpriced: 'Transaction underpriced',
1
+ import { safeString } from '@exodus/safe-string'
2
+
3
+ // Error types for categorizing EVM errors
4
+ export const EVM_ERROR_TYPES = {
5
+ NODE_STATE_READ: safeString`NODE_STATE_READ`, // RPC-level read operations
6
+ CONTRACT_CALL: safeString`CONTRACT_CALL`, // Smart contract calls (eth_call, estimateGas)
7
+ BROADCAST: safeString`BROADCAST`, // Transaction broadcast errors (includes reverts)
8
+ }
9
+
10
+ // Operation-specific reasons
11
+ export const EVM_ERROR_REASONS = {
12
+ // RPC-level read operations
13
+ balanceFetchFailed: {
14
+ reason: safeString`balance fetch failed`,
15
+ type: EVM_ERROR_TYPES.NODE_STATE_READ,
16
+ },
17
+ nonceFetchFailed: {
18
+ reason: safeString`nonce fetch failed`,
19
+ type: EVM_ERROR_TYPES.NODE_STATE_READ,
20
+ },
21
+ getTransactionByHashFailed: {
22
+ reason: safeString`get transaction by hash failed`,
23
+ type: EVM_ERROR_TYPES.NODE_STATE_READ,
24
+ },
25
+ getCodeFailed: {
26
+ reason: safeString`get code failed`,
27
+ type: EVM_ERROR_TYPES.NODE_STATE_READ,
28
+ },
29
+ getGasPriceFailed: {
30
+ reason: safeString`get gas price failed`,
31
+ type: EVM_ERROR_TYPES.NODE_STATE_READ,
32
+ },
33
+ getBaseFeePerGasFailed: {
34
+ reason: safeString`get base fee per gas failed`,
35
+ type: EVM_ERROR_TYPES.NODE_STATE_READ,
36
+ },
37
+
38
+ // Generic smart contract call failure.
39
+ ethCallFailed: {
40
+ reason: safeString`eth call failed`,
41
+ type: EVM_ERROR_TYPES.CONTRACT_CALL,
42
+ },
43
+ ethCallErc20Failed: {
44
+ reason: safeString`eth call erc20 failed`,
45
+ type: EVM_ERROR_TYPES.CONTRACT_CALL,
46
+ },
47
+
48
+ // Gas Usage Simulation (checks contract reverts)
49
+ fetchGasLimitFailed: {
50
+ reason: safeString`fetch gas limit failed`,
51
+ type: EVM_ERROR_TYPES.CONTRACT_CALL,
52
+ },
53
+
54
+ // Write operations (checks sender account state/ability to transact)
55
+ // Generic failure, no info about a particular smart contract revert.
56
+ broadcastTxFailed: {
57
+ reason: safeString`broadcast tx failed`,
58
+ type: EVM_ERROR_TYPES.BROADCAST,
59
+ },
60
+
61
+ // Reversion reasons
62
+ executionReverted: {
63
+ reason: safeString`execution reverted`,
64
+ type: EVM_ERROR_TYPES.BROADCAST,
65
+ },
66
+ invalidOpcode: {
67
+ reason: safeString`invalid opcode`,
68
+ type: EVM_ERROR_TYPES.BROADCAST,
69
+ },
70
+ insufficientFundsForGas: {
71
+ reason: safeString`insufficient funds for gas * price + value`,
72
+ type: EVM_ERROR_TYPES.BROADCAST,
73
+ },
74
+ alreadyKnown: {
75
+ reason: safeString`already known`,
76
+ type: EVM_ERROR_TYPES.BROADCAST,
77
+ },
78
+ nonceTooLow: {
79
+ reason: safeString`nonce too low`,
80
+ type: EVM_ERROR_TYPES.BROADCAST,
81
+ },
82
+ insufficientBalance: {
83
+ reason: safeString`execution reverted: insufficient balance / transfer exceeds balance`,
84
+ type: EVM_ERROR_TYPES.BROADCAST,
85
+ },
86
+ replacementTransactionUnderpriced: {
87
+ reason: safeString`replacement transaction underpriced`,
88
+ type: EVM_ERROR_TYPES.BROADCAST,
89
+ },
90
+ zeroTransfer: {
91
+ reason: safeString`execution reverted: transfer amount must be greater than zero`,
92
+ type: EVM_ERROR_TYPES.BROADCAST,
93
+ },
94
+ mathError: {
95
+ reason: safeString`execution reverted: arithmetic overflow/underflow`,
96
+ type: EVM_ERROR_TYPES.BROADCAST,
97
+ },
98
+
99
+ transactionUnderpriced: {
100
+ reason: safeString`transaction underpriced`,
101
+ type: EVM_ERROR_TYPES.BROADCAST,
102
+ },
103
+ nonceTooHigh: {
104
+ reason: safeString`nonce too high`,
105
+ type: EVM_ERROR_TYPES.BROADCAST,
106
+ },
107
+ gasPriceBelowMin: {
108
+ reason: safeString`transaction gas price below minimum`,
109
+ type: EVM_ERROR_TYPES.BROADCAST,
110
+ },
111
+ insufficientGas: {
112
+ reason: safeString`insufficient gas`,
113
+ type: EVM_ERROR_TYPES.BROADCAST,
114
+ },
115
+ futureTransactionReplace: {
116
+ reason: safeString`future transaction tries to replace pending`,
117
+ type: EVM_ERROR_TYPES.BROADCAST,
118
+ },
119
+ eip155Required: {
120
+ reason: safeString`eip-155 replay protection required`,
121
+ type: EVM_ERROR_TYPES.BROADCAST,
122
+ },
123
+ invalidRlpSerialization: {
124
+ reason: safeString`invalid rlp serialization`,
125
+ type: EVM_ERROR_TYPES.BROADCAST,
126
+ },
127
+ transactionTypeNotSupported: {
128
+ reason: safeString`transaction type not supported`,
129
+ type: EVM_ERROR_TYPES.BROADCAST,
130
+ },
131
+ delegatedAccountError: {
132
+ reason: safeString`delegated account error`,
133
+ type: EVM_ERROR_TYPES.BROADCAST,
134
+ },
135
+ invalidJumpDestination: {
136
+ reason: safeString`invalid jump destination`,
137
+ type: EVM_ERROR_TYPES.BROADCAST,
138
+ },
139
+ }
140
+
141
+ // Patterns to normalize (group similar errors together)
142
+ const EVM_NORMALIZATION_PATTERNS = [
143
+ // Insufficient funds for gas (native currency, not tokens)
144
+ {
145
+ pattern: /(insufficient funds for gas \* price \+ value|insufficient funds(?!.*balance))/i,
146
+ errorInfo: EVM_ERROR_REASONS.insufficientFundsForGas,
147
+ },
148
+ // "nonce too low" or "nonce is too low" - tx nonce already used
149
+ {
150
+ pattern: /\bnonce\b.*\btoo low\b/i,
151
+ errorInfo: EVM_ERROR_REASONS.nonceTooLow,
152
+ },
153
+ // Comprehensive insufficient balance / transfer exceeds balance pattern (TOKEN balance issues)
154
+ // Catches: ERC20/BEP20, 0xe450d38c custom error, insufficient balance, balanceNotEnough,
155
+ // and token-specific "transfer amount exceeds balance" (BaseToken, SaveYourAssets, XVS, Ondo, FLOKI, etc.)
156
+ {
157
+ pattern:
158
+ /(0xe450d38c|insufficient.?balance|balance.?not.?enough|not enough fund|(erc20|bep20):.*transfer amount exceeds balance|\w+:.*transfer amount exceeds balance)/i,
159
+ errorInfo: EVM_ERROR_REASONS.insufficientBalance,
160
+ },
161
+ // Arithmetic overflow/underflow errors (SafeMath, Solidity 0.8+, etc.)
162
+ {
163
+ pattern: /(safemath:.*overflow|arithmetic underflow or overflow)/i,
164
+ errorInfo: EVM_ERROR_REASONS.mathError,
165
+ },
166
+ // Transfer amount must be greater than zero (various phrasings)
167
+ {
168
+ pattern:
169
+ /(transfer amount must be greater than zero|amount must be greater than 0|amt must be over than 0|zero_amount|transfer amount must be positive|transfer amount zero)/i,
170
+ errorInfo: EVM_ERROR_REASONS.zeroTransfer,
171
+ },
172
+ // Gas price below network minimum (not mempool competition, but protocol minimum)
173
+ // "gas price below minimum", "gas tip cap...minimum", "max fee per gas less than block base fee"
174
+ {
175
+ pattern:
176
+ /gas price below minimum|gas tip cap.*minimum|max fee per gas less than block base fee/i,
177
+ errorInfo: EVM_ERROR_REASONS.gasPriceBelowMin,
178
+ },
179
+ // Gas limit errors
180
+ // "intrinsic gas too low", "insufficient gas", "floor data gas cost" - gas limit below minimum required
181
+ {
182
+ pattern: /intrinsic gas too low|insufficient gas|floor data gas cost/i,
183
+ errorInfo: EVM_ERROR_REASONS.insufficientGas,
184
+ },
185
+ // "nonce too high" or "nonce is too high" - tx nonce too far ahead
186
+ {
187
+ pattern: /\bnonce\b.*\btoo high\b/i,
188
+ errorInfo: EVM_ERROR_REASONS.nonceTooHigh,
189
+ },
190
+ // Transaction type not supported
191
+ // "transaction type not supported", "tx type not supported" - chain doesn't support this tx type (e.g., EIP-1559)
192
+ {
193
+ pattern: /transaction type not supported|tx type not supported/i,
194
+ errorInfo: EVM_ERROR_REASONS.transactionTypeNotSupported,
195
+ },
196
+ // Invalid jump destination
197
+ {
198
+ pattern: /invalid jump destination/i,
199
+ errorInfo: EVM_ERROR_REASONS.invalidJumpDestination,
200
+ },
201
+ // Transaction already in mempool/chain
202
+ // "already known", "known transaction", "already imported", "already in mempool"
203
+ {
204
+ pattern: /already known|known transaction|already imported|already in mempool/i,
205
+ errorInfo: EVM_ERROR_REASONS.alreadyKnown,
206
+ },
207
+ // Replacement transaction underpriced (must come BEFORE "transaction underpriced")
208
+ {
209
+ pattern: /replacement transaction underpriced/i,
210
+ errorInfo: EVM_ERROR_REASONS.replacementTransactionUnderpriced,
211
+ },
212
+ // Transaction underpriced (checked after replacement to avoid double match)
213
+ {
214
+ pattern: /transaction underpriced/i,
215
+ errorInfo: EVM_ERROR_REASONS.transactionUnderpriced,
216
+ },
217
+ // Replay-protected transactions required (EIP-155 chain ID signing)
218
+ // "only replay-protected", "replay-protected transaction/tx required", "not replay protected", "invalid chain id"
219
+ {
220
+ pattern:
221
+ /only replay-protected|replay-protected (transaction|tx) required|not replay protected|invalid chain id/i,
222
+ errorInfo: EVM_ERROR_REASONS.eip155Required,
223
+ },
224
+ // Future transaction tries to replace pending (nonce conflict)
225
+ {
226
+ pattern: /future transaction.*replace.*pending/i,
227
+ errorInfo: EVM_ERROR_REASONS.futureTransactionReplace,
228
+ },
229
+ // Serialization/signature errors
230
+ // "rlp:", "invalid rlp", "decode rlp", "invalid sender", "invalid signature", "bad signature"
231
+ {
232
+ pattern: /rlp:|invalid rlp|decode rlp|invalid sender|invalid signature|bad signature/i,
233
+ errorInfo: EVM_ERROR_REASONS.invalidRlpSerialization,
234
+ },
235
+ // EIP-7702 delegation errors
236
+ // "delegated" (but not "delegatecall"), "eip-7702", "eip7702" - account delegation related errors
237
+ {
238
+ pattern: /\bdelegated\b(?!\s*call)|eip-?7702/i,
239
+ errorInfo: EVM_ERROR_REASONS.delegatedAccountError,
240
+ },
241
+ // GENERIC CATCH-ALL PATTERNS (must be last)
242
+ {
243
+ pattern: /^execution reverted$/i,
244
+ errorInfo: EVM_ERROR_REASONS.executionReverted,
245
+ },
246
+ {
247
+ pattern: /\binvalid opcode\b/i,
248
+ errorInfo: EVM_ERROR_REASONS.invalidOpcode,
249
+ },
250
+ {
251
+ pattern: /^execution reverted: 0x$/i,
252
+ errorInfo: EVM_ERROR_REASONS.executionReverted,
253
+ },
254
+ // Final catch-all for any remaining "execution reverted:" messages
255
+ {
256
+ pattern: /execution reverted:/i,
257
+ errorInfo: EVM_ERROR_REASONS.executionReverted,
258
+ },
259
+ ]
260
+
261
+ // This should be renamed something better
262
+ export function getEvmErrorReason(errorMessage) {
263
+ for (const { pattern, errorInfo } of EVM_NORMALIZATION_PATTERNS) {
264
+ if (pattern.test(errorMessage)) {
265
+ return {
266
+ reason: errorInfo.reason,
267
+ type: errorInfo.type,
268
+ wasNormalized: true,
269
+ patternUsed: pattern.toString(),
270
+ }
271
+ }
272
+ }
273
+
274
+ return null
11
275
  }
12
276
 
13
277
  const MAX_HINT_LENGTH = 100
@@ -19,15 +283,22 @@ export class EthLikeError extends Error {
19
283
  * Creates an instance of EthLikeError.
20
284
  *
21
285
  * @param {string} message - Standard error message.
22
- * @param {string} reason - A constant indicating the generic failure. Must not contain any sensitive information such as private keys, transaction IDs, or wallet addresses.
286
+ * @param {Object} errorReasonInfo - Object containing reason and type for the error.
287
+ * @param {string} errorReasonInfo.reason - A constant indicating the generic failure. Must not contain any sensitive information such as private keys, transaction IDs, or wallet addresses.
288
+ * @param {string} errorReasonInfo.type - A category for the error (e.g., 'NODE_STATE_READ', 'CONTRACT_CALL', 'BROADCAST').
23
289
  * @param {string} hint - A hint to help the user understand the error. Must not contain any sensitive information such as private keys, transaction IDs, or wallet addresses.
290
+ * @param {TraceId} [traceId] - Optional TraceId object from HTTP response 'traceparent' header for debugging.
291
+ * @param {string} [baseAssetName] - Optional base asset name (e.g. 'ethereum', 'bnbsmartchain') for identifying the network.
24
292
  */
25
- constructor({ message, reason, hint }) {
293
+ constructor({ message, errorReasonInfo, hint, traceId, baseAssetName }) {
26
294
  super(message)
27
- this.name = 'EthLikeError'
295
+ this.name = safeString`EthLikeError`
28
296
  this.#hintStack = [hint] // NOTE: we can add more hints to the stack
29
- this.reason = reason
297
+ this.reason = errorReasonInfo.reason
30
298
  this.hint = this.#extractHint(hint)
299
+ this.type = errorReasonInfo.type
300
+ this.traceId = traceId
301
+ this.baseAssetName = baseAssetName
31
302
  }
32
303
 
33
304
  addHint = (hint) => {
@@ -41,6 +312,15 @@ export class EthLikeError extends Error {
41
312
  return this
42
313
  }
43
314
 
315
+ getContext = () => {
316
+ return {
317
+ reason: this.reason,
318
+ type: this.type,
319
+ traceId: this.traceId,
320
+ baseAssetName: this.baseAssetName,
321
+ }
322
+ }
323
+
44
324
  // NOTE: a class method for now, if we move this to be a generic error for all assets, each asset will have its own implementation
45
325
  #extractHint = (hint) => {
46
326
  if (!hint) return ''
@@ -72,7 +352,7 @@ export class EthLikeError extends Error {
72
352
  }
73
353
  }
74
354
 
75
- export const withErrorReason = async (promise, errorReason, hint) => {
355
+ export const withErrorReason = async ({ promise, errorReasonInfo, hint, baseAssetName }) => {
76
356
  try {
77
357
  return await promise
78
358
  } catch (err) {
@@ -84,8 +364,10 @@ export const withErrorReason = async (promise, errorReason, hint) => {
84
364
 
85
365
  throw new EthLikeError({
86
366
  message: err.message,
87
- reason: errorReason,
367
+ errorReasonInfo,
88
368
  hint,
369
+ traceId: err.traceId,
370
+ baseAssetName,
89
371
  })
90
372
  }
91
373
  }
@@ -1,9 +1,10 @@
1
1
  import { memoizeLruCache } from '@exodus/asset-lib'
2
2
  import { ABI, isEthereumLikeAsset, isEthereumLikeToken, normalizeTxId } from '@exodus/ethereum-lib'
3
+ import { safeString } from '@exodus/safe-string'
3
4
  import SolidityContract from '@exodus/solidity-contract'
4
5
  import assert from 'minimalistic-assert'
5
6
 
6
- import * as ErrorWrapper from './error-wrapper.js'
7
+ import { EthLikeError, EVM_ERROR_REASONS, withErrorReason } from './error-wrapper.js'
7
8
  import { getServer, getServerByName } from './exodus-eth-server/index.js'
8
9
  import { fromHexToString } from './number-utils.js'
9
10
 
@@ -18,7 +19,12 @@ export async function isContract(baseAssetName, address) {
18
19
  }
19
20
 
20
21
  export async function isContractAddress({ asset, address }) {
21
- return getServer(asset).isContract(address)
22
+ return withErrorReason({
23
+ promise: getServer(asset).isContract(address),
24
+ errorReasonInfo: EVM_ERROR_REASONS.getCodeFailed,
25
+ hint: safeString`isContractAddress`,
26
+ baseAssetName: asset.baseAsset.name,
27
+ })
22
28
  }
23
29
 
24
30
  export const isContractAddressCached = memoizeLruCache(
@@ -29,7 +35,12 @@ export const isContractAddressCached = memoizeLruCache(
29
35
 
30
36
  export async function isForwarderContract({ asset, address }) {
31
37
  const server = getServer(asset)
32
- const contractCode = await server.getCode(address)
38
+ const contractCode = await withErrorReason({
39
+ promise: server.getCode(address),
40
+ errorReasonInfo: EVM_ERROR_REASONS.getCodeFailed,
41
+ hint: safeString`isForwarderContract`,
42
+ baseAssetName: asset.baseAsset.name,
43
+ })
33
44
  return contractCode === FORWARDER_CONTRACT_CODE
34
45
  }
35
46
 
@@ -41,21 +52,23 @@ export const isForwarderContractCached = memoizeLruCache(
41
52
 
42
53
  export async function getNonce({ asset, address, tag = 'latest' }) {
43
54
  const server = getServer(asset)
44
- const nonce = await ErrorWrapper.withErrorReason(
45
- server.getTransactionCount(address, tag),
46
- ErrorWrapper.reasons.nonceFetchFailed,
47
- 'getNonce'
48
- )
55
+ const nonce = await withErrorReason({
56
+ promise: server.getTransactionCount(address, tag),
57
+ errorReasonInfo: EVM_ERROR_REASONS.nonceFetchFailed,
58
+ hint: safeString`getNonce`,
59
+ baseAssetName: asset.baseAsset.name,
60
+ })
49
61
 
50
62
  return parseInt(nonce, 16)
51
63
  }
52
64
 
53
65
  export async function estimateGas({ asset, ...args }) {
54
- return ErrorWrapper.withErrorReason(
55
- getServer(asset).estimateGas(args),
56
- ErrorWrapper.reasons.fetchGasLimitFailed,
57
- 'estimateGas'
58
- )
66
+ return withErrorReason({
67
+ promise: getServer(asset).estimateGas(args),
68
+ errorReasonInfo: EVM_ERROR_REASONS.fetchGasLimitFailed,
69
+ hint: safeString`estimateGas`,
70
+ baseAssetName: asset.baseAsset.name,
71
+ })
59
72
  }
60
73
 
61
74
  // Only base assets, not tokens
@@ -63,7 +76,12 @@ export async function getBalance({ asset, address }) {
63
76
  if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
64
77
 
65
78
  const server = getServer(asset)
66
- const balances = await server.getBalance(address)
79
+ const balances = await withErrorReason({
80
+ promise: server.getBalance(address),
81
+ errorReasonInfo: EVM_ERROR_REASONS.balanceFetchFailed,
82
+ hint: safeString`getBalance`,
83
+ baseAssetName: asset.baseAsset.name,
84
+ })
67
85
  return balances?.confirmed?.value || '0'
68
86
  }
69
87
 
@@ -71,16 +89,27 @@ export async function getBalance({ asset, address }) {
71
89
  export async function getBalanceProxied({ asset, address, tag = 'latest' }) {
72
90
  if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
73
91
 
74
- const result = await getServer(asset).getBalanceProxied(address)
92
+ const result = await withErrorReason({
93
+ promise: getServer(asset).getBalanceProxied(address),
94
+ errorReasonInfo: EVM_ERROR_REASONS.balanceFetchFailed,
95
+ hint: safeString`getBalanceProxied`,
96
+ baseAssetName: asset.baseAsset.name,
97
+ })
75
98
  return fromHexToString(result)
76
99
  }
77
100
 
78
101
  // Only ETH-like assets with token support
102
+ // How would this return token balances? It does the same as getBalanceProxied... getting ETH balance
79
103
  export async function getTokenBalance({ asset, address }) {
80
104
  if (!isEthereumLikeToken(asset)) throw new Error(`unsupported ETH-like token ${asset.name}`)
81
105
 
82
106
  const server = getServer(asset)
83
- const balances = await server.getBalance(address)
107
+ const balances = await withErrorReason({
108
+ promise: server.getBalance(address),
109
+ errorReasonInfo: EVM_ERROR_REASONS.balanceFetchFailed,
110
+ hint: safeString`getTokenBalance`,
111
+ baseAssetName: asset.baseAsset.name,
112
+ })
84
113
  const contractAddress = asset.contract.address.toLowerCase()
85
114
  return balances?.confirmed?.[contractAddress] || '0'
86
115
  }
@@ -90,7 +119,12 @@ export async function getTokenBalanceFromNode({ asset, address }) {
90
119
 
91
120
  const server = getServer(asset)
92
121
  const contractAddress = asset.contract.address.toLowerCase()
93
- const balances = await server.balanceOf(address, contractAddress)
122
+ const balances = await withErrorReason({
123
+ promise: server.balanceOf(address, contractAddress),
124
+ errorReasonInfo: EVM_ERROR_REASONS.ethCallErc20Failed,
125
+ hint: safeString`getTokenBalanceFromNode`,
126
+ baseAssetName: asset.baseAsset.name,
127
+ })
94
128
  return balances?.confirmed?.[contractAddress] || '0'
95
129
  }
96
130
 
@@ -102,14 +136,24 @@ export function sendRawTransaction(asset) {
102
136
  export async function transactionExists({ asset, txId }) {
103
137
  const server = getServer(asset)
104
138
  txId = normalizeTxId(txId)
105
- const txResult = await server.getTransactionByHash(txId)
139
+ const txResult = await withErrorReason({
140
+ promise: server.getTransactionByHash(txId),
141
+ errorReasonInfo: EVM_ERROR_REASONS.getTransactionByHashFailed,
142
+ hint: safeString`transactionExists`,
143
+ baseAssetName: asset.baseAsset.name,
144
+ })
106
145
  return Boolean(txResult && txResult.hash === txId)
107
146
  }
108
147
 
109
148
  export async function getTransaction({ asset, txId }) {
110
149
  const server = getServer(asset)
111
150
  txId = normalizeTxId(txId)
112
- return server.getTransactionByHash(txId)
151
+ return withErrorReason({
152
+ promise: server.getTransactionByHash(txId),
153
+ errorReasonInfo: EVM_ERROR_REASONS.getTransactionByHashFailed,
154
+ hint: safeString`getTransaction`,
155
+ baseAssetName: asset.baseAsset.name,
156
+ })
113
157
  }
114
158
 
115
159
  export const getIsForwarderContract = memoizeLruCache(
@@ -140,18 +184,12 @@ export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARA
140
184
  try {
141
185
  callResponse = await server.ethCall({ to: address, data: ERC20[method].methodId })
142
186
  } catch (err) {
143
- if (err.message === 'execution reverted') {
144
- throw new ErrorWrapper.EthLikeError({
145
- message: `Can't find parameters for contract with address ${address}. Are you sure it is a valid ERC20 contract?`,
146
- reason: ErrorWrapper.reasons.ethCallErc20Failed,
147
- hint: 'ethCall:executionReverted',
148
- })
149
- }
150
-
151
- throw new ErrorWrapper.EthLikeError({
187
+ throw new EthLikeError({
152
188
  message: err.message,
153
- reason: ErrorWrapper.reasons.ethCallErc20Failed,
154
- hint: 'ethCall',
189
+ errorReasonInfo: EVM_ERROR_REASONS.ethCallErc20Failed,
190
+ hint: safeString`getERC20Params`,
191
+ traceId: err.traceId,
192
+ baseAssetName: asset.baseAsset.name,
155
193
  })
156
194
  }
157
195
 
@@ -181,7 +219,12 @@ export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARA
181
219
  }
182
220
 
183
221
  export async function getEIP7702Delegation({ address, server }) {
184
- const code = await server.getCode(address)
222
+ const code = await withErrorReason({
223
+ promise: server.getCode(address),
224
+ errorReasonInfo: EVM_ERROR_REASONS.getCodeFailed,
225
+ hint: safeString`getEIP7702Delegation`,
226
+ baseAssetName: server.baseAssetName,
227
+ })
185
228
 
186
229
  // No code at all
187
230
  if (!code || code === '0x' || code === '0x0') {
@@ -11,8 +11,9 @@ import { getFallbackGasPriceEstimation } from './utils.js'
11
11
  const { isEmpty } = lodash
12
12
 
13
13
  export default class ApiCoinNodesServer extends EventEmitter {
14
- constructor({ uri }) {
14
+ constructor({ baseAssetName, uri }) {
15
15
  super()
16
+ this.baseAssetName = baseAssetName
16
17
  this.uri = uri
17
18
  this.defaultUri = uri
18
19
  this.id = 0