@exodus/ethereum-api 9.0.0-alpha.3 → 9.0.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.
Files changed (143) hide show
  1. package/CHANGELOG.md +2139 -0
  2. package/LICENSE +7 -0
  3. package/README.md +124 -0
  4. package/package.json +34 -33
  5. package/src/address-has-history.js +6 -2
  6. package/src/allowance/constants.js +4 -5
  7. package/src/allowance/index.js +159 -40
  8. package/src/check-tx/create-assess-transaction.js +23 -0
  9. package/src/check-tx/create-check-tx.js +104 -0
  10. package/src/check-tx/index.js +1 -0
  11. package/src/create-asset-plugin-factory.js +115 -0
  12. package/src/create-asset-utils.js +520 -0
  13. package/src/create-asset.js +273 -97
  14. package/src/create-token-factory.js +47 -17
  15. package/src/custom-fees.js +57 -0
  16. package/src/eip7702-whitelist.js +62 -0
  17. package/src/ens/index.js +12 -17
  18. package/src/error-wrapper.js +383 -0
  19. package/src/eth-like-util.js +172 -33
  20. package/src/exodus-eth-server/api-coin-nodes.js +27 -196
  21. package/src/exodus-eth-server/api.js +23 -14
  22. package/src/exodus-eth-server/clarity-v2.js +255 -0
  23. package/src/exodus-eth-server/clarity.js +67 -265
  24. package/src/exodus-eth-server/errors.js +40 -0
  25. package/src/exodus-eth-server/eth-like-server.js +385 -0
  26. package/src/exodus-eth-server/fetch-json.js +48 -0
  27. package/src/exodus-eth-server/index.js +77 -0
  28. package/src/exodus-eth-server/utils.js +31 -0
  29. package/src/exodus-eth-server/ws-gateway.js +306 -0
  30. package/src/exodus-eth-server/ws.js +1 -2
  31. package/src/fee-data/EthLikeFeeData.js +81 -0
  32. package/src/fee-data/index.js +54 -0
  33. package/src/fee-utils.js +229 -0
  34. package/src/gas-estimation.js +183 -66
  35. package/src/get-balance-for-address.js +1 -1
  36. package/src/get-balances.js +285 -27
  37. package/src/get-fee.js +92 -56
  38. package/src/get-historical-balance.js +58 -0
  39. package/src/hooks/index.js +1 -1
  40. package/src/hooks/monitor.js +182 -43
  41. package/src/hooks/staking-freshness.js +58 -0
  42. package/src/index.js +118 -12
  43. package/src/move-funds.js +153 -0
  44. package/src/multicall3/index.js +212 -0
  45. package/src/multisig-address.js +146 -0
  46. package/src/nft-utils.js +60 -0
  47. package/src/number-utils.js +34 -2
  48. package/src/optimism-gas/index.js +13 -5
  49. package/src/send-validations.js +58 -0
  50. package/src/server-based-fee-monitor.js +12 -19
  51. package/src/simulate-tx/fetch-tx-preview.js +0 -2
  52. package/src/simulate-tx/index.js +2 -2
  53. package/src/simulate-tx/simulate-eth-tx.js +24 -11
  54. package/src/simulation/common.js +34 -0
  55. package/src/simulation/create-simulate-message.js +49 -0
  56. package/src/simulation/create-simulate-transactions.js +108 -0
  57. package/src/simulation/estimate-fee.js +14 -0
  58. package/src/simulation/estimate-simple-transfer.js +15 -0
  59. package/src/simulation/get-message-type.js +18 -0
  60. package/src/simulation/simulate-message-api.js +68 -0
  61. package/src/simulation/simulate-transactions-api.js +266 -0
  62. package/src/simulation/simulate-transactions.js +16 -0
  63. package/src/simulation/transactions.js +52 -0
  64. package/src/simulation/try-estimating-changes-locally.js +26 -0
  65. package/src/staking/api/index.js +43 -0
  66. package/src/staking/cached-read.js +33 -0
  67. package/src/staking/ethereum/api.js +166 -34
  68. package/src/staking/ethereum/deps.js +37 -0
  69. package/src/staking/ethereum/everstake.js +113 -0
  70. package/src/staking/ethereum/index.js +4 -3
  71. package/src/staking/ethereum/service.js +682 -181
  72. package/src/staking/ethereum/staking-utils.js +275 -4
  73. package/src/staking/fantom-staking.js +9 -9
  74. package/src/staking/index.js +4 -4
  75. package/src/staking/matic/addresses.js +2 -0
  76. package/src/staking/matic/api.js +69 -49
  77. package/src/staking/matic/index.js +3 -3
  78. package/src/staking/matic/matic-staking-utils.js +243 -5
  79. package/src/staking/matic/matic-staking.md +53 -0
  80. package/src/staking/staking-provider-client.js +1 -1
  81. package/src/staking/utils/index.js +39 -0
  82. package/src/tx-create.js +666 -0
  83. package/src/tx-log/clarity-monitor.js +276 -103
  84. package/src/tx-log/clarity-truncated-history-monitor.js +130 -0
  85. package/src/tx-log/clarity-truncated-history-monitor.md +219 -0
  86. package/src/tx-log/clarity-utils/absolute.js +96 -0
  87. package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +94 -31
  88. package/src/tx-log/clarity-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
  89. package/src/tx-log/clarity-utils/index.js +9 -3
  90. package/src/tx-log/clarity-utils/is-spam-pending-tx.js +13 -0
  91. package/src/tx-log/clarity-utils/normalize-transactions-response.js +65 -0
  92. package/src/tx-log/ethereum-monitor.js +93 -47
  93. package/src/tx-log/ethereum-no-history-monitor.js +52 -41
  94. package/src/tx-log/get-optimistic-txlog-effects.js +379 -0
  95. package/src/tx-log/index.js +5 -3
  96. package/src/tx-log/monitor-utils/check-pending-transactions.js +14 -5
  97. package/src/tx-log/monitor-utils/exclude-unchanged-token-balances.js +1 -1
  98. package/src/tx-log/monitor-utils/extract-balance-from-clarity-account-info.js +108 -0
  99. package/src/tx-log/monitor-utils/get-balance-updates.js +44 -0
  100. package/src/tx-log/monitor-utils/get-batched-rpc-balances.js +47 -0
  101. package/src/tx-log/monitor-utils/get-current-blacklist-status.js +38 -0
  102. package/src/tx-log/monitor-utils/get-current-eip7702-delegation.js +79 -0
  103. package/src/tx-log/monitor-utils/get-derive-transactions-to-check.js +10 -5
  104. package/src/tx-log/monitor-utils/get-fee-amount.js +1 -1
  105. package/src/tx-log/monitor-utils/get-history-from-server.js +1 -1
  106. package/src/tx-log/monitor-utils/get-log-items-from-server-tx.js +75 -33
  107. package/src/tx-log/monitor-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
  108. package/src/tx-log/monitor-utils/get-raw-transfer-amount.js +29 -0
  109. package/src/tx-log/monitor-utils/get-transfers-by-token-name.js +3 -2
  110. package/src/tx-log/monitor-utils/get-value-of-transfers.js +10 -12
  111. package/src/tx-log/monitor-utils/index.js +12 -7
  112. package/src/tx-log/monitor-utils/is-unverifiable-pending-tx-expired.js +19 -0
  113. package/src/tx-log/monitor-utils/verify-pending-tx-status-rpc.js +90 -0
  114. package/src/tx-log-staking-processor/asset-staking-tx-data.js +82 -24
  115. package/src/tx-log-staking-processor/index.js +28 -14
  116. package/src/tx-log-staking-processor/utils.js +21 -20
  117. package/src/tx-send/broadcast-error-handler.js +63 -0
  118. package/src/tx-send/index.js +1 -2
  119. package/src/tx-send/nonce-utils.js +97 -0
  120. package/src/tx-send/tx-send.js +94 -288
  121. package/src/tx-sign/index.js +17 -0
  122. package/src/tx-type/index.js +280 -0
  123. package/src/watch-tx.js +36 -0
  124. package/src/web3/createSimulateMessage.js +3 -2
  125. package/src/web3/createSimulateTransactions.js +11 -0
  126. package/src/web3/index.js +3 -1
  127. package/src/etherscan/account.js +0 -49
  128. package/src/etherscan/index.js +0 -28
  129. package/src/etherscan/logs.js +0 -16
  130. package/src/etherscan/proxy.js +0 -47
  131. package/src/etherscan/request.js +0 -26
  132. package/src/etherscan/ws.js +0 -88
  133. package/src/exodus-eth-server/server-factory.js +0 -28
  134. package/src/exodus-eth-server/server-map.js +0 -21
  135. package/src/get-fee-async.js +0 -97
  136. package/src/staking/matic/service.js +0 -437
  137. package/src/staking-api.js +0 -5
  138. package/src/tx-log/clarity-utils/get-derive-data-needed-for-tick.js +0 -12
  139. package/src/tx-log-staking-processor/get-asset-tx-amount.js +0 -26
  140. package/src/tx-send/get-fee-info.js +0 -41
  141. package/src/websocket/index.android.js +0 -2
  142. package/src/websocket/index.ios.js +0 -2
  143. package/src/websocket/index.js +0 -21
