@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.
- package/CHANGELOG.md +22 -0
- package/package.json +2 -2
- package/src/create-asset-utils.js +0 -10
- package/src/error-wrapper.js +9 -1
- package/src/eth-like-util.js +7 -2
- package/src/exodus-eth-server/index.js +0 -4
- package/src/get-balance-for-address.js +1 -1
- package/src/get-balances.js +23 -13
- package/src/index.js +0 -1
- package/src/nft-utils.js +6 -2
- package/src/server-based-fee-monitor.js +1 -1
- package/src/tx-log/clarity-truncated-history-monitor.js +31 -10
- package/src/tx-log/clarity-truncated-history-monitor.md +6 -2
- package/src/tx-log/index.js +0 -1
- package/src/tx-log/monitor-utils/extract-balance-from-clarity-account-info.js +7 -1
- package/src/tx-log/monitor-utils/index.js +0 -2
- package/src/exodus-eth-server/api.js +0 -276
- package/src/exodus-eth-server/ws.js +0 -126
- package/src/tx-log/ethereum-monitor.js +0 -351
- package/src/tx-log/monitor-utils/get-history-from-server.js +0 -43
- package/src/tx-log/monitor-utils/get-log-items-from-server-tx.js +0 -203
- package/src/tx-log/monitor-utils/get-names-of-tokens-transferred-by-server-tx.js +0 -40
- package/src/tx-log/ws-updates.js +0 -71
|
@@ -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
|
-
}
|