@exodus/ethereum-api 9.0.2 → 10.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.
@@ -1,276 +0,0 @@
1
- import { randomUUID } from '@exodus/crypto/randomUUID'
2
- import { bufferToHex } from '@exodus/ethereumjs/util'
3
- import fetchival from '@exodus/fetch/experimental/fetchival'
4
- import { retry } from '@exodus/simple-retry'
5
- import SolidityContract from '@exodus/solidity-contract'
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'
11
-
12
- const RETRY_DELAYS = ['10s']
13
-
14
- export function create(defaultURL, ensAssetName) {
15
- let API_URL = defaultURL
16
- const ws = createWebSocket(() => {
17
- const obj = new URL(API_URL)
18
- obj.protocol = 'wss:'
19
- return `${obj}`
20
- })
21
-
22
- function baseUrl(version = 'v1') {
23
- return new URL(version === 'v1' ? API_URL : API_URL.replace('v1', version))
24
- }
25
-
26
- async function request(module, params = {}, { version = 'v1', method = 'get' } = {}) {
27
- try {
28
- return await fetchival(baseUrl(version), { timeout: ms('15s') })(module).method(
29
- method,
30
- params
31
- )
32
- } catch (err) {
33
- let nerr = err
34
- if (err.response && (err.response.status === 500 || err.response.status === 400)) {
35
- try {
36
- const data = await err.response.json()
37
- const msg = data.error.replace(/^RPC error \(code: -\d+\): /, '')
38
- nerr = new Error(msg)
39
- } catch {}
40
-
41
- nerr.finalError = true
42
- }
43
-
44
- throw nerr
45
- }
46
- }
47
-
48
- // Default retry function
49
- const requestWithRetry = retry(request, { delayTimesMs: RETRY_DELAYS })
50
-
51
- return {
52
- getURL() {
53
- return API_URL
54
- },
55
-
56
- setURL(newURL) {
57
- if (newURL === API_URL) return // prevents useless WS reconnections
58
-
59
- API_URL = newURL || defaultURL
60
-
61
- if (ws.isCreated()) {
62
- ws.close()
63
- ws.open()
64
- }
65
- },
66
-
67
- stop() {
68
- ws.close()
69
- },
70
-
71
- ws,
72
-
73
- async getBalance(address, opts) {
74
- opts = { startblock: 'earliest', endblock: 'pending', ...opts }
75
- return requestWithRetry('balance', { address, from: opts.startblock, to: opts.endblock })
76
- },
77
-
78
- async getBalanceProxied(address, tag = 'latest') {
79
- return requestWithRetry('proxy', { method: 'eth_getBalance', address, tag })
80
- },
81
-
82
- async balanceOf(address, tokenAddress, tag = 'latest') {
83
- const contract = SolidityContract.simpleErc20(tokenAddress)
84
- const callData = contract.balanceOf.build(address)
85
- const data = {
86
- data: bufferToHex(callData),
87
- to: tokenAddress,
88
- tag,
89
- }
90
- const result = await retry(this.ethCall, { delayTimesMs: RETRY_DELAYS })(data)
91
- const balance = fromHexToString(result)
92
- return {
93
- confirmed: {
94
- [tokenAddress]: balance,
95
- },
96
- }
97
- },
98
-
99
- async getHistory(address, opts) {
100
- opts = { startblock: 'earliest', endblock: 'pending', limit: 1000, ...opts }
101
- return requestWithRetry('history', {
102
- address,
103
- from: opts.startblock,
104
- to: opts.endblock,
105
- limit: opts.limit,
106
- })
107
- },
108
-
109
- async getHistoryV2(address, opts = {}) {
110
- return requestWithRetry(
111
- 'history',
112
- {
113
- address,
114
- ...opts,
115
- },
116
- { version: 'v2' }
117
- )
118
- },
119
-
120
- async gasPrice() {
121
- return requestWithRetry('proxy', { method: 'eth_gasPrice' })
122
- },
123
-
124
- async getGasPriceEstimation() {
125
- return getFallbackGasPriceEstimation({ server: this })
126
- },
127
-
128
- // for fee monitor
129
- async getGasPrice() {
130
- return requestWithRetry('proxy', { method: 'eth_gasPrice' })
131
- },
132
-
133
- async getLatestBlock() {
134
- return this.getBlockByNumber('latest')
135
- },
136
-
137
- async getBaseFeePerGas() {
138
- const response = await this.getLatestBlock()
139
- if (response.baseFeePerGas) {
140
- return fromHexToString(response.baseFeePerGas)
141
- }
142
- },
143
-
144
- async getTransactionCount(address, tag = 'latest') {
145
- return requestWithRetry('proxy', { method: 'eth_getTransactionCount', address, tag })
146
- },
147
-
148
- async getTransactionByHash(hash) {
149
- return requestWithRetry('proxy', { method: 'eth_getTransactionByHash', hash })
150
- },
151
-
152
- async getTransactionReceipt(txhash) {
153
- return requestWithRetry('proxy', { method: 'eth_getTransactionReceipt', txhash })
154
- },
155
-
156
- async getCode(address, tag = 'latest') {
157
- return requestWithRetry('proxy', { method: 'eth_getCode', address, tag })
158
- },
159
-
160
- async getStorageAt(address, slot, tag = 'latest') {
161
- return requestWithRetry('proxy', { method: 'eth_getStorageAt', address, slot, tag })
162
- },
163
-
164
- async isContract(address) {
165
- const code = await requestWithRetry('proxy', { method: 'eth_getCode', address })
166
- return code.length > 2
167
- },
168
-
169
- async sendRawTransaction(data) {
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
- )
177
- },
178
-
179
- async estimateGas(data, tag = 'latest') {
180
- return requestWithRetry('proxy', { method: 'eth_estimateGas', ...data, tag })
181
- },
182
-
183
- /**
184
- * Hits a magnifier endpoint returning maximum gas limit that a specified
185
- * contract along with method consumed in the past.
186
- * @param opts : Object with parameters
187
- * @returns {Promise<*>}
188
- */
189
- async getGasEstimation({ address, data }) {
190
- return requestWithRetry('gas-estimation', {
191
- address,
192
- data,
193
- })
194
- },
195
-
196
- async ethCall(data, tag = 'latest') {
197
- return request('proxy', { method: 'eth_call', ...data, tag })
198
- },
199
-
200
- async blockNumber() {
201
- return requestWithRetry('proxy', { method: 'eth_blockNumber' })
202
- },
203
-
204
- async getBlockByNumber(number, isFullTransactions = false) {
205
- return requestWithRetry('proxy', {
206
- method: 'eth_getBlockByNumber',
207
- number,
208
- isFullTransactions,
209
- })
210
- },
211
-
212
- async getLogs(params) {
213
- return requestWithRetry('proxy', { method: 'eth_getLogs', ...params })
214
- },
215
-
216
- async simulateRawTransaction(rawTransaction, applyPending = true) {
217
- rawTransaction = rawTransaction.replace('0x', '')
218
- return request('proxy', {
219
- method: 'debug_simulateRawTransaction',
220
- rawTransaction,
221
- applyPending,
222
- })
223
- },
224
-
225
- async getCoinbase() {
226
- return request('proxy', {
227
- method: 'eth_coinbase',
228
- })
229
- },
230
-
231
- async getBlockByHash(blockHash, fullTx = false) {
232
- return request('proxy', {
233
- method: 'eth_getBlockByHash',
234
- blockHash,
235
- fullTx,
236
- })
237
- },
238
-
239
- async getBlockTransactionCountByHash(blockHash) {
240
- return request('proxy', {
241
- method: 'eth_getBlockTransactionCountByHash',
242
- blockHash,
243
- })
244
- },
245
-
246
- async getBlockTransactionCountByNumber(quantityOrTag) {
247
- return request('proxy', {
248
- method: 'eth_getBlockTransactionCountByNumber',
249
- quantityOrTag,
250
- })
251
- },
252
-
253
- async getCompilers() {
254
- return request('proxy', {
255
- method: 'eth_getCompilers',
256
- })
257
- },
258
-
259
- async getNetVersion() {
260
- return request('proxy', {
261
- method: 'net_version',
262
- })
263
- },
264
-
265
- async proxyToCoinNode(requestData) {
266
- // eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
267
- if (!requestData.jsonrpc) requestData.jsonrpc = '2.0'
268
- // eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
269
- if (!requestData.id) requestData.id = randomUUID()
270
- // eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
271
- if (!requestData.params) requestData.params = []
272
-
273
- return request('proxy', requestData, { version: 'v2', method: 'post' })
274
- },
275
- }
276
- }
@@ -1,126 +0,0 @@
1
- import EventEmitter from 'events/events.js' // forces it to use the module from node_modules
2
- import ms from 'ms'
3
-
4
- const RECONNECT_INTERVAL = ms('10s')
5
- const PING_INTERVAL = ms('10s')
6
- const MAX_RECONNECT_DELAY = ms('15s')
7
-
8
- export default function createWebSocket(getURL) {
9
- const addresses = new Set()
10
- const events = new EventEmitter()
11
- let ws
12
- let pingIntervalId = null
13
- let opened = false
14
- let isWSOpened = false
15
- let openTimeoutId
16
-
17
- function subscribeAddress(address) {
18
- const data = JSON.stringify({ type: 'subscribe-address', address })
19
- ws.send(data)
20
- }
21
-
22
- function subscribeGasPrice() {
23
- const data = JSON.stringify({ type: 'subscribe-gasprice' })
24
- ws.send(data)
25
- }
26
-
27
- function clearPing() {
28
- clearInterval(pingIntervalId)
29
- pingIntervalId = null
30
- }
31
-
32
- function onMessage(data) {
33
- data = JSON.parse(data)
34
- switch (data.type) {
35
- case 'address':
36
- events.emit(`address-${data.address}`)
37
- break
38
- case 'gasprice':
39
- events.emit('gasprice', { gasPrice: data.gasprice, baseFeePerGas: data.baseGasPrice })
40
- break
41
- }
42
- }
43
-
44
- function open() {
45
- opened = true
46
- clearTimeout(openTimeoutId)
47
- if (ws) return
48
-
49
- ws = new WebSocket(getURL())
50
- // eslint-disable-next-line unicorn/prefer-add-event-listener
51
- ws.onerror = (e) => {}
52
- // eslint-disable-next-line unicorn/prefer-add-event-listener
53
- ws.onmessage = (e) => {
54
- try {
55
- onMessage(e.data)
56
- } catch {}
57
- }
58
-
59
- // eslint-disable-next-line unicorn/prefer-add-event-listener
60
- ws.onopen = () => {
61
- for (const address of addresses.values()) subscribeAddress(address)
62
- subscribeGasPrice()
63
- if (typeof ws.ping === 'function') {
64
- pingIntervalId = setInterval(() => ws.ping(), PING_INTERVAL)
65
- } else {
66
- console.warn('Client side ping not available')
67
- }
68
-
69
- isWSOpened = true
70
- events.emit('open')
71
- }
72
-
73
- // eslint-disable-next-line unicorn/prefer-add-event-listener
74
- ws.onclose = () => {
75
- ws = null
76
- isWSOpened = false
77
- clearPing()
78
- if (opened) {
79
- openTimeoutId = setTimeout(open, reconnectDelay())
80
- }
81
-
82
- events.emit('close')
83
- }
84
- }
85
-
86
- function close() {
87
- opened = false
88
- isWSOpened = false
89
- clearTimeout(openTimeoutId)
90
- if (!ws) return
91
- // eslint-disable-next-line unicorn/prefer-add-event-listener
92
- ws.onerror = null
93
- // eslint-disable-next-line unicorn/prefer-add-event-listener
94
- ws.onmessage = null
95
- // eslint-disable-next-line unicorn/prefer-add-event-listener
96
- ws.onopen = null
97
- // eslint-disable-next-line unicorn/prefer-add-event-listener
98
- ws.onclose = null
99
- try {
100
- ws.close()
101
- } catch (e) {
102
- if (!/WebSocket was closed before the connection was established/.test(e.message)) {
103
- throw e
104
- }
105
- }
106
-
107
- ws = null
108
- clearPing()
109
- events.emit('close')
110
- }
111
-
112
- function reconnectDelay() {
113
- const min = RECONNECT_INTERVAL
114
- const max = MAX_RECONNECT_DELAY
115
- return Math.floor(Math.random() * (max - min + 1) + min)
116
- }
117
-
118
- function watch(address) {
119
- if (addresses.has(address)) return
120
- addresses.add(address)
121
-
122
- if (ws) subscribeAddress(address)
123
- }
124
-
125
- return { events, open, close, watch, isCreated: () => !!ws, isOpened: () => isWSOpened }
126
- }