@@ -1,12 +1,25 @@
1
- import assert from 'minimalistic-assert'
2
- import { normalizeTxId, isEthereumLikeAsset, isEthereumLikeToken, ABI } from '@exodus/ethereum-lib'
3
1
  import { memoizeLruCache } from '@exodus/asset-lib'
2
+ import { ABI, isEthereumLikeAsset, isEthereumLikeToken, normalizeTxId } from '@exodus/ethereum-lib'
3
+ import { safeString } from '@exodus/safe-string'
4
4
  import SolidityContract from '@exodus/solidity-contract'
5
- import { getServer } from './exodus-eth-server/server-map'
6
- import { fromHexToString } from './number-utils'
5
+ import assert from 'minimalistic-assert'
6
+
7
+ import { EthLikeError, EVM_ERROR_REASONS, withErrorReason } from './error-wrapper.js'
8
+ import { getServer } from './exodus-eth-server/index.js'
9
+ import { fromHexToString } from './number-utils.js'
10
+
11
+ export const FORWARDER_CONTRACT_CODE =
12
+ '0x5836818037808036817364b29dc43e817817cf77468c8dda63d98ce08fb25af43d91908282803e602b57fd5bf3'
13
+
14
+ export const EIP7702_DELEGATION_DESIGNATOR = '0xef0100'
7
15
 
