@exodus/ethereum-api 9.0.0-alpha.3 → 9.0.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.
Files changed (143) hide show
  1. package/CHANGELOG.md +2121 -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 +272 -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 +273 -103
  84. package/src/tx-log/clarity-truncated-history-monitor.js +109 -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
@@ -0,0 +1,40 @@
1
+ import { safeString } from '@exodus/safe-string'
2
+
3
+ // Transplated from: https://github.com/ethereum/go-ethereum/blob/master/core/txpool/errors.go.
4
+ const TxPoolErrors = {
5
+ ALREADY_KNOWN: safeString`already known`,
6
+ INVALID_SENDER: safeString`invalid sender`,
7
+ UNDERPRICED: safeString`transaction underpriced`,
8
+ REPLACE_UNDERPRICED: safeString`replacement transaction underpriced`,
9
+ TX_GAS_PRICE_TOO_LOW: safeString`transaction gas price below minimum`,
10
+ ACCOUNT_LIMIT_EXCEEDED: safeString`account limit exceeded`,
11
+ GAS_LIMIT: safeString`exceeds block gas limit`,
12
+ NEGATIVE_VALUE: safeString`negative value`,
13
+ OVERSIZED_DATA: safeString`oversized data`,
14
+ TX_BLOB_LIMIT_EXCEEDED: safeString`transaction blob limit exceeded`,
15
+ ALREADY_RESERVED: safeString`address already reserved`,
16
+ INFLIGHT_TX_LIMIT_REACHED: safeString`in-flight transaction limit reached for delegated accounts`,
17
+ }
18
+
19
+ const DYNAMIC_RPC_ERRORS = {
20
+ TRANSACTION_TYPE_NOT_SUPPORTED: safeString`transaction type not supported`,
21
+ ONLY_REPLAY_PROTECTED_TRANSACTIONS: safeString`only replay-protected (EIP-155) transactions allowed over RPC`,
22
+ TRANSACTION_GAS_PRICE_BELOW_MINIMUM: safeString`transaction gas price below minimum`,
23
+ }
24
+
25
+ const SERVO_ERRORS = {
26
+ EMPTY_BUNDLE: safeString`numTxs=0: invalid num txs`,
27
+ }
28
+
29
+ export const KNOWN_RPC_ERRORS = { ...TxPoolErrors, ...DYNAMIC_RPC_ERRORS, ...SERVO_ERRORS }
30
+
31
+ export const tryMappingToRpcErrors = (errorMessage) => {
32
+ if (typeof errorMessage !== 'string') return
33
+
34
+ return Object.values(KNOWN_RPC_ERRORS)
35
+ .sort((a, b) => b.length - a.length)
36
+ .find((knownRpcError) => errorMessage.includes(knownRpcError))
37
+ }
38
+
39
+ export const errorMessageToSafeHint = (messageOrCode) =>
40
+ tryMappingToRpcErrors(messageOrCode) || messageOrCode
@@ -0,0 +1,385 @@
1
+ import { bufferToHex } from '@exodus/ethereumjs/util'
2
+ import SolidityContract from '@exodus/solidity-contract'
3
+ import EventEmitter from 'events/events.js'
4
+ import assert from 'minimalistic-assert'
5
+
6
+ import { fromHexToString } from '../number-utils.js'
7
+ import { getFallbackGasPriceEstimation } from './utils.js'
8
+
9
+ export class EthLikeServerBase extends EventEmitter {
10
+ id = 0
11
+
12
+ buildRequest({ method, params = [] }) {
13
+ return { jsonrpc: '2.0', id: this.id++, method, params }
14
+ }
15
+
16
+ balanceOfRequest(address, tokenAddress, tag = 'latest') {
17
+ const contract = SolidityContract.simpleErc20(tokenAddress)
18
+ const callData = contract.balanceOf.build(address)
19
+ const data = {
20
+ data: bufferToHex(callData),
21
+ to: tokenAddress,
22
+ }
23
+ return this.ethCallRequest(data, tag)
24
+ }
25
+
26
+ getBalanceRequest(address, tag = 'latest') {
27
+ return this.buildRequest({ method: 'eth_getBalance', params: [address, tag] })
28
+ }
29
+
30
+ gasPriceRequest() {
31
+ return this.buildRequest({ method: 'eth_gasPrice' })
32
+ }
33
+
34
+ estimateGasRequest(data, tag = 'latest') {
35
+ return this.buildRequest({ method: 'eth_estimateGas', params: [data, tag] })
36
+ }
37
+
38
+ sendRawTransactionRequest(data) {
39
+ const _data = data instanceof Uint8Array ? Buffer.from(data).toString('hex') : data
40
+ const hex = _data.startsWith('0x') ? _data : '0x' + _data
41
+ return this.buildRequest({ method: 'eth_sendRawTransaction', params: [hex] })
42
+ }
43
+
44
+ // @deprecated
45
+ coinbaseRequest() {
46
+ return this.buildRequest({ method: 'eth_coinbase' })
47
+ }
48
+
49
+ getCodeRequest(address, tag = 'latest') {
50
+ return this.buildRequest({ method: 'eth_getCode', params: [address, tag] })
51
+ }
52
+
53
+ getStorageAtRequest(address, position, tag = 'latest') {
54
+ return this.buildRequest({ method: 'eth_getStorageAt', params: [address, position, tag] })
55
+ }
56
+
57
+ getTransactionCountRequest(address, tag = 'latest') {
58
+ return this.buildRequest({ method: 'eth_getTransactionCount', params: [address, tag] })
59
+ }
60
+
61
+ getTransactionByHashRequest(hash) {
62
+ return this.buildRequest({ method: 'eth_getTransactionByHash', params: [hash] })
63
+ }
64
+
65
+ getTransactionReceiptRequest(txhash) {
66
+ return this.buildRequest({ method: 'eth_getTransactionReceipt', params: [txhash] })
67
+ }
68
+
69
+ ethCallRequest(data, tag = 'latest') {
70
+ return this.buildRequest({ method: 'eth_call', params: [data, tag] })
71
+ }
72
+
73
+ blockNumberRequest() {
74
+ return this.buildRequest({ method: 'eth_blockNumber' })
75
+ }
76
+
77
+ getLogsRequest(object) {
78
+ return this.buildRequest({ method: 'eth_getLogs', params: [object] })
79
+ }
80
+
81
+ getBlockByNumberRequest(numberHex, isFullTxs = false) {
82
+ return this.buildRequest({ method: 'eth_getBlockByNumber', params: [numberHex, isFullTxs] })
83
+ }
84
+
85
+ getBlockByHashRequest(blockHash, isFullTxs = false) {
86
+ return this.buildRequest({ method: 'eth_getBlockByHash', params: [blockHash, isFullTxs] })
87
+ }
88
+
89
+ getBlockTransactionCountByHashRequest(blockHash) {
90
+ return this.buildRequest({ method: 'eth_getBlockTransactionCountByHash', params: [blockHash] })
91
+ }
92
+
93
+ getBlockTransactionCountByNumberRequest(quantityOrTag) {
94
+ return this.buildRequest({
95
+ method: 'eth_getBlockTransactionCountByNumber',
96
+ params: [quantityOrTag],
97
+ })
98
+ }
99
+
100
+ // @deprectated
101
+ getCompilersRequest() {
102
+ return this.buildRequest({ method: 'eth_getCompilers' })
103
+ }
104
+
105
+ // TODO: Expose on Clarity
106
+ // maxPriorityFeePerGasRequest() {
107
+ // return this.buildRequest({ method: 'eth_maxPriorityFeePerGas' })
108
+ // }
109
+
110
+ // TODO: Expose on Clarity
111
+ // feeHistoryRequest(blockCount, newestBlock, rewardPercentiles) {
112
+ // return this.buildRequest({
113
+ // method: 'eth_feeHistory',
114
+ // params: [blockCount, newestBlock, rewardPercentiles],
115
+ // })
116
+ // }
117
+
118
+ simulateV1Request(...params) {
119
+ return this.buildRequest({
120
+ method: 'eth_simulateV1',
121
+ params,
122
+ })
123
+ }
124
+
125
+ getNetVersionRequest() {
126
+ return this.buildRequest({ method: 'net_version' })
127
+ }
128
+
129
+ // @deprecated
130
+ simulateRawTransactionRequest(rawTx, applyPending = true) {
131
+ const replaced = rawTx.replace('0x', '')
132
+ return this.buildRequest({
133
+ method: 'debug_simulateRawTransaction',
134
+ params: [replaced, applyPending],
135
+ })
136
+ }
137
+
138
+ contractStaticCallFromRequest(contract, functionName, from, ...args) {
139
+ assert(contract instanceof SolidityContract, 'expected SolidityContract contract')
140
+ assert(typeof functionName === 'string', 'expected string functionName')
141
+
142
+ const requestProps = {
143
+ to: contract.address,
144
+ data: bufferToHex(contract[functionName].build(...args)),
145
+ }
146
+
147
+ if (from) requestProps.from = from
148
+
149
+ return this.ethCallRequest(requestProps)
150
+ }
151
+
152
+ sendBundleRequest({ txs }) {
153
+ assert(Array.isArray(txs), 'expected array txs')
154
+ return this.buildRequest({ method: 'eth_sendBundle', params: [{ txs }] })
155
+ }
156
+ }
157
+
158
+ const __MISSING_FUNCTION_IMPLEMENTATION__ = () => assert(false)
159
+
160
+ export class EthLikeServer extends EthLikeServerBase {
161
+ /**
162
+ * Validates a JSON-RPC batch response, returning each `result` ordered to
163
+ * match `batch` (correlated by `id`, since responses can arrive out of order).
164
+ *
165
+ * @param {Array<{ id: number|string }>} args.batch - Requests sent, in the order results are expected.
166
+ * @param {unknown} args.responses - Raw transport payload; expected `[{ id, result }]` but treated as untrusted (gateways may return `{ message }` on error).
167
+ * @param {string} args.errorMessage - Thrown-error prefix per transport (e.g. `'Bad rpc batch response'`, `'Bad Response'`).
168
+ * @param {boolean} [args.tolerant] - When true, a request without a valid entry (JSON-RPC error entry, non-numeric `id`, duplicated or out-of-batch `id`, missing from a short response) maps to `undefined` instead of failing the whole batch.
169
+ * @returns {unknown[]} Each request's `result`, ordered to match `batch`. Entries with a duplicated or out-of-batch `id` are discarded in both modes, so an affected request maps to `undefined` rather than another request's result.
170
+ * @throws {Error} When `responses` isn't an array; in strict mode also when length mismatches `batch` or any entry lacks a numeric `id` / defined `result`.
171
+ */
172
+ parseBatchResponse({ batch, responses, errorMessage, tolerant = false }) {
173
+ // Gateways can respond with a JSON object (e.g. `{ message }`) instead of a batch array.
174
+ if (!Array.isArray(responses)) {
175
+ // Untrusted server text: keep only a short string preview, developer-facing (no `hint`).
176
+ const message = [responses?.message, responses?.error?.message].find(
177
+ (value) => typeof value === 'string'
178
+ )
179
+ const preview = message ? `: ${message.slice(0, 100)}` : ''
180
+ throw new Error(`${errorMessage}${preview}`)
181
+ }
182
+
183
+ const isValidEntry = (response) => !isNaN(response?.id) && response?.result !== undefined
184
+
185
+ if (!tolerant && (responses.length !== batch.length || !responses.every(isValidEntry))) {
186
+ throw new Error(errorMessage)
187
+ }
188
+
189
+ // Safe against prototype keys: only entries with a validated numeric `id` are kept.
190
+ const keyed = Object.fromEntries(
191
+ responses
192
+ .filter(isValidEntry)
193
+ .filter(({ id }, _i, all) => {
194
+ const key = `${id}`
195
+ const inBatch = batch.some((request) => `${request.id}` === key)
196
+ const unique = all.filter(({ id: other }) => `${other}` === key).length === 1
197
+ return inBatch && unique
198
+ })
199
+ .map((response) => [`${response.id}`, response.result])
200
+ )
201
+ return batch.map((request) => keyed[`${request.id}`])
202
+ }
203
+
204
+ async getCode(...params) {
205
+ const request = this.getCodeRequest(...params)
206
+ return this.sendRequest(request)
207
+ }
208
+
209
+ async estimateGas(...params) {
210
+ const request = this.estimateGasRequest(...params)
211
+ return this.sendRequest(request)
212
+ }
213
+
214
+ async sendRawTransaction(...params) {
215
+ const request = this.sendRawTransactionRequest(...params)
216
+ return this.sendRequest(request)
217
+ }
218
+
219
+ async getStorageAt(...params) {
220
+ const request = this.getStorageAtRequest(...params)
221
+ return this.sendRequest(request)
222
+ }
223
+
224
+ async getTransactionCount(...params) {
225
+ const request = this.getTransactionCountRequest(...params)
226
+ return this.sendRequest(request)
227
+ }
228
+
229
+ async getTransactionByHash(...params) {
230
+ const request = this.getTransactionByHashRequest(...params)
231
+ return this.sendRequest(request)
232
+ }
233
+
234
+ async getTransactionReceipt(...params) {
235
+ const request = this.getTransactionReceiptRequest(...params)
236
+ return this.sendRequest(request)
237
+ }
238
+
239
+ async ethCall(...params) {
240
+ const request = this.ethCallRequest(...params)
241
+ return this.sendRequest(request)
242
+ }
243
+
244
+ async blockNumber(...params) {
245
+ const request = this.blockNumberRequest(...params)
246
+ return this.sendRequest(request)
247
+ }
248
+
249
+ async getBlockByNumber(...params) {
250
+ const request = this.getBlockByNumberRequest(...params)
251
+ return this.sendRequest(request)
252
+ }
253
+
254
+ async isContract(address) {
255
+ const code = await this.getCode(address)
256
+ return code.length > 2
257
+ }
258
+
259
+ // @deprecated
260
+ async simulateRawTransaction(...params) {
261
+ const request = this.simulateRawTransactionRequest(...params)
262
+ return this.sendRequest(request)
263
+ }
264
+
265
+ async getBaseFeePerGas() {
266
+ const response = await this.getLatestBlock()
267
+ if (response.baseFeePerGas) {
268
+ return fromHexToString(response.baseFeePerGas)
269
+ }
270
+ }
271
+
272
+ async getLatestBlock() {
273
+ return this.getBlockByNumber('latest')
274
+ }
275
+
276
+ async gasPrice(...params) {
277
+ const request = this.gasPriceRequest(...params)
278
+ return this.sendRequest(request)
279
+ }
280
+
281
+ // for fee monitor
282
+ async getGasPrice() {
283
+ return this.gasPrice()
284
+ }
285
+
286
+ async getBalance(...params) {
287
+ const request = this.getBalanceRequest(...params)
288
+ return this.sendRequest(request)
289
+ }
290
+
291
+ async getBalanceProxied(...params) {
292
+ return this.getBalance(...params)
293
+ }
294
+
295
+ async getBlockByHash(...params) {
296
+ const request = this.getBlockByHashRequest(...params)
297
+ return this.sendRequest(request)
298
+ }
299
+
300
+ async getBlockTransactionCountByNumber(...params) {
301
+ const request = this.getBlockTransactionCountByNumberRequest(...params)
302
+ return this.sendRequest(request)
303
+ }
304
+
305
+ async getBlockTransactionCountByHash(...params) {
306
+ const request = this.getBlockTransactionCountByHashRequest(...params)
307
+ return this.sendRequest(request)
308
+ }
309
+
310
+ async simulateV1(...params) {
311
+ const request = this.simulateV1Request(...params)
312
+ return this.sendRequest(request)
313
+ }
314
+
315
+ // @deprecated
316
+ async getCoinbase() {
317
+ const request = this.coinbaseRequest()
318
+ return this.sendRequest(request)
319
+ }
320
+
321
+ // @deprecated
322
+ async getCompilers() {
323
+ const request = this.getCompilersRequest()
324
+ return this.sendRequest(request)
325
+ }
326
+
327
+ // TODO: Expose on Clarity
328
+ // async getMaxPriorityFeePerGas() {
329
+ // const request = this.maxPriorityFeePerGasRequest()
330
+ // return this.sendRequest(request)
331
+ // }
332
+
333
+ // TODO: Expose on Clarity
334
+ // async getFeeHistory(...params) {
335
+ // const request = this.feeHistoryRequest(...params)
336
+ // return this.sendRequest(request)
337
+ // }
338
+
339
+ async getNetVersion() {
340
+ const request = this.getNetVersionRequest()
341
+ return this.sendRequest(request)
342
+ }
343
+
344
+ async getLogs(...params) {
345
+ const request = this.getLogsRequest(...params)
346
+ return this.sendRequest(request)
347
+ }
348
+
349
+ async proxyToCoinNode(params) {
350
+ const request = this.buildRequest(params)
351
+ return this.sendRequest(request)
352
+ }
353
+
354
+ async getGasPriceEstimation() {
355
+ return getFallbackGasPriceEstimation({ server: this })
356
+ }
357
+
358
+ async balanceOf(address, tokenAddress, tag = 'latest') {
359
+ const request = this.balanceOfRequest(address, tokenAddress, tag)
360
+ const result = await this.sendRequest(request)
361
+ const balance = fromHexToString(result)
362
+ return {
363
+ confirmed: {
364
+ [tokenAddress]: balance,
365
+ },
366
+ }
367
+ }
368
+
369
+ async contractStaticCallFrom(contract, functionName, ...params) {
370
+ const request = this.contractStaticCallFromRequest(contract, functionName, ...params)
371
+ return contract[functionName].parse(await this.sendRequest(request))
372
+ }
373
+
374
+ async contractStaticCall(contract, functionName, ...params) {
375
+ return this.contractStaticCallFrom(contract, functionName, undefined, ...params)
376
+ }
377
+
378
+ sendRequest() {
379
+ return __MISSING_FUNCTION_IMPLEMENTATION__()
380
+ }
381
+
382
+ sendBatchRequest() {
383
+ return __MISSING_FUNCTION_IMPLEMENTATION__()
384
+ }
385
+ }
@@ -0,0 +1,48 @@
1
+ import { retry } from '@exodus/simple-retry'
2
+ import { TraceId } from '@exodus/traceparent'
3
+
4
+ const getTextFromResponse = async (response) => {
5
+ try {
6
+ const responseBody = await response.text()
7
+ return responseBody.slice(0, 100)
8
+ } catch {
9
+ return ''
10
+ }
11
+ }
12
+
13
+ export const fetchJson = async (url, fetchOptions) => {
14
+ const response = await fetch(url, fetchOptions)
15
+
16
+ if (!response.ok) {
17
+ const traceId = TraceId.fromResponse(response)
18
+ const error = new Error(
19
+ `${url} returned ${response.status}: ${
20
+ response.statusText || 'Unknown Status Text'
21
+ }. Body: ${await getTextFromResponse(response)}`
22
+ )
23
+ if (traceId) {
24
+ error.traceId = traceId
25
+ }
26
+
27
+ throw error
28
+ }
29
+
30
+ const json = await response.json()
31
+
32
+ // Only capture trace ID if there's an RPC error in the response
33
+ // (consumers extract it via __traceId when throwing the error)
34
+ if (json.error) {
35
+ const traceId = TraceId.fromResponse(response)
36
+ if (traceId) {
37
+ json.__traceId = traceId
38
+ }
39
+ }
40
+
41
+ return json
42
+ }
43
+
44
+ export const fetchJsonRetry = (url, fetchOptions) => {
45
+ const waitTimes = ['3s']
46
+ const fetchWithRetry = retry(fetchJson, { delayTimesMs: waitTimes })
47
+ return fetchWithRetry(url, fetchOptions)
48
+ }
@@ -0,0 +1,77 @@
1
+ import assert from 'minimalistic-assert'
2
+
3
+ import { create } from './api.js'
4
+ import ApiCoinNodesServer from './api-coin-nodes.js'
5
+ import ClarityServer from './clarity.js'
6
+ import ClarityServerV2 from './clarity-v2.js'
7
+ import WsGateway from './ws-gateway.js'
8
+
9
+ export const ValidMonitorTypes = [
10
+ 'no-history',
11
+ 'clarity',
12
+ 'clarity-v2',
13
+ 'clarity-truncated-history',
14
+ 'clarity-v3',
15
+ 'magnifier',
16
+ ]
17
+
18
+ export function createEvmServer({ assetName, serverUrl, monitorType }) {
19
+ assert(assetName, 'assetName is required')
20
+ assert(serverUrl, 'serverUrl is required')
21
+ assert(monitorType, 'monitorType is required')
22
+
23
+ // Truncated-history uses the same Clarity v2 HTTP/WS server; only the monitor class differs.
24
+ switch (monitorType) {
25
+ case 'no-history':
26
+ return new ApiCoinNodesServer({ baseAssetName: assetName, uri: serverUrl })
27
+ case 'clarity':
28
+ return new ClarityServer({ baseAssetName: assetName, uri: serverUrl })
29
+ case 'clarity-v2':
30
+ case 'clarity-truncated-history':
31
+ case 'clarity-v3':
32
+ return new ClarityServerV2({ baseAssetName: assetName, uri: serverUrl })
33
+ case 'magnifier':
34
+ return create(serverUrl, assetName)
35
+ default:
36
+ throw new Error(`No server API found for ${assetName} and type ${monitorType}`)
37
+ }
38
+ }
39
+
40
+ const gatewayUriToWsGateway = Object.create(null)
41
+
42
+ export const createWsGateway = ({ uri } = Object.create(null)) => {
43
+ assert(typeof uri === 'string', 'expected string uri')
44
+
45
+ uri = new URL(uri).toString()
46
+
47
+ const wsGateway = gatewayUriToWsGateway[uri]
48
+ if (wsGateway) return wsGateway
49
+
50
+ // NOTE: We disable the ability to `setServer` on the
51
+ // `WsGateway`, since we assume a mapping between
52
+ // `gatewayUri -> WsGateway`.
53
+ //
54
+ // This is a safe modification since a `WsGateway`
55
+ // monitor isn't rigged up to the same life cycle
56
+ // as other servers.
57
+ class FrozenWsGateway extends WsGateway {
58
+ constructor() {
59
+ super({ uri })
60
+ }
61
+
62
+ setServer() {
63
+ assert(false, 'setServer is disabled')
64
+ }
65
+ }
66
+
67
+ const frozenWsGateway = new FrozenWsGateway()
68
+ gatewayUriToWsGateway[uri] = frozenWsGateway
69
+
70
+ return frozenWsGateway
71
+ }
72
+
73
+ export function getServer(asset) {
74
+ const { name, server } = asset.baseAsset
75
+ assert(server, `${name} server is not set on asset.baseAsset.server`)
76
+ return server
77
+ }
@@ -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
+ }