@exodus/ethereum-api 2.16.0 → 2.17.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 (49) hide show
  1. package/package/package.json +33 -0
  2. package/package/src/eth-like-util.js +147 -0
  3. package/package/src/etherscan/account.js +49 -0
  4. package/package/src/etherscan/index.js +48 -0
  5. package/package/src/etherscan/logs.js +16 -0
  6. package/package/src/etherscan/proxy.js +47 -0
  7. package/package/src/etherscan/request.js +25 -0
  8. package/package/src/etherscan/ws.js +88 -0
  9. package/package/src/exodus-eth-server/api.js +242 -0
  10. package/package/src/exodus-eth-server/index.js +36 -0
  11. package/package/src/exodus-eth-server/ws.js +108 -0
  12. package/package/src/fee-monitor/avalanchec.js +12 -0
  13. package/package/src/fee-monitor/bsc.js +12 -0
  14. package/package/src/fee-monitor/ethereum.js +13 -0
  15. package/package/src/fee-monitor/ethereumclassic.js +12 -0
  16. package/package/src/fee-monitor/fantom.js +12 -0
  17. package/package/src/fee-monitor/harmony.js +12 -0
  18. package/package/src/fee-monitor/index.js +7 -0
  19. package/package/src/fee-monitor/polygon.js +12 -0
  20. package/package/src/gas-estimation.js +103 -0
  21. package/package/src/get-balances.js +38 -0
  22. package/package/src/index.js +11 -0
  23. package/package/src/simulate-tx/fetch-tx-preview.js +21 -0
  24. package/package/src/simulate-tx/index.js +2 -0
  25. package/package/src/simulate-tx/simulate-eth-tx.js +86 -0
  26. package/package/src/staking/fantom-staking.js +115 -0
  27. package/package/src/staking/index.js +2 -0
  28. package/package/src/staking/matic-staking.js +159 -0
  29. package/package/src/tx-log/__tests__/assets-for-test-helper.js +30 -0
  30. package/package/src/tx-log/__tests__/bsc-history-return-values-for-test-helper.js +94 -0
  31. package/package/src/tx-log/__tests__/bsc-monitor.integration.test.js +167 -0
  32. package/package/src/tx-log/__tests__/bsc-monitor.test.js +143 -0
  33. package/package/src/tx-log/__tests__/ethereum-history-return-values-for-test-helper.js +357 -0
  34. package/package/src/tx-log/__tests__/ethereum-history-unknown-token-helper.js +612 -0
  35. package/package/src/tx-log/__tests__/ethereum-monitor.integration.test.js +163 -0
  36. package/package/src/tx-log/__tests__/ethereum-monitor.test.js +211 -0
  37. package/package/src/tx-log/__tests__/monitor-test-helper.js +39 -0
  38. package/package/src/tx-log/__tests__/steth-monitor.integration.test.js +91 -0
  39. package/package/src/tx-log/__tests__/uniswap-monitor.integration.test.js +86 -0
  40. package/package/src/tx-log/__tests__/uniswap-monitor.test.js +158 -0
  41. package/package/src/tx-log/__tests__/uniswap-return-values-for-test-helper.js +193 -0
  42. package/package/src/tx-log/ethereum-monitor.js +293 -0
  43. package/package/src/tx-log/index.js +1 -0
  44. package/package/src/tx-log/ws-updates.js +75 -0
  45. package/package/src/websocket/index.android.js +2 -0
  46. package/package/src/websocket/index.ios.js +2 -0
  47. package/package/src/websocket/index.js +23 -0
  48. package/package.json +3 -3
  49. package/exodus-ethereum-api-2.66.66.tgz +0 -0
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@exodus/ethereum-api",
3
+ "version": "2.21.0",
4
+ "description": "Ethereum Api",
5
+ "main": "src/index.js",
6
+ "author": "Exodus Movement, Inc.",
7
+ "license": "UNLICENSED",
8
+ "homepage": "https://github.com/ExodusMovement/ethereum#readme",
9
+ "files": ["src", "!src/__tests__"],
10
+ "publishConfig": {
11
+ "access": "restricted"
12
+ },
13
+ "dependencies": {
14
+ "@exodus/asset-lib": "^3.5.4",
15
+ "@exodus/crypto": "^1.0.0-rc.0",
16
+ "@exodus/ethereum-lib": "^2.15.0",
17
+ "@exodus/ethereumjs-util": "^7.1.0-exodus.6",
18
+ "@exodus/simple-retry": "^0.0.6",
19
+ "@exodus/solidity-contract": "^1.0.1",
20
+ "fetchival": "0.3.3",
21
+ "make-concurrent": "4.0.0",
22
+ "minimalistic-assert": "^1.0.1",
23
+ "ms": "^2.1.1",
24
+ "url": "0.10.3",
25
+ "url-join": "4.0.0",
26
+ "ws": "6.1.0"
27
+ },
28
+ "devDependencies": {
29
+ "@exodus/assets": "^8.0.67",
30
+ "@exodus/assets-base": "^8.0.136",
31
+ "@exodus/models": "^8.7.2"
32
+ }
33
+ }
@@ -0,0 +1,147 @@
1
+ import { normalizeTxId, isEthereumLikeAsset, isEthereumLikeToken, ABI } from '@exodus/ethereum-lib'
2
+ import { eth, serverMap, getServer } from './exodus-eth-server'
3
+ import { memoizeLruCache } from '@exodus/asset-lib'
4
+ import assets from '@exodus/assets'
5
+ import SolidityContract from '@exodus/solidity-contract'
6
+
7
+ // Mobile only.
8
+ // Behavior is buggy, because the default server used is ethereum.
9
+ // We should refactor mobile to pass 'asset' instead of 'assetName' so that we can use 'isContractAddress'. But that would touch many assets.
10
+ export async function isContract(baseAssetName, address) {
11
+ const server = serverMap[baseAssetName] || eth
12
+ return server.isContract(address)
13
+ }
14
+
15
+ export async function isContractAddress({ asset, address }) {
16
+ return getServer(asset).isContract(address)
17
+ }
18
+
19
+ export async function isForwarderContract({ asset, address }) {
20
+ const contractCode = await getServer(asset).getCode(address)
21
+ return (
22
+ contractCode ===
23
+ '0x5836818037808036817364b29dc43e817817cf77468c8dda63d98ce08fb25af43d91908282803e602b57fd5bf3'
24
+ )
25
+ }
26
+
27
+ export async function getNonce({ asset, address, tag = 'latest' }) {
28
+ const server = getServer(asset)
29
+ const nonce = await server.getTransactionCount(address, tag)
30
+ return parseInt(nonce, 16)
31
+ }
32
+
33
+ export async function estimateGas({ asset, ...args }) {
34
+ return getServer(asset).estimateGas(args)
35
+ }
36
+
37
+ // Only base assets, not tokens
38
+ export async function getBalance({ asset, address }) {
39
+ if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
40
+ const server = getServer(asset)
41
+ const balances = await server.getBalance(address)
42
+ return balances?.confirmed?.value || '0'
43
+ }
44
+
45
+ // Only ETH-like assets with token support
46
+ export async function getTokenBalance({ asset, address }) {
47
+ if (!isEthereumLikeToken(asset)) throw new Error(`unsupported ETH-like token ${asset.name}`)
48
+ const server = getServer(asset)
49
+ const balances = await server.getBalance(address)
50
+ const contractAddress = asset.contract.address.toLowerCase()
51
+ return balances?.confirmed?.[contractAddress] || '0'
52
+ }
53
+
54
+ export async function getTokenBalanceFromNode({ asset, address }) {
55
+ if (!isEthereumLikeToken(asset)) throw new Error(`unsupported ETH-like token ${asset.name}`)
56
+ const server = getServer(asset)
57
+ const contractAddress = asset.contract.address.toLowerCase()
58
+ const balances = await server.balanceOf(address, contractAddress)
59
+ return balances?.confirmed?.[contractAddress] || '0'
60
+ }
61
+
62
+ // Returns function for supplied asset
63
+ export function sendRawTransaction(asset) {
64
+ return getServer(asset).sendRawTransaction
65
+ }
66
+
67
+ export async function transactionExists({ asset, txId }) {
68
+ const server = getServer(asset)
69
+ txId = normalizeTxId(txId)
70
+ const txResult = await server.getTransactionByHash(txId)
71
+ return txResult && txResult.hash === txId
72
+ }
73
+
74
+ export const getIsForwarderContract = memoizeLruCache(
75
+ isForwarderContract,
76
+ ({ asset, address }) => `${asset.name}:${address}`,
77
+ { max: 100 }
78
+ )
79
+
80
+ const ERC20 = new SolidityContract(ABI.erc20)
81
+ const ERC20BytesParams = new SolidityContract(ABI.erc20BytesParams)
82
+ const DEFAULT_PARAM_NAMES = ['decimals', 'name', 'symbol']
83
+ const erc20ParamsCache = {}
84
+
85
+ export const getERC20Params = async ({
86
+ assetName,
87
+ address,
88
+ paramNames = DEFAULT_PARAM_NAMES,
89
+ } = {}) => {
90
+ const asset = assets[assetName]
91
+ if (!asset) {
92
+ throw new Error(`${assetName} not found`)
93
+ }
94
+ if (!address) {
95
+ throw new Error(`Token address should be provided, got: ${address}`)
96
+ }
97
+
98
+ const cacheKey = `${address}:${paramNames}`
99
+ if (erc20ParamsCache[cacheKey]) {
100
+ return erc20ParamsCache[cacheKey]
101
+ }
102
+
103
+ const server = getServer(asset)
104
+
105
+ const paramValues = await Promise.all(
106
+ paramNames.map(async (method) => {
107
+ let callResponse
108
+ try {
109
+ callResponse = await server.ethCall({ to: address, data: ERC20[method].methodId })
110
+ } catch (err) {
111
+ if (err.message === 'execution reverted') {
112
+ throw Error(
113
+ `Can't find parameters for contract with address ${address}. Are you sure it is a valid ERC20 contract?`
114
+ )
115
+ }
116
+
117
+ throw Error(err.message)
118
+ }
119
+
120
+ if (method === 'decimals') return parseInt(callResponse)
121
+
122
+ try {
123
+ return ERC20.decodeOutput({ method, data: callResponse })[0]
124
+ } catch (err) {
125
+ // sometimes ERC20s violate the standard and use 'bytes32' type instead of 'string'
126
+ if (err.message.includes('overflow') && callResponse) {
127
+ const hex = ERC20BytesParams.decodeOutput({ method, data: callResponse })[0]
128
+ const rawName = Buffer.from(hex.split('0x')[1], 'hex').toString()
129
+
130
+ // trims 'Maker\x00\x00\x00...' to 'Maker'
131
+ return rawName.slice(0, rawName.indexOf('\x00'))
132
+ }
133
+ }
134
+ })
135
+ )
136
+
137
+ const response = paramNames.reduce(
138
+ (accumulatedObj, paramName, index) => ({
139
+ ...accumulatedObj,
140
+ [paramName]: paramValues[index],
141
+ }),
142
+ {}
143
+ )
144
+ erc20ParamsCache[cacheKey] = response
145
+
146
+ return response
147
+ }
@@ -0,0 +1,49 @@
1
+ import assert from 'assert'
2
+ import request from './request'
3
+
4
+ const isValidResponseCheck = (x) =>
5
+ (x.status === '1' && x.message === 'OK') || x.message === 'No transactions found'
6
+ const _request = async (...args) => request(isValidResponseCheck, 'account', ...args)
7
+
8
+ export async function fetchBalance(address) {
9
+ const balance = await _request('balance', { address })
10
+
11
+ const isValid = /^[0-9]+$/.test(balance)
12
+ if (!isValid) throw new RangeError(`Invalid balance: ${balance}`)
13
+
14
+ return balance
15
+ }
16
+
17
+ export async function fetchTxlist(address, options) {
18
+ const params = { startblock: 0, endblock: 'latest', ...options, address }
19
+ const txlist = await _request('txlist', params)
20
+
21
+ // simple check
22
+ assert(Array.isArray(txlist), `Invalid transactions: ${txlist}`)
23
+
24
+ return txlist
25
+ }
26
+
27
+ export async function fetchTxlistinternal(address, options) {
28
+ const params = { startblock: 0, endblock: 'latest', ...options, address }
29
+ const txlist = await _request('txlistinternal', params)
30
+
31
+ // simple check
32
+ assert(Array.isArray(txlist), `Invalid transactions: ${txlist}`)
33
+
34
+ return txlist
35
+ }
36
+
37
+ export async function tokenBalance(token, address) {
38
+ const params = {
39
+ [token.length === 42 ? 'contractaddress' : 'tokenname']: token,
40
+ address,
41
+ tag: 'latest',
42
+ }
43
+ const balance = await _request('tokenbalance', params)
44
+
45
+ const isValid = /^[0-9]+$/.test(balance)
46
+ if (!isValid) throw new RangeError(`Invalid balance: ${balance}`)
47
+
48
+ return balance
49
+ }
@@ -0,0 +1,48 @@
1
+ import {
2
+ fetchBalance as _fetchBalance,
3
+ fetchTxlist as _fetchTxlist,
4
+ fetchTxlistinternal as _fetchTxlistinternal,
5
+ tokenBalance as _tokenBalance,
6
+ } from './account'
7
+ import {
8
+ sendRawTransaction as _sendRawTransaction,
9
+ getTransactionCount as _getTransactionCount,
10
+ getTransactionReceipt as _getTransactionReceipt,
11
+ estimateGas as _estimateGas,
12
+ getCode as _getCode,
13
+ gasPrice as _gasPrice,
14
+ ethCall as _ethCall,
15
+ } from './proxy'
16
+ import { setEtherscanApiKey } from './request'
17
+ import { getLogs as _getLogs } from './logs'
18
+ import createWebSocket from './ws'
19
+
20
+ export const ETHERSCAN_WS_URL = 'wss://socket.etherscan.io/wshandler'
21
+
22
+ export const fetchBalance = _fetchBalance
23
+ export const fetchTxlist = _fetchTxlist
24
+ export const fetchTxlistinternal = _fetchTxlistinternal
25
+ export const tokenBalance = _tokenBalance
26
+
27
+ export const sendRawTransaction = _sendRawTransaction
28
+ export const getTransactionCount = _getTransactionCount
29
+ export const getTransactionReceipt = _getTransactionReceipt
30
+ export const estimateGas = _estimateGas
31
+ export const getCode = _getCode
32
+ export const gasPrice = _gasPrice
33
+
34
+ export const getLogs = _getLogs
35
+
36
+ export const setApiKey = setEtherscanApiKey
37
+
38
+ export const ws = createWebSocket(ETHERSCAN_WS_URL)
39
+
40
+ export const ethCall = _ethCall
41
+
42
+ export function filterTxsSent(addr, etherscanTxs) {
43
+ return etherscanTxs.filter((tx) => tx.from.toLowerCase() === addr.toLowerCase())
44
+ }
45
+
46
+ export function filterTxsReceived(addr, etherscanTxs) {
47
+ return etherscanTxs.filter((tx) => tx.to.toLowerCase() === addr.toLowerCase())
48
+ }
@@ -0,0 +1,16 @@
1
+ import assert from 'assert'
2
+ import request from './request'
3
+
4
+ const isValidResponseCheck = (x) =>
5
+ (x.status === '1' && x.message === 'OK') || x.message === 'No records found'
6
+ const _request = async (...args) => request(isValidResponseCheck, 'logs', ...args)
7
+
8
+ export async function getLogs(address, fromBlock, toBlock, options) {
9
+ const params = { ...options, address, fromBlock, toBlock }
10
+ const events = await _request('getLogs', params)
11
+
12
+ // simple check
13
+ assert(Array.isArray(events), `Invalid transactions: ${events}`)
14
+
15
+ return events
16
+ }
@@ -0,0 +1,47 @@
1
+ import request from './request'
2
+
3
+ const isValidResponseCheck = (x) => x.result !== undefined
4
+ const _request = async (...args) => request(isValidResponseCheck, 'proxy', ...args)
5
+
6
+ export async function sendRawTransaction(data) {
7
+ const txhash = await _request('eth_sendRawTransaction', { hex: '0x' + data })
8
+
9
+ const isValidTxHash = /^0x[0-9a-fA-F]{64}$/.test(txhash)
10
+ if (!isValidTxHash) throw new Error(`Invalid tx hash: ${txhash}`)
11
+
12
+ return txhash.slice(2)
13
+ }
14
+
15
+ export async function getTransactionCount(address, tag = 'latest') {
16
+ return _request('eth_getTransactionCount', { address, tag })
17
+ }
18
+
19
+ export async function getTransactionReceipt(txhash) {
20
+ return _request('eth_getTransactionReceipt', { txhash })
21
+ }
22
+
23
+ export async function estimateGas(data) {
24
+ return _request('eth_estimateGas', data)
25
+ }
26
+
27
+ export async function getCode(address) {
28
+ const code = await _request('eth_getCode', { address })
29
+
30
+ const isValidCode = /^0x[0-9a-fA-F]*$/.test(code) && code.length % 2 === 0
31
+ if (!isValidCode) throw new Error(`Invalid address code: ${code}`)
32
+
33
+ return code
34
+ }
35
+
36
+ export async function gasPrice() {
37
+ const price = await _request('eth_gasPrice')
38
+
39
+ const isValidPrice = /^0x[0-9a-fA-F]+$/.test(price)
40
+ if (!isValidPrice) throw new Error(`Invalid price: ${price}`)
41
+
42
+ return price
43
+ }
44
+
45
+ export async function ethCall(data) {
46
+ return _request('eth_call', data)
47
+ }
@@ -0,0 +1,25 @@
1
+ import ms from 'ms'
2
+ import makeConcurrent from 'make-concurrent'
3
+ import fetchival from 'fetchival'
4
+ // The module in desktop explicitly sets node-fetch. Do we need this?
5
+ // import fetch from '../fetch'
6
+ // fetchival.fetch = fetch
7
+
8
+ const ETHERSCAN_API_URL = 'https://api.etherscan.io/api'
9
+ const DEFAULT_ETHERSCAN_API_KEY = 'XM3VGRSNW1TMSIR14I9MVFP15X74GNHTRI'
10
+
11
+ let etherscanApiKey = DEFAULT_ETHERSCAN_API_KEY
12
+
13
+ export function setEtherscanApiKey(apiKey) {
14
+ etherscanApiKey = apiKey || DEFAULT_ETHERSCAN_API_KEY
15
+ }
16
+
17
+ export default makeConcurrent(
18
+ async function(isValidResponseCheck, module, action, params = {}) {
19
+ const queryParams = { ...params, module, action, apiKey: etherscanApiKey }
20
+ const data = await fetchival(ETHERSCAN_API_URL, { timeout: ms('15s') }).get(queryParams)
21
+ if (!isValidResponseCheck(data)) throw new Error(`Invalid response: ${JSON.stringify(data)}`)
22
+ return data.result
23
+ },
24
+ { concurrency: 3 }
25
+ )
@@ -0,0 +1,88 @@
1
+ import { EventEmitter } from 'events'
2
+ import ms from 'ms'
3
+ import WebSocket from '../websocket'
4
+
5
+ const RECONNECT_INTERVAL = ms('10s')
6
+ const PING_INTERVAL = ms('20s')
7
+
8
+ export default function createWebSocket(url) {
9
+ const addresses = new Set()
10
+ const events = new EventEmitter().setMaxListeners(20)
11
+ const pingMessage = JSON.stringify({ event: 'ping' })
12
+ let ws
13
+ let wsOpened = false
14
+ let opened = false
15
+ let openTimeoutId
16
+ let pingIntervalId
17
+
18
+ function subscribeAddress(address) {
19
+ const data = JSON.stringify({ event: 'txlist', address })
20
+ ws.send(data)
21
+ }
22
+
23
+ function onMessage(data) {
24
+ data = JSON.parse(data)
25
+ switch (data.event) {
26
+ case 'txlist':
27
+ for (const tx of data.result) events.emit(`address-${data.address}`, tx)
28
+ break
29
+
30
+ case 'subscribe-txlist':
31
+ const match = data.message.toLowerCase().match(/0x[0-9a-f]{40}/)
32
+ if (match && data.status === '1') events.emit(`address-${match[0]}-subscribed`)
33
+ else ws.close()
34
+ break
35
+ }
36
+ }
37
+
38
+ function isOpened() {
39
+ return wsOpened
40
+ }
41
+
42
+ function open() {
43
+ opened = true
44
+ clearTimeout(openTimeoutId)
45
+ if (ws) return
46
+
47
+ ws = new WebSocket(url)
48
+
49
+ ws.on('message', (data) => {
50
+ try {
51
+ onMessage(data)
52
+ } catch (err) {}
53
+ })
54
+ ws.once('open', () => {
55
+ for (const address of addresses.values()) subscribeAddress(address)
56
+ pingIntervalId = setInterval(() => ws && ws.send(pingMessage), PING_INTERVAL)
57
+ wsOpened = true
58
+ events.emit('open')
59
+ })
60
+ ws.once('close', () => {
61
+ ws = null
62
+ clearInterval(pingIntervalId)
63
+ if (opened) openTimeoutId = setTimeout(open, RECONNECT_INTERVAL)
64
+ wsOpened = false
65
+ events.emit('close')
66
+ })
67
+ }
68
+
69
+ function close() {
70
+ opened = false
71
+ clearTimeout(openTimeoutId)
72
+ if (!ws) return
73
+
74
+ ws.close()
75
+ ws = null
76
+ }
77
+
78
+ function watch(address) {
79
+ address = address.toLowerCase()
80
+
81
+ if (addresses.has(address)) return
82
+ addresses.add(address)
83
+
84
+ if (wsOpened) subscribeAddress(address)
85
+ }
86
+
87
+ return { events, isOpened, open, close, watch }
88
+ }
@@ -0,0 +1,242 @@
1
+ import urlJoin from 'url-join'
2
+ import url from 'url'
3
+ import fetchival from 'fetchival'
4
+ import ms from 'ms'
5
+ import createWebSocket from './ws'
6
+ import { retry } from '@exodus/simple-retry'
7
+ import SolidityContract from '@exodus/solidity-contract'
8
+ import { bufferToHex } from '@exodus/ethereumjs-util'
9
+ import { randomUUID } from '@exodus/crypto/randomUUID'
10
+
11
+ const RETRY_DELAYS = ['10s']
12
+
13
+ export function create(defaultURL) {
14
+ let API_URL = defaultURL
15
+ const ws = createWebSocket(() => {
16
+ // eslint-disable-next-line
17
+ const obj = url.parse(API_URL)
18
+ obj.protocol = 'wss:'
19
+ return url.format(obj)
20
+ })
21
+
22
+ async function request(module, params = {}, { version = 'v1', method = 'get' } = {}) {
23
+ const url = urlJoin(version === 'v1' ? API_URL : API_URL.replace('v1', version), module)
24
+ try {
25
+ return await fetchival(url, { timeout: ms('15s') })[method](params)
26
+ } catch (err) {
27
+ let nerr = err
28
+ if (err.response && err.response.status === 500) {
29
+ try {
30
+ const data = await err.response.json()
31
+ const msg = data.error.replace(/^RPC error \(code: -\d+\): /, '')
32
+ nerr = new Error(msg)
33
+ } catch (err) {}
34
+ nerr.finalError = true
35
+ }
36
+
37
+ throw nerr
38
+ }
39
+ }
40
+
41
+ // Default retry function
42
+ const requestWithRetry = retry(request, { delayTimesMs: RETRY_DELAYS })
43
+
44
+ return {
45
+ getURL() {
46
+ return API_URL
47
+ },
48
+
49
+ setURL(newURL) {
50
+ if (newURL === API_URL) return // prevents useless WS reconnections
51
+
52
+ API_URL = newURL || defaultURL
53
+
54
+ if (ws.isCreated()) {
55
+ ws.close()
56
+ ws.open()
57
+ }
58
+ },
59
+
60
+ stop() {
61
+ if (ws.isCreated()) {
62
+ ws.close()
63
+ }
64
+ },
65
+
66
+ ws,
67
+
68
+ async getBalance(address, opts) {
69
+ opts = { startblock: 'earliest', endblock: 'pending', ...opts }
70
+ return requestWithRetry('balance', { address, from: opts.startblock, to: opts.endblock })
71
+ },
72
+
73
+ async getBalanceProxied(address, tag = 'latest') {
74
+ return requestWithRetry('proxy', { method: 'eth_getBalance', address, tag })
75
+ },
76
+
77
+ async balanceOf(address, tokenAddress, tag = 'latest') {
78
+ const contract = SolidityContract.simpleErc20(tokenAddress)
79
+ const callData = contract.balanceOf.build(address)
80
+ const data = {
81
+ data: bufferToHex(callData),
82
+ to: tokenAddress,
83
+ tag,
84
+ }
85
+ const result = await retry(this.ethCall, { delayTimesMs: RETRY_DELAYS })(data)
86
+ return {
87
+ confirmed: {
88
+ [tokenAddress]: parseInt(result, 16),
89
+ },
90
+ }
91
+ },
92
+
93
+ async getHistory(address, opts) {
94
+ opts = { startblock: 'earliest', endblock: 'pending', limit: 1000, ...opts }
95
+ return requestWithRetry('history', {
96
+ address,
97
+ from: opts.startblock,
98
+ to: opts.endblock,
99
+ limit: opts.limit,
100
+ })
101
+ },
102
+
103
+ async getHistoryV2(address, opts = {}) {
104
+ return requestWithRetry(
105
+ 'history',
106
+ {
107
+ address,
108
+ ...opts,
109
+ },
110
+ { version: 'v2' }
111
+ )
112
+ },
113
+
114
+ async gasPrice() {
115
+ return requestWithRetry('proxy', { method: 'eth_gasPrice' })
116
+ },
117
+
118
+ async getTransactionCount(address, tag = 'latest') {
119
+ return requestWithRetry('proxy', { method: 'eth_getTransactionCount', address, tag })
120
+ },
121
+
122
+ async getTransactionByHash(hash) {
123
+ return requestWithRetry('proxy', { method: 'eth_getTransactionByHash', hash })
124
+ },
125
+
126
+ async getTransactionReceipt(txhash) {
127
+ return requestWithRetry('proxy', { method: 'eth_getTransactionReceipt', txhash })
128
+ },
129
+
130
+ async getCode(address, tag = 'latest') {
131
+ return requestWithRetry('proxy', { method: 'eth_getCode', address, tag })
132
+ },
133
+
134
+ async getStorageAt({ address, slot, tag = 'latest' }) {
135
+ return requestWithRetry('proxy', { method: 'eth_getStorageAt', address, slot, tag })
136
+ },
137
+
138
+ async isContract(address) {
139
+ const code = await requestWithRetry('proxy', { method: 'eth_getCode', address })
140
+ return code.length > 2
141
+ },
142
+
143
+ async sendRawTransaction(data) {
144
+ const hex = data.startsWith('0x') ? data : '0x' + data
145
+ return requestWithRetry('proxy', { method: 'eth_sendRawTransaction', hex })
146
+ },
147
+
148
+ async estimateGas(data, tag = 'latest') {
149
+ return requestWithRetry('proxy', { method: 'eth_estimateGas', ...data, tag })
150
+ },
151
+
152
+ /**
153
+ * Hits a magnifier endpoint returning maximum gas limit that a specified
154
+ * contract along with method consumed in the past.
155
+ * @param opts : Object with parameters
156
+ * @returns {Promise<*>}
157
+ */
158
+ async getGasEstimation({ address, data }) {
159
+ return requestWithRetry('gas-estimation', {
160
+ address,
161
+ data,
162
+ })
163
+ },
164
+
165
+ async ethCall(data, tag = 'latest') {
166
+ return request('proxy', { method: 'eth_call', ...data, tag })
167
+ },
168
+
169
+ async blockNumber() {
170
+ return requestWithRetry('proxy', { method: 'eth_blockNumber' })
171
+ },
172
+
173
+ async getBlockByNumber(number, isFullTransactions = false) {
174
+ return requestWithRetry('proxy', {
175
+ method: 'eth_getBlockByNumber',
176
+ number,
177
+ isFullTransactions,
178
+ })
179
+ },
180
+
181
+ async getLogs(params) {
182
+ return requestWithRetry('proxy', { method: 'eth_getLogs', ...params })
183
+ },
184
+
185
+ async simulateRawTransaction(rawTransaction, applyPending = true) {
186
+ rawTransaction = rawTransaction.replace('0x', '')
187
+ return request('proxy', {
188
+ method: 'debug_simulateRawTransaction',
189
+ rawTransaction,
190
+ applyPending,
191
+ })
192
+ },
193
+
194
+ async getCoinbase() {
195
+ return request('proxy', {
196
+ method: 'eth_coinbase',
197
+ })
198
+ },
199
+
200
+ async getBlockByHash(blockHash, fullTx = false) {
201
+ return request('proxy', {
202
+ method: 'eth_getBlockByHash',
203
+ blockHash,
204
+ fullTx,
205
+ })
206
+ },
207
+
208
+ async getBlockTransactionCountByHash(blockHash) {
209
+ return request('proxy', {
210
+ method: 'eth_getBlockTransactionCountByHash',
211
+ blockHash,
212
+ })
213
+ },
214
+
215
+ async getBlockTransactionCountByNumber(quantityOrTag) {
216
+ return request('proxy', {
217
+ method: 'eth_getBlockTransactionCountByNumber',
218
+ quantityOrTag,
219
+ })
220
+ },
221
+
222
+ async getCompilers() {
223
+ return request('proxy', {
224
+ method: 'eth_getCompilers',
225
+ })
226
+ },
227
+
228
+ async getNetVersion() {
229
+ return request('proxy', {
230
+ method: 'net_version',
231
+ })
232
+ },
233
+
234
+ async proxyToCoinNode(requestData) {
235
+ if (!requestData.jsonrpc) requestData.jsonrpc = '2.0'
236
+ if (!requestData.id) requestData.id = randomUUID()
237
+ if (!requestData.params) requestData.params = []
238
+
239
+ return request('proxy', requestData, { version: 'v2', method: 'post' })
240
+ },
241
+ }
242
+ }