@exodus/ethereum-api 2.17.0 → 2.18.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 -3
- package/package/package.json +0 -33
- package/package/src/eth-like-util.js +0 -147
- package/package/src/etherscan/account.js +0 -49
- package/package/src/etherscan/index.js +0 -48
- package/package/src/etherscan/logs.js +0 -16
- package/package/src/etherscan/proxy.js +0 -47
- package/package/src/etherscan/request.js +0 -25
- package/package/src/etherscan/ws.js +0 -88
- package/package/src/exodus-eth-server/api.js +0 -242
- package/package/src/exodus-eth-server/index.js +0 -36
- package/package/src/exodus-eth-server/ws.js +0 -108
- package/package/src/fee-monitor/avalanchec.js +0 -12
- package/package/src/fee-monitor/bsc.js +0 -12
- package/package/src/fee-monitor/ethereum.js +0 -13
- package/package/src/fee-monitor/ethereumclassic.js +0 -12
- package/package/src/fee-monitor/fantom.js +0 -12
- package/package/src/fee-monitor/harmony.js +0 -12
- package/package/src/fee-monitor/index.js +0 -7
- package/package/src/fee-monitor/polygon.js +0 -12
- package/package/src/gas-estimation.js +0 -103
- package/package/src/get-balances.js +0 -38
- package/package/src/index.js +0 -11
- package/package/src/simulate-tx/fetch-tx-preview.js +0 -21
- package/package/src/simulate-tx/index.js +0 -2
- package/package/src/simulate-tx/simulate-eth-tx.js +0 -86
- package/package/src/staking/fantom-staking.js +0 -115
- package/package/src/staking/index.js +0 -2
- package/package/src/staking/matic-staking.js +0 -159
- package/package/src/tx-log/__tests__/assets-for-test-helper.js +0 -30
- package/package/src/tx-log/__tests__/bsc-history-return-values-for-test-helper.js +0 -94
- package/package/src/tx-log/__tests__/bsc-monitor.integration.test.js +0 -167
- package/package/src/tx-log/__tests__/bsc-monitor.test.js +0 -143
- package/package/src/tx-log/__tests__/ethereum-history-return-values-for-test-helper.js +0 -357
- package/package/src/tx-log/__tests__/ethereum-history-unknown-token-helper.js +0 -612
- package/package/src/tx-log/__tests__/ethereum-monitor.integration.test.js +0 -163
- package/package/src/tx-log/__tests__/ethereum-monitor.test.js +0 -211
- package/package/src/tx-log/__tests__/monitor-test-helper.js +0 -39
- package/package/src/tx-log/__tests__/steth-monitor.integration.test.js +0 -91
- package/package/src/tx-log/__tests__/uniswap-monitor.integration.test.js +0 -86
- package/package/src/tx-log/__tests__/uniswap-monitor.test.js +0 -158
- package/package/src/tx-log/__tests__/uniswap-return-values-for-test-helper.js +0 -193
- package/package/src/tx-log/ethereum-monitor.js +0 -293
- package/package/src/tx-log/index.js +0 -1
- package/package/src/tx-log/ws-updates.js +0 -75
- package/package/src/websocket/index.android.js +0 -2
- package/package/src/websocket/index.ios.js +0 -2
- package/package/src/websocket/index.js +0 -23
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { EthereumMonitor } from '../ethereum-monitor'
|
|
2
|
-
import { toBalanceFromTx, expectSameValue, mockServer } from './monitor-test-helper'
|
|
3
|
-
import assetMap from './assets-for-test-helper'
|
|
4
|
-
import { AccountState } from '@exodus/models'
|
|
5
|
-
import { ethereum as feeData } from '@exodus/ethereum-lib/src/fee-data'
|
|
6
|
-
import historyReturnValuesForTest from './uniswap-return-values-for-test-helper'
|
|
7
|
-
import { cloneDeep } from 'lodash'
|
|
8
|
-
import { PlainAddressResolver, logger, InMemoryAssetClientInterface } from '@exodus/assets-testing'
|
|
9
|
-
|
|
10
|
-
const { ethereum } = assetMap
|
|
11
|
-
|
|
12
|
-
export default class EthereumAccountState extends AccountState {
|
|
13
|
-
static defaults = {
|
|
14
|
-
cursor: '',
|
|
15
|
-
balance: ethereum.currency.ZERO,
|
|
16
|
-
tokenBalances: {},
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
ethereum.api = {
|
|
21
|
-
createAccountState: () => EthereumAccountState,
|
|
22
|
-
getConfirmationsNumber: () => 2,
|
|
23
|
-
getFeeData: () => feeData,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Taking address from https://etherscan.io/txs?a=0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
|
|
27
|
-
const address = '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd'
|
|
28
|
-
export const walletAddresses = {
|
|
29
|
-
ethereum: [address],
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('uniswap ethereum monitor', () => {
|
|
33
|
-
test('Processes simulated txs error handling with airgap', async () => {
|
|
34
|
-
// It tests that a following page patches a transaction once has failed.
|
|
35
|
-
const assetClientInterface = new InMemoryAssetClientInterface({
|
|
36
|
-
logger,
|
|
37
|
-
assets: assetMap,
|
|
38
|
-
addressResolver: new PlainAddressResolver({ walletAddresses }),
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const getHistoryV2Returns = cloneDeep(historyReturnValuesForTest)
|
|
42
|
-
const server = mockServer({ getHistoryV2Returns })
|
|
43
|
-
|
|
44
|
-
const monitor = new EthereumMonitor({
|
|
45
|
-
interval: 10000,
|
|
46
|
-
asset: ethereum,
|
|
47
|
-
assetClientInterface,
|
|
48
|
-
logger,
|
|
49
|
-
server: server,
|
|
50
|
-
})
|
|
51
|
-
try {
|
|
52
|
-
await monitor.start()
|
|
53
|
-
expect(logger.warn).not.toBeCalled()
|
|
54
|
-
expect(logger.error).not.toBeCalled()
|
|
55
|
-
|
|
56
|
-
const getTxLog = (walletAccount, assetName) => {
|
|
57
|
-
return assetClientInterface.getTxLog({ walletAccount, assetName })
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
expectSameValue(
|
|
61
|
-
toBalanceFromTx(await getTxLog('exodus0', 'ethereum')),
|
|
62
|
-
ethereum.currency.defaultUnit('4.30113376')
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
expect((await getTxLog('exodus0', 'ethereum')).size).toEqual(3)
|
|
66
|
-
expect(
|
|
67
|
-
(await getTxLog('exodus0', 'ethereum')).get(
|
|
68
|
-
'0x8bb940145571529d0f55a1129f64a91ba2fca622f21cfc4c32b611d4edeaecc5'
|
|
69
|
-
).error
|
|
70
|
-
).toEqual(null)
|
|
71
|
-
|
|
72
|
-
// In the following tick, the transaction has an error.
|
|
73
|
-
await monitor.refresh()
|
|
74
|
-
expect(logger.warn).not.toBeCalled()
|
|
75
|
-
expect(logger.error).not.toBeCalled()
|
|
76
|
-
expectSameValue(
|
|
77
|
-
toBalanceFromTx(await getTxLog('exodus0', 'ethereum')),
|
|
78
|
-
ethereum.currency.defaultUnit('4.352547448906009307')
|
|
79
|
-
)
|
|
80
|
-
expect((await getTxLog('exodus0', 'ethereum')).size).toEqual(5)
|
|
81
|
-
|
|
82
|
-
expect(
|
|
83
|
-
(await getTxLog('exodus0', 'ethereum')).get(
|
|
84
|
-
'0x8bb940145571529d0f55a1129f64a91ba2fca622f21cfc4c32b611d4edeaecc5'
|
|
85
|
-
).error
|
|
86
|
-
).toEqual('Internal error') // The transaction gets updated!
|
|
87
|
-
} finally {
|
|
88
|
-
await monitor.stop()
|
|
89
|
-
}
|
|
90
|
-
expect(logger.error).not.toBeCalled()
|
|
91
|
-
expect(logger.warn).not.toBeCalled()
|
|
92
|
-
expect(monitor.timer.isRunning).toEqual(false)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
test('Processes simulated txs error handling without airgap', async () => {
|
|
96
|
-
// It tests that same tick patches the errors
|
|
97
|
-
const assetClientInterface = new InMemoryAssetClientInterface({
|
|
98
|
-
logger,
|
|
99
|
-
assets: assetMap,
|
|
100
|
-
addressResolver: new PlainAddressResolver({ walletAddresses }),
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
const getHistoryV2Returns = cloneDeep(historyReturnValuesForTest)
|
|
104
|
-
// removing airgap
|
|
105
|
-
getHistoryV2Returns[address] = getHistoryV2Returns[address].filter((page) => page.length)
|
|
106
|
-
const server = mockServer({ getHistoryV2Returns })
|
|
107
|
-
|
|
108
|
-
const monitor = new EthereumMonitor({
|
|
109
|
-
interval: 10000,
|
|
110
|
-
asset: ethereum,
|
|
111
|
-
assetClientInterface,
|
|
112
|
-
logger,
|
|
113
|
-
server: server,
|
|
114
|
-
})
|
|
115
|
-
try {
|
|
116
|
-
await monitor.start()
|
|
117
|
-
expect(logger.warn).not.toBeCalled()
|
|
118
|
-
expect(logger.error).not.toBeCalled()
|
|
119
|
-
|
|
120
|
-
const getTxLog = (walletAccount, assetName) => {
|
|
121
|
-
return assetClientInterface.getTxLog({ walletAccount, assetName })
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
expectSameValue(
|
|
125
|
-
toBalanceFromTx(await getTxLog('exodus0', 'ethereum')),
|
|
126
|
-
ethereum.currency.defaultUnit('4.352547448906009307') // same value as above with gap
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
expect((await getTxLog('exodus0', 'ethereum')).size).toEqual(5)
|
|
130
|
-
expect(
|
|
131
|
-
(await getTxLog('exodus0', 'ethereum')).get(
|
|
132
|
-
'0x8bb940145571529d0f55a1129f64a91ba2fca622f21cfc4c32b611d4edeaecc5'
|
|
133
|
-
).error
|
|
134
|
-
).toEqual('Internal error') // The transaction gets updated!
|
|
135
|
-
|
|
136
|
-
// In the following tick does not do anything...
|
|
137
|
-
await monitor.refresh()
|
|
138
|
-
expect(logger.warn).not.toBeCalled()
|
|
139
|
-
expect(logger.error).not.toBeCalled()
|
|
140
|
-
expectSameValue(
|
|
141
|
-
toBalanceFromTx(await getTxLog('exodus0', 'ethereum')),
|
|
142
|
-
ethereum.currency.defaultUnit('4.352547448906009307')
|
|
143
|
-
)
|
|
144
|
-
expect((await getTxLog('exodus0', 'ethereum')).size).toEqual(5)
|
|
145
|
-
|
|
146
|
-
expect(
|
|
147
|
-
(await getTxLog('exodus0', 'ethereum')).get(
|
|
148
|
-
'0x8bb940145571529d0f55a1129f64a91ba2fca622f21cfc4c32b611d4edeaecc5'
|
|
149
|
-
).error
|
|
150
|
-
).toEqual('Internal error') // The transaction gets updated!
|
|
151
|
-
} finally {
|
|
152
|
-
await monitor.stop()
|
|
153
|
-
}
|
|
154
|
-
expect(logger.error).not.toBeCalled()
|
|
155
|
-
expect(logger.warn).not.toBeCalled()
|
|
156
|
-
expect(monitor.timer.isRunning).toEqual(false)
|
|
157
|
-
})
|
|
158
|
-
})
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
'0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd': [
|
|
3
|
-
[
|
|
4
|
-
{
|
|
5
|
-
blockHash: '0x3a6677ae47c3f00ac472c6ca694df9894a1f56d5f5405e7508f6476fc37831fc',
|
|
6
|
-
blockNumber: '0xe37f71',
|
|
7
|
-
timestamp: '0x629cabf6',
|
|
8
|
-
confirmations: 23362,
|
|
9
|
-
addressIndex: 0,
|
|
10
|
-
hash: '0xdca82329a249baa2679c87dda91442a6827e03e734c3849b062d7260dd41b4ea',
|
|
11
|
-
nonce: '0x88b3d',
|
|
12
|
-
gasPrice: '0xb15d64aa8',
|
|
13
|
-
gas: '0x15f90',
|
|
14
|
-
gasUsed: '0x5208',
|
|
15
|
-
to: '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd',
|
|
16
|
-
from: '0xcad621da75a66c7a8f4ff86d30a2bf981bfc8fdd',
|
|
17
|
-
value: '0x3c65ad436b6a6000',
|
|
18
|
-
status: 1,
|
|
19
|
-
error: null,
|
|
20
|
-
internal: [],
|
|
21
|
-
erc20: [],
|
|
22
|
-
erc721: [],
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
blockHash: '0x74bc1b36ef1b7a6f3b5cf843e880fe02a67dc76ce81239070d53953753966504',
|
|
26
|
-
blockNumber: '0xe3da1b',
|
|
27
|
-
timestamp: '0x62a1e230',
|
|
28
|
-
confirmations: 88,
|
|
29
|
-
addressIndex: 118,
|
|
30
|
-
hash: '0xbd428a8caaa7f0cfb58d3da56c589d66563cd61a6c549f71617f9eb0ba46c930',
|
|
31
|
-
nonce: '0x3d',
|
|
32
|
-
gasPrice: '0x12a05f2000',
|
|
33
|
-
gas: '0xb71b0',
|
|
34
|
-
gasUsed: '0x25406',
|
|
35
|
-
to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
36
|
-
from: '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd',
|
|
37
|
-
value: '0x470de4df820000',
|
|
38
|
-
status: 1,
|
|
39
|
-
error: null,
|
|
40
|
-
internal: [
|
|
41
|
-
{
|
|
42
|
-
type: 'call',
|
|
43
|
-
from: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
44
|
-
to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
45
|
-
value: '0x470de4df820000',
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
erc20: [
|
|
49
|
-
{
|
|
50
|
-
events: true,
|
|
51
|
-
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
52
|
-
from: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
53
|
-
to: '0x8862e2ea7fc090b92d293818e72d1bf646c416db',
|
|
54
|
-
value: '0x470de4df820000',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
events: true,
|
|
58
|
-
address: '0xc85ff37a86f9e5b4bd6d6f96ee77f7cb82cc2a02',
|
|
59
|
-
from: '0x8862e2ea7fc090b92d293818e72d1bf646c416db',
|
|
60
|
-
to: '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd',
|
|
61
|
-
value: '0x3055059cad56',
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
erc721: [],
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
blockHash: '0x0384ae6ce3d7806807fe5b3a620266152fc52ce86a3f6c9d3434b1548dd66023',
|
|
68
|
-
blockNumber: '0xe3da1c',
|
|
69
|
-
timestamp: '0x62a1e249',
|
|
70
|
-
confirmations: 1,
|
|
71
|
-
addressIndex: 120,
|
|
72
|
-
hash: '0x8bb940145571529d0f55a1129f64a91ba2fca622f21cfc4c32b611d4edeaecc5',
|
|
73
|
-
nonce: '0x3e',
|
|
74
|
-
gasPrice: '0x12a05f2000',
|
|
75
|
-
gas: '0xb71b0',
|
|
76
|
-
gasUsed: '0x1aa81',
|
|
77
|
-
to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
78
|
-
from: '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd',
|
|
79
|
-
value: '0x2386f26fc10000',
|
|
80
|
-
status: 1, // Not Failed
|
|
81
|
-
error: null, // Not failed yet
|
|
82
|
-
internal: [
|
|
83
|
-
{
|
|
84
|
-
type: 'call',
|
|
85
|
-
from: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
86
|
-
to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
87
|
-
value: '0x2386f26fc10000',
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
erc20: [],
|
|
91
|
-
erc721: [],
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
[], // Air gap between 2 calls
|
|
95
|
-
[
|
|
96
|
-
{
|
|
97
|
-
blockHash: '0x0384ae6ce3d7806807fe5b3a620266152fc52ce86a3f6c9d3434b1548dd66023',
|
|
98
|
-
blockNumber: '0xe3da1c',
|
|
99
|
-
timestamp: '0x62a1e249',
|
|
100
|
-
confirmations: 87,
|
|
101
|
-
addressIndex: 120,
|
|
102
|
-
hash: '0x8bb940145571529d0f55a1129f64a91ba2fca622f21cfc4c32b611d4edeaecc5',
|
|
103
|
-
nonce: '0x3e',
|
|
104
|
-
gasPrice: '0x12a05f2000',
|
|
105
|
-
gas: '0xb71b0',
|
|
106
|
-
gasUsed: '0x1aa81',
|
|
107
|
-
to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
108
|
-
from: '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd',
|
|
109
|
-
value: '0x2386f26fc10000',
|
|
110
|
-
status: false, // Failing!!!
|
|
111
|
-
error: 'Internal error', // Now the transaction is failing!!!
|
|
112
|
-
internal: [
|
|
113
|
-
{
|
|
114
|
-
type: 'call',
|
|
115
|
-
from: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
116
|
-
to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
117
|
-
value: '0x2386f26fc10000',
|
|
118
|
-
},
|
|
119
|
-
],
|
|
120
|
-
erc20: [],
|
|
121
|
-
erc721: [],
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
blockHash: '0xc277da3873d4b51ed732b29f7df790c30d66d7ef5f693325f82822c16929eb75',
|
|
125
|
-
blockNumber: '0xe3da1d',
|
|
126
|
-
timestamp: '0x62a1e265',
|
|
127
|
-
confirmations: 86,
|
|
128
|
-
addressIndex: 121,
|
|
129
|
-
hash: '0xa4e4bd4d0a18410f8d1c09f8c0c1e2e6ca2b3714283c110ef4f168224e1a68e1',
|
|
130
|
-
nonce: '0x3f',
|
|
131
|
-
gasPrice: '0x174876e800',
|
|
132
|
-
gas: '0xb71b0',
|
|
133
|
-
gasUsed: '0xb622',
|
|
134
|
-
to: '0xc85ff37a86f9e5b4bd6d6f96ee77f7cb82cc2a02',
|
|
135
|
-
from: '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd',
|
|
136
|
-
value: '0x0',
|
|
137
|
-
status: 1,
|
|
138
|
-
error: null,
|
|
139
|
-
internal: [],
|
|
140
|
-
erc20: [],
|
|
141
|
-
erc721: [],
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
blockHash: '0xe0bb1c7ce2f0b4c49d71d8a819113e1b20228b6a8e77e7e6057618de1bd4134b',
|
|
145
|
-
blockNumber: '0xe3da55',
|
|
146
|
-
timestamp: '0x62a1e5e9',
|
|
147
|
-
confirmations: 30,
|
|
148
|
-
addressIndex: 137,
|
|
149
|
-
hash: '0x95add305bb33d0c46bd52ef8ffc2bbc219134c437b2d32e2f1fef3cfa1fa8d08',
|
|
150
|
-
nonce: '0x47',
|
|
151
|
-
gasPrice: '0x174876e800',
|
|
152
|
-
gas: '0xb71b0',
|
|
153
|
-
gasUsed: '0x27698',
|
|
154
|
-
to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
155
|
-
from: '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd',
|
|
156
|
-
value: '0x0',
|
|
157
|
-
status: 1,
|
|
158
|
-
error: null,
|
|
159
|
-
internal: [
|
|
160
|
-
{
|
|
161
|
-
type: 'call',
|
|
162
|
-
from: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
163
|
-
to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
164
|
-
value: '0xdd0c4ba6595adb',
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
type: 'call',
|
|
168
|
-
from: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
169
|
-
to: '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd',
|
|
170
|
-
value: '0xdd0c4ba6595adb',
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
erc20: [
|
|
174
|
-
{
|
|
175
|
-
events: true,
|
|
176
|
-
address: '0xc85ff37a86f9e5b4bd6d6f96ee77f7cb82cc2a02',
|
|
177
|
-
from: '0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd',
|
|
178
|
-
to: '0x8862e2ea7fc090b92d293818e72d1bf646c416db',
|
|
179
|
-
value: '0x98df444b7569',
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
events: true,
|
|
183
|
-
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
184
|
-
from: '0x8862e2ea7fc090b92d293818e72d1bf646c416db',
|
|
185
|
-
to: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d',
|
|
186
|
-
value: '0xdd0c4ba6595adb',
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
erc721: [],
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
],
|
|
193
|
-
}
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/* @flow */
|
|
2
|
-
import { getServer } from '@exodus/ethereum-api'
|
|
3
|
-
import {
|
|
4
|
-
type PendingTransactionsDictionary,
|
|
5
|
-
checkPendingTransactions,
|
|
6
|
-
getAllLogItemsByAsset,
|
|
7
|
-
getDeriveDataNeededForTick,
|
|
8
|
-
getDeriveTransactionsToCheck,
|
|
9
|
-
getHistoryFromServer,
|
|
10
|
-
isRpcBalanceAsset,
|
|
11
|
-
getAssetAddresses,
|
|
12
|
-
} from '@exodus/ethereum-lib'
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
enableWSUpdates,
|
|
16
|
-
subscribeToGasPriceNotifications,
|
|
17
|
-
subscribeToWSNotifications,
|
|
18
|
-
} from './ws-updates'
|
|
19
|
-
|
|
20
|
-
import { isEmpty } from 'lodash'
|
|
21
|
-
|
|
22
|
-
import { type Tx } from '@exodus/models'
|
|
23
|
-
|
|
24
|
-
import { BaseMonitor } from '@exodus/asset-lib'
|
|
25
|
-
|
|
26
|
-
export type { AssetSource } from '@exodus/models/lib/types'
|
|
27
|
-
|
|
28
|
-
type DerivedData = {
|
|
29
|
-
ourWalletAddress: string,
|
|
30
|
-
currentAccountState: Object,
|
|
31
|
-
minimumConfirmations: number,
|
|
32
|
-
unconfirmedTransactions: PendingTransactionsDictionary,
|
|
33
|
-
pendingTransactionsGroupedByAddressAndNonce: PendingTransactionsDictionary,
|
|
34
|
-
simulatedTransactions: any,
|
|
35
|
-
}
|
|
36
|
-
// The base ethereum monitor class handles listening for, fetching,
|
|
37
|
-
// formatting, and populating-to-state all ETH/ETC/ERC20 transactions.
|
|
38
|
-
|
|
39
|
-
export class EthereumMonitor extends BaseMonitor {
|
|
40
|
-
constructor({ server, config, ...args }) {
|
|
41
|
-
super(args)
|
|
42
|
-
this.server = server || getServer(this.asset)
|
|
43
|
-
this.config = { GAS_PRICE_FROM_WEBSOCKET: true, ...config }
|
|
44
|
-
this.deriveDataNeededForTick = getDeriveDataNeededForTick(this.aci)
|
|
45
|
-
this.deriveTransactionsToCheck = getDeriveTransactionsToCheck({
|
|
46
|
-
getTxLog: (...args) => this.aci.getTxLog(...args),
|
|
47
|
-
})
|
|
48
|
-
this.subscribedToWalletAccountNotificationsMap = new Map()
|
|
49
|
-
this.addHook('before-start', (...args) => this.beforeStart(...args))
|
|
50
|
-
this.addHook('after-stop', (...args) => this.afterStop(...args))
|
|
51
|
-
this.subscribedToGasPriceMap = new Map()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
setServer(config = {}) {
|
|
55
|
-
if (config.serverv1 && this.server.getURL() !== config.serverv1) {
|
|
56
|
-
this.server.setURL(config.serverv1)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async deriveData({ assetSource, tokens }: Object): DerivedData {
|
|
61
|
-
const { asset: assetName, walletAccount } = assetSource
|
|
62
|
-
|
|
63
|
-
const {
|
|
64
|
-
ourWalletAddress,
|
|
65
|
-
currentAccountState,
|
|
66
|
-
minimumConfirmations,
|
|
67
|
-
} = await this.deriveDataNeededForTick({ assetName, walletAccount })
|
|
68
|
-
const transactionsToCheck = await this.deriveTransactionsToCheck({
|
|
69
|
-
assetName,
|
|
70
|
-
walletAccount,
|
|
71
|
-
tokens,
|
|
72
|
-
ourWalletAddress,
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
ourWalletAddress,
|
|
77
|
-
currentAccountState,
|
|
78
|
-
minimumConfirmations,
|
|
79
|
-
...transactionsToCheck,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// eslint-disable-next-line no-undef
|
|
84
|
-
async checkPendingTransactions(params): { txsToRemove: { tx: Tx, assetSource: AssetSource }[] } {
|
|
85
|
-
const {
|
|
86
|
-
pendingTransactionsToCheck,
|
|
87
|
-
pendingTransactionsGroupedByAddressAndNonce,
|
|
88
|
-
} = checkPendingTransactions(params)
|
|
89
|
-
const txsToRemove = []
|
|
90
|
-
const {
|
|
91
|
-
assetSource: { walletAccount },
|
|
92
|
-
} = params
|
|
93
|
-
|
|
94
|
-
const updateTx = (tx, asset, { error, remove }) => {
|
|
95
|
-
if (remove) {
|
|
96
|
-
txsToRemove.push({ tx, assetSource: { asset, walletAccount } })
|
|
97
|
-
} else {
|
|
98
|
-
params.logItemsByAsset[asset].push({
|
|
99
|
-
...tx,
|
|
100
|
-
dropped: true,
|
|
101
|
-
error,
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// in case this is an ETH fee tx that has associated ERC20 send txs
|
|
106
|
-
const promises = tx.tokens.map(async (assetName) => {
|
|
107
|
-
const tokenTxSet = await this.aci.getTxLog({ assetName, walletAccount })
|
|
108
|
-
if (remove) {
|
|
109
|
-
txsToRemove.push({
|
|
110
|
-
tx: tokenTxSet.get(tx.txId),
|
|
111
|
-
assetSource: { asset: assetName, walletAccount },
|
|
112
|
-
})
|
|
113
|
-
} else if (tokenTxSet && tokenTxSet.has(tx.txId)) {
|
|
114
|
-
params.logItemsByAsset[assetName].push({
|
|
115
|
-
...tokenTxSet.get(tx.txId),
|
|
116
|
-
error,
|
|
117
|
-
dropped: true,
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
return Promise.all(promises)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
for (const { tx, assetName, replaced = false } of Object.values(
|
|
125
|
-
pendingTransactionsGroupedByAddressAndNonce
|
|
126
|
-
)) {
|
|
127
|
-
if (replaced) {
|
|
128
|
-
await updateTx(tx, assetName, { remove: true })
|
|
129
|
-
delete pendingTransactionsToCheck[tx.txId]
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
for (const { tx, assetName } of Object.values(pendingTransactionsToCheck)) {
|
|
134
|
-
if (params.refresh) await updateTx(tx, assetName, { remove: true })
|
|
135
|
-
else await updateTx(tx, assetName, { error: 'Dropped' })
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
txsToRemove,
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async tick({ refresh, walletAccount }) {
|
|
144
|
-
await this._subscribeToWSNotifications() // checks if it's a new walletAccount
|
|
145
|
-
|
|
146
|
-
const assets = await this.aci.getAssetsForNetwork({ baseAssetName: this.asset.name })
|
|
147
|
-
const tokens = Object.values(assets).filter((asset) => asset.baseAsset.name !== asset.name)
|
|
148
|
-
const tokensByAddress = tokens.reduce((map, token) => {
|
|
149
|
-
const addresses = getAssetAddresses(token)
|
|
150
|
-
for (const address of addresses) map.set(address.toLowerCase(), token)
|
|
151
|
-
return map
|
|
152
|
-
}, new Map())
|
|
153
|
-
const assetSource = { asset: this.asset.name, walletAccount }
|
|
154
|
-
|
|
155
|
-
const derivedData = await this.deriveData({ assetSource, tokens })
|
|
156
|
-
const derivedIndex = derivedData.currentAccountState?.index
|
|
157
|
-
|
|
158
|
-
const { allTransactionsFromServer, index } = await getHistoryFromServer({
|
|
159
|
-
server: this.server,
|
|
160
|
-
ourWalletAddress: derivedData.ourWalletAddress,
|
|
161
|
-
minimumConfirmations: derivedData.minimumConfirmations,
|
|
162
|
-
index: refresh ? 0 : derivedIndex ?? 0,
|
|
163
|
-
})
|
|
164
|
-
const hasNewIndex = !derivedIndex || index > derivedIndex
|
|
165
|
-
|
|
166
|
-
const logItemsByAsset = getAllLogItemsByAsset({
|
|
167
|
-
ourWalletAddress: derivedData.ourWalletAddress,
|
|
168
|
-
allTransactionsFromServer,
|
|
169
|
-
asset: this.asset,
|
|
170
|
-
tokensByAddress,
|
|
171
|
-
assets,
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
const { txsToRemove } = await this.checkPendingTransactions({
|
|
175
|
-
txlist: allTransactionsFromServer,
|
|
176
|
-
assetSource,
|
|
177
|
-
refresh,
|
|
178
|
-
logItemsByAsset,
|
|
179
|
-
asset: this.asset,
|
|
180
|
-
...derivedData,
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
const accountState = await this.getNewAccountState({
|
|
184
|
-
tokens,
|
|
185
|
-
ourWalletAddress: derivedData.ourWalletAddress,
|
|
186
|
-
})
|
|
187
|
-
await this.updateAccountState({ newData: { index, ...accountState }, walletAccount })
|
|
188
|
-
|
|
189
|
-
await this.removeFromTxLog(txsToRemove)
|
|
190
|
-
await this.updateTxLogByAsset({ logItemsByAsset, walletAccount, refresh })
|
|
191
|
-
if (refresh || hasNewIndex) {
|
|
192
|
-
const unknownTokenAddresses = this._getUnknownTokenAddresses({
|
|
193
|
-
transactions: allTransactionsFromServer,
|
|
194
|
-
tokensByAddress,
|
|
195
|
-
})
|
|
196
|
-
if (unknownTokenAddresses.length > 0) {
|
|
197
|
-
this.emit('unknown-tokens', unknownTokenAddresses)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async updateGasPrice(newGasPrice) {
|
|
203
|
-
try {
|
|
204
|
-
const feeConfig = { gasPrice: `${parseInt(newGasPrice, 16)} wei` }
|
|
205
|
-
this.logger.debug(`Update ${this.asset.name} gas price: ${feeConfig.gasPrice}`)
|
|
206
|
-
await this.aci.updateFeeConfig({ assetName: this.asset.name, feeConfig })
|
|
207
|
-
} catch (e) {
|
|
208
|
-
this.logger.warn('error updating gasPrice', e)
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async getNewAccountState({ tokens, ourWalletAddress }) {
|
|
213
|
-
const asset = this.asset
|
|
214
|
-
const newAccountState = {}
|
|
215
|
-
const server = this.server
|
|
216
|
-
if (isRpcBalanceAsset(asset)) {
|
|
217
|
-
const { confirmed } = await server.getBalance(ourWalletAddress)
|
|
218
|
-
newAccountState.balance = asset.currency.baseUnit(confirmed.value)
|
|
219
|
-
}
|
|
220
|
-
const tokenBalancePairs = await Promise.all(
|
|
221
|
-
tokens
|
|
222
|
-
.filter((token) => isRpcBalanceAsset(token) && token.contract.address)
|
|
223
|
-
.map(async (token) => {
|
|
224
|
-
const { confirmed } = await server.balanceOf(ourWalletAddress, token.contract.address)
|
|
225
|
-
const value = token.currency.baseUnit(confirmed[token.contract.address] || 0)
|
|
226
|
-
return value.isZero ? null : [token.name, value]
|
|
227
|
-
})
|
|
228
|
-
)
|
|
229
|
-
const tokenBalances = Object.fromEntries(tokenBalancePairs.filter((pair) => pair))
|
|
230
|
-
if (!isEmpty(tokenBalances)) newAccountState.tokenBalances = tokenBalances
|
|
231
|
-
return newAccountState
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async getReceiveAddressesByWalletAccount() {
|
|
235
|
-
const walletAccounts = await this.aci.getWalletAccounts({ assetName: this.asset.name })
|
|
236
|
-
const addressesByAccount = {}
|
|
237
|
-
for (const walletAccount of walletAccounts) {
|
|
238
|
-
addressesByAccount[walletAccount] = await this.aci.getReceiveAddresses({
|
|
239
|
-
assetName: this.asset.name,
|
|
240
|
-
walletAccount,
|
|
241
|
-
})
|
|
242
|
-
}
|
|
243
|
-
return addressesByAccount
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async _subscribeToWSNotifications() {
|
|
247
|
-
const addressesByWalletAccount = await this.getReceiveAddressesByWalletAccount()
|
|
248
|
-
subscribeToWSNotifications({
|
|
249
|
-
addressesByWalletAccount,
|
|
250
|
-
tick: (...args) => this.tick(...args),
|
|
251
|
-
server: this.server,
|
|
252
|
-
})
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
_getUnknownTokenAddresses({ transactions, tokensByAddress }) {
|
|
256
|
-
const set = transactions.reduce((acc, txn) => {
|
|
257
|
-
const transfers = txn.erc20 || []
|
|
258
|
-
transfers.forEach((transfer) => {
|
|
259
|
-
const addr = transfer.address.toLowerCase()
|
|
260
|
-
if (!tokensByAddress.has(addr)) {
|
|
261
|
-
acc.add(addr)
|
|
262
|
-
}
|
|
263
|
-
}, acc)
|
|
264
|
-
return acc
|
|
265
|
-
}, new Set())
|
|
266
|
-
return Array.from(set)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async beforeStart() {
|
|
270
|
-
const addressesByWalletAccount = await this.getReceiveAddressesByWalletAccount()
|
|
271
|
-
enableWSUpdates({
|
|
272
|
-
interval: this.interval,
|
|
273
|
-
server: this.server,
|
|
274
|
-
timer: this.timer,
|
|
275
|
-
tick: (...args) => this.tick(...args),
|
|
276
|
-
tickAllWalletAccounts: () => this.tickAllWalletAccounts(),
|
|
277
|
-
addressesByWalletAccount,
|
|
278
|
-
beforeStart: true,
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
if (this.config.GAS_PRICE_FROM_WEBSOCKET) {
|
|
282
|
-
subscribeToGasPriceNotifications({
|
|
283
|
-
server: this.server,
|
|
284
|
-
updateGasPrice: (newGasPrice) => this.updateGasPrice(newGasPrice),
|
|
285
|
-
subscribedToGasPriceMap: this.subscribedToGasPriceMap,
|
|
286
|
-
})
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async afterStop() {
|
|
291
|
-
await this.server.stop()
|
|
292
|
-
}
|
|
293
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './ethereum-monitor'
|