@exodus/ethereum-api 0.1.1 → 0.2.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/package.json +3 -6
- package/src/etherscan/account.js +49 -0
- package/src/etherscan/index.js +48 -0
- package/src/etherscan/logs.js +16 -0
- package/src/etherscan/proxy.js +47 -0
- package/src/etherscan/request.js +25 -0
- package/src/etherscan/ws.js +88 -0
- package/src/exodus-eth-server/api.js +114 -0
- package/src/exodus-eth-server/index.js +9 -0
- package/src/exodus-eth-server/ws.js +89 -0
- package/src/index.js +5 -0
- package/src/websocket/index.android.js +2 -0
- package/src/websocket/index.ios.js +2 -0
- package/src/websocket/index.js +3 -0
- package/src/with-fallback.js +14 -0
- package/lib/etherscan/account.js +0 -66
- package/lib/etherscan/index.js +0 -59
- package/lib/etherscan/logs.js +0 -28
- package/lib/etherscan/proxy.js +0 -65
- package/lib/etherscan/request.js +0 -43
- package/lib/etherscan/ws.js +0 -107
- package/lib/exodus-eth-server/api.js +0 -170
- package/lib/exodus-eth-server/index.js +0 -17
- package/lib/exodus-eth-server/ws.js +0 -116
- package/lib/fetch/index.android.js +0 -11
- package/lib/fetch/index.ios.js +0 -11
- package/lib/fetch/index.js +0 -13
- package/lib/index.js +0 -43
- package/lib/websocket/index.android.js +0 -10
- package/lib/websocket/index.ios.js +0 -10
- package/lib/websocket/index.js +0 -13
- package/lib/with-fallback.js +0 -24
package/package.json
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Ethereum Api",
|
|
5
|
-
"main": "
|
|
6
|
-
"files": [
|
|
7
|
-
"lib"
|
|
8
|
-
],
|
|
5
|
+
"main": "src/index.js",
|
|
9
6
|
"author": "Exodus Movement, Inc.",
|
|
10
7
|
"license": "UNLICENSED",
|
|
11
8
|
"homepage": "https://github.com/ExodusMovement/ethereum#readme",
|
|
@@ -20,5 +17,5 @@
|
|
|
20
17
|
"url-join": "4.0.0",
|
|
21
18
|
"ws": "6.1.0"
|
|
22
19
|
},
|
|
23
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "c04f7f85e7025762fa69cd816adf95c5811455d0"
|
|
24
21
|
}
|
|
@@ -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) {
|
|
16
|
+
return _request('eth_getTransactionCount', { address })
|
|
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,114 @@
|
|
|
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
|
+
|
|
7
|
+
export function create(defaultURL) {
|
|
8
|
+
let API_URL = defaultURL
|
|
9
|
+
const ws = createWebSocket(() => {
|
|
10
|
+
// eslint-disable-next-line
|
|
11
|
+
const obj = url.parse(API_URL)
|
|
12
|
+
obj.protocol = 'wss:'
|
|
13
|
+
return url.format(obj)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
async function request(module, params = {}) {
|
|
17
|
+
const url = urlJoin(API_URL, module)
|
|
18
|
+
try {
|
|
19
|
+
return await fetchival(url, { timeout: ms('15s') }).get(params)
|
|
20
|
+
} catch (err) {
|
|
21
|
+
let nerr = err
|
|
22
|
+
if (err.response && err.response.status === 500) {
|
|
23
|
+
try {
|
|
24
|
+
const data = await err.response.json()
|
|
25
|
+
const msg = data.error.replace(/^RPC error \(code: -\d+\): /, '')
|
|
26
|
+
nerr = new Error(msg)
|
|
27
|
+
} catch (err) {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
throw nerr
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
getURL() {
|
|
36
|
+
return API_URL
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
setURL(newURL) {
|
|
40
|
+
API_URL = newURL || defaultURL
|
|
41
|
+
|
|
42
|
+
if (ws.isCreated()) {
|
|
43
|
+
ws.close()
|
|
44
|
+
ws.open()
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
ws,
|
|
49
|
+
|
|
50
|
+
async getBalance(address, opts) {
|
|
51
|
+
opts = { startblock: 'earliest', endblock: 'pending', ...opts }
|
|
52
|
+
return request('balance', { address, from: opts.startblock, to: opts.endblock })
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async getHistory(address, opts) {
|
|
56
|
+
opts = { startblock: 'earliest', endblock: 'pending', limit: 1000, ...opts }
|
|
57
|
+
return request('history', {
|
|
58
|
+
address,
|
|
59
|
+
from: opts.startblock,
|
|
60
|
+
to: opts.endblock,
|
|
61
|
+
limit: opts.limit,
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async gasPrice() {
|
|
66
|
+
return request('proxy', { method: 'eth_gasPrice' })
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async getTransactionCount(address, tag = 'latest') {
|
|
70
|
+
return request('proxy', { method: 'eth_getTransactionCount', address, tag })
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async getTransactionByHash(hash) {
|
|
74
|
+
return request('proxy', { method: 'eth_getTransactionByHash', hash })
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async getTransactionReceipt(txhash) {
|
|
78
|
+
return request('proxy', { method: 'eth_getTransactionReceipt', txhash })
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async getCode(address, tag = 'latest') {
|
|
82
|
+
return request('proxy', { method: 'eth_getCode', address, tag })
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
async sendRawTransaction(data) {
|
|
86
|
+
return request('proxy', { method: 'eth_sendRawTransaction', hex: '0x' + data })
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async estimateGas(data, tag = 'latest') {
|
|
90
|
+
return request('proxy', { method: 'eth_estimateGas', ...data, tag })
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async ethCall(data, tag = 'latest') {
|
|
94
|
+
return request('proxy', { method: 'eth_call', ...data, tag })
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async blockNumber() {
|
|
98
|
+
return request('proxy', { method: 'eth_blockNumber' })
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async getBlockByNumber(number, isFullTransactions = false) {
|
|
102
|
+
return request('proxy', { method: 'eth_getBlockByNumber', number, isFullTransactions })
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async simulateRawTransaction(rawTransaction, applyPending = true) {
|
|
106
|
+
rawTransaction = rawTransaction.replace('0x', '')
|
|
107
|
+
return request('proxy', {
|
|
108
|
+
method: 'debug_simulateRawTransaction',
|
|
109
|
+
rawTransaction,
|
|
110
|
+
applyPending,
|
|
111
|
+
})
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { create } from './api'
|
|
2
|
+
|
|
3
|
+
const EXODUS_ETH_SERVER_URL = 'https://eth.a.exodus.io/wallet/v1/'
|
|
4
|
+
const EXODUS_ETC_SERVER_URL = 'https://etc.a.exodus.io/wallet/v1/'
|
|
5
|
+
|
|
6
|
+
// allow self-signed certs
|
|
7
|
+
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
8
|
+
export const eth = create(EXODUS_ETH_SERVER_URL)
|
|
9
|
+
export const etc = create(EXODUS_ETC_SERVER_URL)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import ms from 'ms'
|
|
2
|
+
import { EventEmitter } from 'events'
|
|
3
|
+
import WebSocket from '../websocket'
|
|
4
|
+
|
|
5
|
+
const RECONNECT_INTERVAL = ms('10s')
|
|
6
|
+
const PING_INTERVAL = ms('10s')
|
|
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 openTimeoutId
|
|
15
|
+
|
|
16
|
+
function subscribeAddress(address) {
|
|
17
|
+
const data = JSON.stringify({ type: 'subscribe-address', address })
|
|
18
|
+
ws.send(data)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function subscribeGasPrice() {
|
|
22
|
+
const data = JSON.stringify({ type: 'subscribe-gasprice' })
|
|
23
|
+
ws.send(data)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function onMessage(data) {
|
|
27
|
+
data = JSON.parse(data)
|
|
28
|
+
switch (data.type) {
|
|
29
|
+
case 'address':
|
|
30
|
+
events.emit(`address-${data.address}`)
|
|
31
|
+
break
|
|
32
|
+
case 'gasprice':
|
|
33
|
+
events.emit('gasprice', data.gasprice)
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function open() {
|
|
39
|
+
opened = true
|
|
40
|
+
clearTimeout(openTimeoutId)
|
|
41
|
+
if (ws) return
|
|
42
|
+
|
|
43
|
+
ws = new WebSocket(getURL())
|
|
44
|
+
ws.onerror = () => {}
|
|
45
|
+
ws.onmessage = (e) => {
|
|
46
|
+
try {
|
|
47
|
+
onMessage(e.data)
|
|
48
|
+
} catch (err) {}
|
|
49
|
+
}
|
|
50
|
+
ws.onopen = () => {
|
|
51
|
+
for (const address of addresses.values()) subscribeAddress(address)
|
|
52
|
+
subscribeGasPrice()
|
|
53
|
+
pingIntervalId = setInterval(() => ws.ping(), PING_INTERVAL)
|
|
54
|
+
events.emit('open')
|
|
55
|
+
}
|
|
56
|
+
ws.onclose = () => {
|
|
57
|
+
ws = null
|
|
58
|
+
clearInterval(pingIntervalId)
|
|
59
|
+
pingIntervalId = null
|
|
60
|
+
if (opened) openTimeoutId = setTimeout(open, RECONNECT_INTERVAL)
|
|
61
|
+
events.emit('close')
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function close() {
|
|
66
|
+
opened = false
|
|
67
|
+
clearTimeout(openTimeoutId)
|
|
68
|
+
if (!ws) return
|
|
69
|
+
|
|
70
|
+
ws.onerror = null
|
|
71
|
+
ws.onmessage = null
|
|
72
|
+
ws.onopen = null
|
|
73
|
+
ws.onclose = null
|
|
74
|
+
ws.close()
|
|
75
|
+
ws = null
|
|
76
|
+
clearInterval(pingIntervalId)
|
|
77
|
+
pingIntervalId = null
|
|
78
|
+
events.emit('close')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function watch(address) {
|
|
82
|
+
if (addresses.has(address)) return
|
|
83
|
+
addresses.add(address)
|
|
84
|
+
|
|
85
|
+
if (ws) subscribeAddress(address)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { events, open, close, watch, isCreated: () => !!ws, isOpened: () => !!pingIntervalId }
|
|
89
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function withFallback(fn, fn2) {
|
|
2
|
+
return async (...args) => {
|
|
3
|
+
try {
|
|
4
|
+
return await fn(...args)
|
|
5
|
+
} catch (err1) {
|
|
6
|
+
try {
|
|
7
|
+
return await fn2(...args)
|
|
8
|
+
} catch (err2) {
|
|
9
|
+
const err = new Error(`${err1.message} | ${err2.message}`)
|
|
10
|
+
throw Object.assign(err, { err1, err2 })
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
package/lib/etherscan/account.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.fetchBalance = fetchBalance;
|
|
7
|
-
exports.fetchTxlist = fetchTxlist;
|
|
8
|
-
exports.fetchTxlistinternal = fetchTxlistinternal;
|
|
9
|
-
exports.tokenBalance = tokenBalance;
|
|
10
|
-
|
|
11
|
-
var _assert = _interopRequireDefault(require("assert"));
|
|
12
|
-
|
|
13
|
-
var _request2 = _interopRequireDefault(require("./request"));
|
|
14
|
-
|
|
15
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
16
|
-
|
|
17
|
-
const isValidResponseCheck = x => x.status === '1' && x.message === 'OK' || x.message === 'No transactions found';
|
|
18
|
-
|
|
19
|
-
const _request = async (...args) => (0, _request2.default)(isValidResponseCheck, 'account', ...args);
|
|
20
|
-
|
|
21
|
-
async function fetchBalance(address) {
|
|
22
|
-
const balance = await _request('balance', {
|
|
23
|
-
address
|
|
24
|
-
});
|
|
25
|
-
const isValid = /^[0-9]+$/.test(balance);
|
|
26
|
-
if (!isValid) throw new RangeError(`Invalid balance: ${balance}`);
|
|
27
|
-
return balance;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function fetchTxlist(address, options) {
|
|
31
|
-
const params = {
|
|
32
|
-
startblock: 0,
|
|
33
|
-
endblock: 'latest',
|
|
34
|
-
...options,
|
|
35
|
-
address
|
|
36
|
-
};
|
|
37
|
-
const txlist = await _request('txlist', params); // simple check
|
|
38
|
-
|
|
39
|
-
(0, _assert.default)(Array.isArray(txlist), `Invalid transactions: ${txlist}`);
|
|
40
|
-
return txlist;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function fetchTxlistinternal(address, options) {
|
|
44
|
-
const params = {
|
|
45
|
-
startblock: 0,
|
|
46
|
-
endblock: 'latest',
|
|
47
|
-
...options,
|
|
48
|
-
address
|
|
49
|
-
};
|
|
50
|
-
const txlist = await _request('txlistinternal', params); // simple check
|
|
51
|
-
|
|
52
|
-
(0, _assert.default)(Array.isArray(txlist), `Invalid transactions: ${txlist}`);
|
|
53
|
-
return txlist;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function tokenBalance(token, address) {
|
|
57
|
-
const params = {
|
|
58
|
-
[token.length === 42 ? 'contractaddress' : 'tokenname']: token,
|
|
59
|
-
address,
|
|
60
|
-
tag: 'latest'
|
|
61
|
-
};
|
|
62
|
-
const balance = await _request('tokenbalance', params);
|
|
63
|
-
const isValid = /^[0-9]+$/.test(balance);
|
|
64
|
-
if (!isValid) throw new RangeError(`Invalid balance: ${balance}`);
|
|
65
|
-
return balance;
|
|
66
|
-
}
|
package/lib/etherscan/index.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.filterTxsSent = filterTxsSent;
|
|
7
|
-
exports.filterTxsReceived = filterTxsReceived;
|
|
8
|
-
exports.ethCall = exports.ws = exports.setApiKey = exports.getLogs = exports.gasPrice = exports.getCode = exports.estimateGas = exports.getTransactionReceipt = exports.getTransactionCount = exports.sendRawTransaction = exports.tokenBalance = exports.fetchTxlistinternal = exports.fetchTxlist = exports.fetchBalance = exports.ETHERSCAN_WS_URL = void 0;
|
|
9
|
-
|
|
10
|
-
var _account = require("./account");
|
|
11
|
-
|
|
12
|
-
var _proxy = require("./proxy");
|
|
13
|
-
|
|
14
|
-
var _request = require("./request");
|
|
15
|
-
|
|
16
|
-
var _logs = require("./logs");
|
|
17
|
-
|
|
18
|
-
var _ws = _interopRequireDefault(require("./ws"));
|
|
19
|
-
|
|
20
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
21
|
-
|
|
22
|
-
const ETHERSCAN_WS_URL = 'wss://socket.etherscan.io/wshandler';
|
|
23
|
-
exports.ETHERSCAN_WS_URL = ETHERSCAN_WS_URL;
|
|
24
|
-
const fetchBalance = _account.fetchBalance;
|
|
25
|
-
exports.fetchBalance = fetchBalance;
|
|
26
|
-
const fetchTxlist = _account.fetchTxlist;
|
|
27
|
-
exports.fetchTxlist = fetchTxlist;
|
|
28
|
-
const fetchTxlistinternal = _account.fetchTxlistinternal;
|
|
29
|
-
exports.fetchTxlistinternal = fetchTxlistinternal;
|
|
30
|
-
const tokenBalance = _account.tokenBalance;
|
|
31
|
-
exports.tokenBalance = tokenBalance;
|
|
32
|
-
const sendRawTransaction = _proxy.sendRawTransaction;
|
|
33
|
-
exports.sendRawTransaction = sendRawTransaction;
|
|
34
|
-
const getTransactionCount = _proxy.getTransactionCount;
|
|
35
|
-
exports.getTransactionCount = getTransactionCount;
|
|
36
|
-
const getTransactionReceipt = _proxy.getTransactionReceipt;
|
|
37
|
-
exports.getTransactionReceipt = getTransactionReceipt;
|
|
38
|
-
const estimateGas = _proxy.estimateGas;
|
|
39
|
-
exports.estimateGas = estimateGas;
|
|
40
|
-
const getCode = _proxy.getCode;
|
|
41
|
-
exports.getCode = getCode;
|
|
42
|
-
const gasPrice = _proxy.gasPrice;
|
|
43
|
-
exports.gasPrice = gasPrice;
|
|
44
|
-
const getLogs = _logs.getLogs;
|
|
45
|
-
exports.getLogs = getLogs;
|
|
46
|
-
const setApiKey = _request.setEtherscanApiKey;
|
|
47
|
-
exports.setApiKey = setApiKey;
|
|
48
|
-
const ws = (0, _ws.default)(ETHERSCAN_WS_URL);
|
|
49
|
-
exports.ws = ws;
|
|
50
|
-
const ethCall = _proxy.ethCall;
|
|
51
|
-
exports.ethCall = ethCall;
|
|
52
|
-
|
|
53
|
-
function filterTxsSent(addr, etherscanTxs) {
|
|
54
|
-
return etherscanTxs.filter(tx => tx.from.toLowerCase() === addr.toLowerCase());
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function filterTxsReceived(addr, etherscanTxs) {
|
|
58
|
-
return etherscanTxs.filter(tx => tx.to.toLowerCase() === addr.toLowerCase());
|
|
59
|
-
}
|