8
16
  export async function isContractAddress({ asset, address }) {
9
- return getServer(asset).isContract(address)
17
+ return withErrorReason({
18
+ promise: getServer(asset).isContract(address),
19
+ errorReasonInfo: EVM_ERROR_REASONS.getCodeFailed,
20
+ hint: safeString`isContractAddress`,
21
+ baseAssetName: asset.baseAsset.name,
22
+ })
10
23
  }
11
24
 
12
25
  export const isContractAddressCached = memoizeLruCache(
@@ -16,11 +29,14 @@ export const isContractAddressCached = memoizeLruCache(
16
29
  )
17
30
 
18
31
  export async function isForwarderContract({ asset, address }) {
19
- const contractCode = await getServer(asset).getCode(address)
20
- return (
21
- contractCode ===
22
- '0x5836818037808036817364b29dc43e817817cf77468c8dda63d98ce08fb25af43d91908282803e602b57fd5bf3'
23
- )
32
+ const server = getServer(asset)
33
+ const contractCode = await withErrorReason({
34
+ promise: server.getCode(address),
35
+ errorReasonInfo: EVM_ERROR_REASONS.getCodeFailed,
36
+ hint: safeString`isForwarderContract`,
37
+ baseAssetName: asset.baseAsset.name,
38
+ })
39
+ return contractCode === FORWARDER_CONTRACT_CODE
24
40
  }
25
41
 
26
42
  export const isForwarderContractCached = memoizeLruCache(
@@ -31,62 +47,103 @@ export const isForwarderContractCached = memoizeLruCache(
31
47
 
32
48
  export async function getNonce({ asset, address, tag = 'latest' }) {
33
49
  const server = getServer(asset)
34
- const nonce = await server.getTransactionCount(address, tag)
50
+ const nonce = await withErrorReason({
51
+ promise: server.getTransactionCount(address, tag),
52
+ errorReasonInfo: EVM_ERROR_REASONS.nonceFetchFailed,
53
+ hint: safeString`getNonce`,
54
+ baseAssetName: asset.baseAsset.name,
55
+ })
56
+
35
57
  return parseInt(nonce, 16)
36
58
  }
37
59
 
38
60
  export async function estimateGas({ asset, ...args }) {
39
- return getServer(asset).estimateGas(args)
61
+ return withErrorReason({
62
+ promise: getServer(asset).estimateGas(args),
63
+ errorReasonInfo: EVM_ERROR_REASONS.fetchGasLimitFailed,
64
+ hint: safeString`estimateGas`,
65
+ baseAssetName: asset.baseAsset.name,
66
+ })
40
67
  }
41
68
 
42
69
  // Only base assets, not tokens
43
70
  export async function getBalance({ asset, address }) {
44
71
  if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
72
+
45
73
  const server = getServer(asset)
46
- const balances = await server.getBalance(address)
74
+ const balances = await withErrorReason({
75
+ promise: server.getBalance(address),
76
+ errorReasonInfo: EVM_ERROR_REASONS.balanceFetchFailed,
77
+ hint: safeString`getBalance`,
78
+ baseAssetName: asset.baseAsset.name,
79
+ })
47
80
  return balances?.confirmed?.value || '0'
48
81
  }
49
82
 
50
83
  // Only base assets, not tokens
51
84
  export async function getBalanceProxied({ asset, address, tag = 'latest' }) {
52
85
  if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
53
- const result = await getServer(asset).getBalanceProxied(address)
86
+
87
+ const result = await withErrorReason({
88
+ promise: getServer(asset).getBalanceProxied(address),
89
+ errorReasonInfo: EVM_ERROR_REASONS.balanceFetchFailed,
90
+ hint: safeString`getBalanceProxied`,
91
+ baseAssetName: asset.baseAsset.name,
92
+ })
54
93
  return fromHexToString(result)
55
94
  }
56
95
 
57
96
  // Only ETH-like assets with token support
97
+ // How would this return token balances? It does the same as getBalanceProxied... getting ETH balance
58
98
  export async function getTokenBalance({ asset, address }) {
59
99
  if (!isEthereumLikeToken(asset)) throw new Error(`unsupported ETH-like token ${asset.name}`)
60
- const server = getServer(asset)
61
- const balances = await server.getBalance(address)
100
+
62
101
  const contractAddress = asset.contract.address.toLowerCase()
102
+ const server = getServer(asset)
103
+ const balances = await withErrorReason({
104
+ promise: server.balanceOf(address, contractAddress),
105
+ errorReasonInfo: EVM_ERROR_REASONS.balanceFetchFailed,
106
+ hint: safeString`getTokenBalance`,
107
+ baseAssetName: asset.baseAsset.name,
108
+ })
63
109
  return balances?.confirmed?.[contractAddress] || '0'
64
110
  }
65
111
 
66
112
  export async function getTokenBalanceFromNode({ asset, address }) {
67
113
  if (!isEthereumLikeToken(asset)) throw new Error(`unsupported ETH-like token ${asset.name}`)
114
+
68
115
  const server = getServer(asset)
69
116
  const contractAddress = asset.contract.address.toLowerCase()
70
- const balances = await server.balanceOf(address, contractAddress)
117
+ const balances = await withErrorReason({
118
+ promise: server.balanceOf(address, contractAddress),
119
+ errorReasonInfo: EVM_ERROR_REASONS.ethCallErc20Failed,
120
+ hint: safeString`getTokenBalanceFromNode`,
121
+ baseAssetName: asset.baseAsset.name,
122
+ })
71
123
  return balances?.confirmed?.[contractAddress] || '0'
72
124
  }
73
125
 
74
- // Returns function for supplied asset
75
- export function sendRawTransaction(asset) {
76
- return getServer(asset).sendRawTransaction
77
- }
78
-
79
126
  export async function transactionExists({ asset, txId }) {
80
127
  const server = getServer(asset)
81
128
  txId = normalizeTxId(txId)
82
- const txResult = await server.getTransactionByHash(txId)
83
- return txResult && txResult.hash === txId
129
+ const txResult = await withErrorReason({
130
+ promise: server.getTransactionByHash(txId),
131
+ errorReasonInfo: EVM_ERROR_REASONS.getTransactionByHashFailed,
132
+ hint: safeString`transactionExists`,
133
+ baseAssetName: asset.baseAsset.name,
134
+ })
135
+ return Boolean(txResult && txResult.hash === txId)
84
136
  }
85
137
 
86
138
  export async function getTransaction({ asset, txId }) {
87
139
  const server = getServer(asset)
88
140
  txId = normalizeTxId(txId)
89
- return server.getTransactionByHash(txId)
141
+ return withErrorReason({
142
+ promise: server.getTransactionByHash(txId),
143
+ errorReasonInfo: EVM_ERROR_REASONS.getTransactionByHashFailed,
144
+ hint: safeString`getTransaction`,
145
+ baseAssetName: asset.baseAsset.name,
146
+ })
90
147
  }
91
148
 
92
149
  export const getIsForwarderContract = memoizeLruCache(
@@ -97,8 +154,9 @@ export const getIsForwarderContract = memoizeLruCache(
97
154
 
98
155
  const ERC20 = new SolidityContract(ABI.erc20)
99
156
  const ERC20BytesParams = new SolidityContract(ABI.erc20BytesParams)
157
+ const ERC721 = new SolidityContract(ABI.erc721)
100
158
  const DEFAULT_PARAM_NAMES = ['decimals', 'name', 'symbol']
101
- const erc20ParamsCache = {}
159
+ const erc20ParamsCache = Object.create(null)
102
160
 
103
161
  export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARAM_NAMES } = {}) => {
104
162
  assert(asset, 'getERC20Params(): asset required')
@@ -117,13 +175,13 @@ export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARA
117
175
  try {
118
176
  callResponse = await server.ethCall({ to: address, data: ERC20[method].methodId })
119
177
  } catch (err) {
120
- if (err.message === 'execution reverted') {
121
- throw new Error(
122
- `Can't find parameters for contract with address ${address}. Are you sure it is a valid ERC20 contract?`
123
- )
124
- }
125
-
126
- throw new Error(err.message)
178
+ throw new EthLikeError({
179
+ message: err.message,
180
+ errorReasonInfo: EVM_ERROR_REASONS.ethCallErc20Failed,
181
+ hint: safeString`getERC20Params`,
182
+ traceId: err.traceId,
183
+ baseAssetName: asset.baseAsset.name,
184
+ })
127
185
  }
128
186
 
129
187
  if (method === 'decimals') return parseInt(callResponse)
@@ -150,3 +208,84 @@ export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARA
150
208
 
151
209
  return response
152
210
  }
211
+
212
+ // ERC-165 introspection used to drive Ledger's clear-signing for contract calls:
213
+ // it tells the device whether the target is an NFT (ERC-721/1155) or a fungible
214
+ // token. See `signHardwareFactory` in ethereum-lib for how this feeds the device's
215
+ // `isNft` hint. Display-only: never affects the signed transaction.
216
+ const buildSupportsInterfaceData = (interfaceId) => {
217
+ const erc165InterfaceId = `0x${interfaceId}`
218
+ return `0x${ERC721.supportsInterface.build(erc165InterfaceId).toString('hex')}`
219
+ }
220
+
221
+ const decodeBool = (result) => {
222
+ return ERC721.decodeOutput({ method: 'supportsInterface', data: result })[0]
223
+ }
224
+
225
+ // true = supported, false = not supported / reverted, undefined = transport failure
226
+ const probeSupportsInterface = async ({ server, address, interfaceId }) => {
227
+ try {
228
+ const result = await server.ethCall({
229
+ to: address,
230
+ data: buildSupportsInterfaceData(interfaceId),
231
+ })
232
+ return decodeBool(result)
233
+ } catch (err) {
234
+ // Empirically, a contract without supportsInterface comes through Clarity as
235
+ // "Bad rpc response: execution reverted". Unknown errors stay undefined so
236
+ // we don't force a wrong hint.
237
+ const EXECUTION_REVERT_MESSAGE = 'execution reverted'
238
+ const errorMessage = err?.message || String(err || '')
239
+ return errorMessage.toLowerCase().includes(EXECUTION_REVERT_MESSAGE) ? false : undefined
240
+ }
241
+ }
242
+
243
+ // Resolves whether `address` is an NFT contract (ERC-721 or ERC-1155) via ERC-165.
244
+ // Returns true (NFT), false (not an NFT), or undefined (couldn't determine).
245
+ export async function getIsNftContract({ server, address }) {
246
+ assert(server, 'getIsNftContract(): server required')
247
+ assert(address, 'getIsNftContract(): address required')
248
+
249
+ const ERC721_INTERFACE_ID = '80ac58cd'
250
+ const ERC1155_INTERFACE_ID = 'd9b67a26'
251
+
252
+ const [isErc721, isErc1155] = await Promise.all([
253
+ probeSupportsInterface({ server, address, interfaceId: ERC721_INTERFACE_ID }),
254
+ probeSupportsInterface({ server, address, interfaceId: ERC1155_INTERFACE_ID }),
255
+ ])
256
+
257
+ if (isErc721 === true || isErc1155 === true) {
258
+ return true
259
+ }
260
+
261
+ if (isErc721 === false && isErc1155 === false) return false
262
+ }
263
+
264
+ export async function getEIP7702Delegation({ address, server }) {
265
+ const code = await withErrorReason({
266
+ promise: server.getCode(address),
267
+ errorReasonInfo: EVM_ERROR_REASONS.getCodeFailed,
268
+ hint: safeString`getEIP7702Delegation`,
269
+ baseAssetName: server.baseAssetName,
270
+ })
271
+
272
+ // No code at all
273
+ if (!code || code === '0x' || code === '0x0') {
274
+ return { isDelegated: false, delegatedAddress: null }
275
+ }
276
+
277
+ // EIP-7702 delegation code must be exactly 23 bytes (46 hex chars + "0x" prefix = 48 chars)
278
+ // Format: 0xef0100 (3 bytes) + address (20 bytes)
279
+ if (code.length !== 48) {
280
+ return { isDelegated: false, delegatedAddress: null }
281
+ }
282
+
283
+ // Check for EIP-7702 delegation designator prefix
284
+ if (!code.toLowerCase().startsWith(EIP7702_DELEGATION_DESIGNATOR)) {
285
+ return { isDelegated: false, delegatedAddress: null }
286
+ }
287
+
288
+ // Extract the 20-byte delegated address
289
+ const delegatedAddress = '0x' + code.slice(8, 48)
290
+ return { isDelegated: true, delegatedAddress }
291
+ }
@@ -1,16 +1,18 @@
1
- import { bufferToHex } from '@exodus/ethereumjs-util'
2
- import SolidityContract from '@exodus/solidity-contract'
3
- import EventEmitter from 'events'
4
- import { isEmpty } from 'lodash'
5
- import { fetch } from '@exodus/fetch'
6
- import { fromHexToString } from '../number-utils'
1
+ import { safeString } from '@exodus/safe-string'
2
+ import lodash from 'lodash'
7
3
 
8
- export default class ApiCoinNodesServer extends EventEmitter {
9
- constructor({ uri }) {
4
+ import { errorMessageToSafeHint } from './errors.js'
5
+ import { EthLikeServer } from './eth-like-server.js'
6
+ import { fetchJsonRetry } from './fetch-json.js'
7
+
8
+ const { isEmpty } = lodash
9
+
10
+ export default class ApiCoinNodesServer extends EthLikeServer {
11
+ constructor({ baseAssetName, uri }) {
10
12
  super()
13
+ this.baseAssetName = baseAssetName
11
14
  this.uri = uri
12
15
  this.defaultUri = uri
13
- this.id = 0
14
16
  }
15
17
 
16
18
  setURI(uri) {
@@ -24,24 +26,18 @@ export default class ApiCoinNodesServer extends EventEmitter {
24
26
  body: JSON.stringify(body),
25
27
  }
26
28
 
27
- const response = await fetch(this.uri, options)
28
- return response.json()
29
+ return fetchJsonRetry(this.uri, options)
29
30
  }
30
31
 
31
- async sendBatchRequest(batch) {
32
+ async sendBatchRequest(batch, { tolerant } = {}) {
32
33
  if (isEmpty(batch)) return batch
33
34
  const responses = await this.request(batch)
34
- const isValid = responses.every((response) => {
35
- return !isNaN(response?.id) && response?.result
35
+ return this.parseBatchResponse({
36
+ batch,
37
+ responses,
38
+ errorMessage: 'Bad rpc batch response',
39
+ tolerant,
36
40
  })
37
- if (responses.length !== batch.length || !isValid) {
38
- throw new Error('Bad rpc batch response')
39
- }
40
-
41
- const keyed = responses.reduce((acc, response) => {
42
- return { ...acc, [`${response.id}`]: response.result }
43
- }, Object.create(null))
44
- return batch.map((request) => keyed[`${request.id}`])
45
41
  }
46
42
 
47
43
  async sendRequest(request) {
@@ -50,185 +46,20 @@ export default class ApiCoinNodesServer extends EventEmitter {
50
46
  const result = response?.result
51
47
  const error = response?.error
52
48
  if (error || result === undefined) {
53
- const message = error?.message || error?.code || 'no result'
54
- throw new Error(`Bad rpc response: ${message}`)
55
- }
49
+ const message = error?.message || error?.code || safeString`no result`
56
50
 
57
- return result
58
- }
51
+ const revisedError = new Error(`Bad rpc response: ${message}`)
52
+ revisedError.hint = safeString`Bad rpc response: ${errorMessageToSafeHint(message)}`
59
53
 
60
- async isContract(address) {
61
- const code = await this.getCode(address)
62
- return code.length > 2
63
- }
64
-
65
- buildRequest({ method, params = [] }) {
66
- return { jsonrpc: '2.0', id: this.id++, method, params }
67
- }
54
+ const traceId = response?.__traceId
55
+ if (traceId) {
56
+ revisedError.traceId = traceId
57
+ }
68
58
 
69
- balanceOfRequest(address, tokenAddress, tag = 'latest') {
70
- const contract = SolidityContract.simpleErc20(tokenAddress)
71
- const callData = contract.balanceOf.build(address)
72
- const data = {
73
- data: bufferToHex(callData),
74
- to: tokenAddress,
59
+ throw revisedError
75
60
  }
76
- return this.ethCallRequest(data, tag)
77
- }
78
-
79
- getBalanceRequest(address, tag = 'latest') {
80
- return this.buildRequest({ method: 'eth_getBalance', params: [address, tag] })
81
- }
82
-
83
- gasPriceRequest() {
84
- return this.buildRequest({ method: 'eth_gasPrice' })
85
- }
86
-
87
- estimateGasRequest(data) {
88
- return this.buildRequest({ method: 'eth_estimateGas', params: [data] })
89
- }
90
-
91
- sendRawTransactionRequest(data) {
92
- const hex = data.startsWith('0x') ? data : '0x' + data
93
- return this.buildRequest({ method: 'eth_sendRawTransaction', params: [hex] })
94
- }
95
-
96
- getCodeRequest(address, tag = 'latest') {
97
- return this.buildRequest({ method: 'eth_getCode', params: [address, tag] })
98
- }
99
-
100
- getStorageAtRequest(address, position, tag = 'latest') {
101
- return this.buildRequest({ method: 'eth_getStorageAt', params: [address, position, tag] })
102
- }
103
-
104
- getTransactionCountRequest(address, tag = 'latest') {
105
- return this.buildRequest({ method: 'eth_getTransactionCount', params: [address, tag] })
106
- }
107
-
108
- getTransactionByHashRequest(hash) {
109
- return this.buildRequest({ method: 'eth_getTransactionByHash', params: [hash] })
110
- }
111
-
112
- getTransactionReceiptRequest(txhash) {
113
- return this.buildRequest({ method: 'eth_getTransactionReceipt', params: [txhash] })
114
- }
115
-
116
- ethCallRequest(data, tag = 'latest') {
117
- return this.buildRequest({ method: 'eth_call', params: [data, tag] })
118
- }
119
-
120
- blockNumberRequest() {
121
- return this.buildRequest({ method: 'eth_blockNumber' })
122
- }
123
-
124
- getBlockByNumberRequest(numberHex, isFullTxs = false) {
125
- return this.buildRequest({ method: 'eth_getBlockByNumber', params: [numberHex, isFullTxs] })
126
- }
127
-
128
- simulateRawTransactionRequest(rawTx, applyPending = true) {
129
- const replaced = rawTx.replace('0x', '')
130
- return this.buildRequest({
131
- method: 'debug_simulateRawTransaction',
132
- params: [replaced, applyPending],
133
- })
134
- }
135
61
 
136
- async balanceOf(address, tokenAddress, tag = 'latest') {
137
- const request = this.balanceOfRequest(address, tokenAddress, tag)
138
- const result = await this.sendRequest(request)
139
- const balance = fromHexToString(result)
140
- return {
141
- confirmed: {
142
- [tokenAddress]: balance,
143
- },
144
- }
145
- }
146
-
147
- async getBalance(...params) {
148
- const request = this.getBalanceRequest(...params)
149
- return this.sendRequest(request)
150
- }
151
-
152
- async getBalanceProxied(...params) {
153
- return this.getBalance(...params)
154
- }
155
-
156
- async gasPrice(...params) {
157
- const request = this.gasPriceRequest(...params)
158
- return this.sendRequest(request)
159
- }
160
-
161
- // for fee monitor
162
- getGasPrice = this.gasPrice
163
-
164
- async getLatestBlock() {
165
- const request = this.buildRequest({
166
- method: 'eth_getBlockByNumber',
167
- params: ['latest', false],
168
- })
169
- return this.sendRequest(request)
170
- }
171
-
172
- async getBaseFeePerGas() {
173
- const response = await this.getLatestBlock()
174
- if (response.baseFeePerGas) {
175
- return fromHexToString(response.baseFeePerGas)
176
- }
177
- }
178
-
179
- async estimateGas(...params) {
180
- const request = this.estimateGasRequest(...params)
181
- return this.sendRequest(request)
182
- }
183
-
184
- async sendRawTransaction(...params) {
185
- const request = this.sendRawTransactionRequest(...params)
186
- return this.sendRequest(request)
187
- }
188
-
189
- async getCode(...params) {
190
- const request = this.getCodeRequest(...params)
191
- return this.sendRequest(request)
192
- }
193
-
194
- async getStorageAt(...params) {
195
- const request = this.getStorageAtRequest(...params)
196
- return this.sendRequest(request)
197
- }
198
-
199
- async getTransactionCount(...params) {
200
- const request = this.getTransactionCountRequest(...params)
201
- return this.sendRequest(request)
202
- }
203
-
204
- async getTransactionByHash(...params) {
205
- const request = this.getTransactionByHashRequest(...params)
206
- return this.sendRequest(request)
207
- }
208
-
209
- async getTransactionReceipt(...params) {
210
- const request = this.getTransactionReceiptRequest(...params)
211
- return this.sendRequest(request)
212
- }
213
-
214
- async ethCall(...params) {
215
- const request = this.ethCallRequest(...params)
216
- return this.sendRequest(request)
217
- }
218
-
219
- async blockNumber(...params) {
220
- const request = this.blockNumberRequest(...params)
221
- return this.sendRequest(request)
222
- }
223
-
224
- async getBlockByNumber(...params) {
225
- const request = this.getBlockByNumberRequest(...params)
226
- return this.sendRequest(request)
227
- }
228
-
229
- async simulateRawTransaction(...params) {
230
- const request = this.simulateRawTransactionRequest(...params)
231
- return this.sendRequest(request)
62
+ return result
232
63
  }
233
64
 
234
65
  stop() {
@@ -1,11 +1,13 @@
1
- import ms from 'ms'
2
- import createWebSocket from './ws'
1
+ import { randomUUID } from '@exodus/crypto/randomUUID'
2
+ import { bufferToHex } from '@exodus/ethereumjs/util'
3
3
  import fetchival from '@exodus/fetch/experimental/fetchival'
4
4
  import { retry } from '@exodus/simple-retry'
5
5
  import SolidityContract from '@exodus/solidity-contract'
6
- import { bufferToHex } from '@exodus/ethereumjs-util'
7
- import { randomUUID } from '@exodus/crypto/randomUUID'
8
- import { fromHexToString } from '../number-utils'
6
+ import ms from 'ms'
7
+
8
+ import { fromHexToString } from '../number-utils.js'
9
+ import { getFallbackGasPriceEstimation } from './utils.js'
10
+ import createWebSocket from './ws.js'
9
11
 
10
12
  const RETRY_DELAYS = ['10s']
11
13
 
@@ -29,7 +31,7 @@ export function create(defaultURL, ensAssetName) {
29
31
  )
30
32
  } catch (err) {
31
33
  let nerr = err
32
- if (err.response && err.response.status === 500) {
34
+ if (err.response && (err.response.status === 500 || err.response.status === 400)) {
33
35
  try {
34
36
  const data = await err.response.json()
35
37
  const msg = data.error.replace(/^RPC error \(code: -\d+\): /, '')
@@ -63,9 +65,7 @@ export function create(defaultURL, ensAssetName) {
63
65
  },
64
66
 
65
67
  stop() {
66
- if (ws.isCreated()) {
67
- ws.close()
68
- }
68
+ ws.close()
69
69
  },
70
70
 
71
71
  ws,
@@ -121,6 +121,10 @@ export function create(defaultURL, ensAssetName) {
121
121
  return requestWithRetry('proxy', { method: 'eth_gasPrice' })
122
122
  },
123
123
 
124
+ async getGasPriceEstimation() {
125
+ return getFallbackGasPriceEstimation({ server: this })
126
+ },
127
+
124
128
  // for fee monitor
125
129
  async getGasPrice() {
126
130
  return requestWithRetry('proxy', { method: 'eth_gasPrice' })
@@ -163,8 +167,13 @@ export function create(defaultURL, ensAssetName) {
163
167
  },
164
168
 
165
169
  async sendRawTransaction(data) {
166
- const hex = data.startsWith('0x') ? data : '0x' + data
167
- return requestWithRetry('proxy', { method: 'eth_sendRawTransaction', hex })
170
+ const _data = data instanceof Uint8Array ? Buffer.from(data).toString('hex') : data
171
+ const hex = _data.startsWith('0x') ? _data : '0x' + _data
172
+ return requestWithRetry(
173
+ 'proxy',
174
+ { method: 'eth_sendRawTransaction', params: [hex] },
175
+ { version: 'v2', method: 'post' }
176
+ )
168
177
  },
169
178
 
170
179
  async estimateGas(data, tag = 'latest') {
@@ -254,11 +263,11 @@ export function create(defaultURL, ensAssetName) {
254
263
  },
255
264
 
256
265
  async proxyToCoinNode(requestData) {
257
- // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
266
+ // eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
258
267
  if (!requestData.jsonrpc) requestData.jsonrpc = '2.0'
259
- // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
268
+ // eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
260
269
  if (!requestData.id) requestData.id = randomUUID()
261
- // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
270
+ // eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
262
271
  if (!requestData.params) requestData.params = []
263
272
 
264
273
  return request('proxy', requestData, { version: 'v2', method: 'post' })