@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.
- package/package/package.json +33 -0
- package/package/src/eth-like-util.js +147 -0
- package/package/src/etherscan/account.js +49 -0
- package/package/src/etherscan/index.js +48 -0
- package/package/src/etherscan/logs.js +16 -0
- package/package/src/etherscan/proxy.js +47 -0
- package/package/src/etherscan/request.js +25 -0
- package/package/src/etherscan/ws.js +88 -0
- package/package/src/exodus-eth-server/api.js +242 -0
- package/package/src/exodus-eth-server/index.js +36 -0
- package/package/src/exodus-eth-server/ws.js +108 -0
- package/package/src/fee-monitor/avalanchec.js +12 -0
- package/package/src/fee-monitor/bsc.js +12 -0
- package/package/src/fee-monitor/ethereum.js +13 -0
- package/package/src/fee-monitor/ethereumclassic.js +12 -0
- package/package/src/fee-monitor/fantom.js +12 -0
- package/package/src/fee-monitor/harmony.js +12 -0
- package/package/src/fee-monitor/index.js +7 -0
- package/package/src/fee-monitor/polygon.js +12 -0
- package/package/src/gas-estimation.js +103 -0
- package/package/src/get-balances.js +38 -0
- package/package/src/index.js +11 -0
- package/package/src/simulate-tx/fetch-tx-preview.js +21 -0
- package/package/src/simulate-tx/index.js +2 -0
- package/package/src/simulate-tx/simulate-eth-tx.js +86 -0
- package/package/src/staking/fantom-staking.js +115 -0
- package/package/src/staking/index.js +2 -0
- package/package/src/staking/matic-staking.js +159 -0
- package/package/src/tx-log/__tests__/assets-for-test-helper.js +30 -0
- package/package/src/tx-log/__tests__/bsc-history-return-values-for-test-helper.js +94 -0
- package/package/src/tx-log/__tests__/bsc-monitor.integration.test.js +167 -0
- package/package/src/tx-log/__tests__/bsc-monitor.test.js +143 -0
- package/package/src/tx-log/__tests__/ethereum-history-return-values-for-test-helper.js +357 -0
- package/package/src/tx-log/__tests__/ethereum-history-unknown-token-helper.js +612 -0
- package/package/src/tx-log/__tests__/ethereum-monitor.integration.test.js +163 -0
- package/package/src/tx-log/__tests__/ethereum-monitor.test.js +211 -0
- package/package/src/tx-log/__tests__/monitor-test-helper.js +39 -0
- package/package/src/tx-log/__tests__/steth-monitor.integration.test.js +91 -0
- package/package/src/tx-log/__tests__/uniswap-monitor.integration.test.js +86 -0
- package/package/src/tx-log/__tests__/uniswap-monitor.test.js +158 -0
- package/package/src/tx-log/__tests__/uniswap-return-values-for-test-helper.js +193 -0
- package/package/src/tx-log/ethereum-monitor.js +293 -0
- package/package/src/tx-log/index.js +1 -0
- package/package/src/tx-log/ws-updates.js +75 -0
- package/package/src/websocket/index.android.js +2 -0
- package/package/src/websocket/index.ios.js +2 -0
- package/package/src/websocket/index.js +23 -0
- package/package.json +3 -3
- package/exodus-ethereum-api-2.66.66.tgz +0 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { EthereumMonitor } from '../ethereum-monitor'
|
|
2
|
+
import { size, omit } from 'lodash'
|
|
3
|
+
import { AccountState } from '@exodus/models'
|
|
4
|
+
import { once } from 'events'
|
|
5
|
+
import { ethereum as feeData } from '@exodus/ethereum-lib/src/fee-data'
|
|
6
|
+
import assetMap from './assets-for-test-helper'
|
|
7
|
+
import { expectSameValue, toBalanceFromTx } from './monitor-test-helper'
|
|
8
|
+
import { create } from '../../exodus-eth-server/api'
|
|
9
|
+
import {
|
|
10
|
+
PublicKeyAddressResolver,
|
|
11
|
+
logger,
|
|
12
|
+
InMemoryAssetClientInterface,
|
|
13
|
+
} from '@exodus/assets-testing'
|
|
14
|
+
|
|
15
|
+
const EXODUS_ETH_SERVER_URL = 'https://geth.a.exodus.io/wallet/v1/'
|
|
16
|
+
|
|
17
|
+
jest.setTimeout(20000)
|
|
18
|
+
|
|
19
|
+
const { ethereum } = assetMap
|
|
20
|
+
|
|
21
|
+
// Replacing default gasPrice to be sure WS will change it
|
|
22
|
+
const dummyGasPrice = ethereum.currency.Gwei(1) // 1 is the min fee...
|
|
23
|
+
const feeDataMock = feeData.update({ gasPrice: dummyGasPrice.toString() })
|
|
24
|
+
expect(feeDataMock.gasPrice).toEqual(dummyGasPrice)
|
|
25
|
+
|
|
26
|
+
export default class EthereumAccountState extends AccountState {
|
|
27
|
+
static defaults = {
|
|
28
|
+
cursor: '',
|
|
29
|
+
balance: ethereum.currency.ZERO,
|
|
30
|
+
tokenBalances: {},
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
ethereum.api = {
|
|
35
|
+
createAccountState: () => EthereumAccountState,
|
|
36
|
+
getConfirmationsNumber: () => 2,
|
|
37
|
+
getFeeData: () => feeDataMock,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Fernando's dev wallet. Profile 1 and Profile 2
|
|
41
|
+
const walletPublicKeys = {
|
|
42
|
+
ethereum: [
|
|
43
|
+
Buffer.from('0273c38a3c31c31b361dc8d6b93e56c316e34991c478d3a14ea3fcd1ab552bc25e', 'hex'),
|
|
44
|
+
Buffer.from('02f33b1edf1016f6720518c776da30567fc7b7052ae6b596dd22909e0a87164f8d', 'hex'),
|
|
45
|
+
],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('ethereum monitor', () => {
|
|
49
|
+
test('can start monitor and update txs', async () => {
|
|
50
|
+
const assetClientInterface = new InMemoryAssetClientInterface({
|
|
51
|
+
logger,
|
|
52
|
+
assets: assetMap,
|
|
53
|
+
addressResolver: new PublicKeyAddressResolver({ walletPublicKeys }),
|
|
54
|
+
})
|
|
55
|
+
const server = create(EXODUS_ETH_SERVER_URL)
|
|
56
|
+
const getHistoryV2 = jest.fn(async (...args) => {
|
|
57
|
+
const transactions = await server.getHistoryV2(...args)
|
|
58
|
+
return transactions.filter((tx) => {
|
|
59
|
+
return parseInt(tx.blockNumber, 16) < 14808250
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
const monitor = new EthereumMonitor({
|
|
63
|
+
interval: 10000,
|
|
64
|
+
asset: ethereum,
|
|
65
|
+
assetClientInterface,
|
|
66
|
+
logger,
|
|
67
|
+
server: { ...server, getHistoryV2 },
|
|
68
|
+
})
|
|
69
|
+
try {
|
|
70
|
+
await monitor.start()
|
|
71
|
+
expect(logger.error).not.toBeCalled()
|
|
72
|
+
expect(logger.warn).not.toBeCalled()
|
|
73
|
+
|
|
74
|
+
// Sintax sugar
|
|
75
|
+
const getTxLog = (walletAccount, assetName) => {
|
|
76
|
+
return assetClientInterface.getTxLog({ walletAccount, assetName })
|
|
77
|
+
}
|
|
78
|
+
const getState = (walletAccount, assetName) => {
|
|
79
|
+
return assetClientInterface.getAccountState({ walletAccount, assetName })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
expect((await getTxLog('exodus0', 'ethereum')).size).toEqual(7)
|
|
83
|
+
expect((await getTxLog('exodus0', 'bat')).size).toEqual(1)
|
|
84
|
+
expect((await getTxLog('exodus0', 'tetherusd')).size).toEqual(3)
|
|
85
|
+
expect((await getTxLog('exodus1', 'ethereum')).size).toEqual(1)
|
|
86
|
+
|
|
87
|
+
expectSameValue(
|
|
88
|
+
toBalanceFromTx(await getTxLog('exodus0', 'ethereum')),
|
|
89
|
+
ethereum.currency.defaultUnit('0.051201488965893697') // string due to floating error
|
|
90
|
+
)
|
|
91
|
+
expectSameValue(
|
|
92
|
+
toBalanceFromTx(await getTxLog('exodus0', 'bat')),
|
|
93
|
+
assetMap.bat.currency.defaultUnit(8.42277112)
|
|
94
|
+
)
|
|
95
|
+
expectSameValue(
|
|
96
|
+
toBalanceFromTx(await getTxLog('exodus0', 'tetherusd')),
|
|
97
|
+
assetMap.tetherusd.currency.defaultUnit(7.414404)
|
|
98
|
+
)
|
|
99
|
+
expectSameValue(
|
|
100
|
+
toBalanceFromTx(await getTxLog('exodus1', 'ethereum')),
|
|
101
|
+
ethereum.currency.defaultUnit(0.00821921)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// It seems eth balances and tokenBalances are not being filled. Clients needs to read tx like above
|
|
105
|
+
const accountState0 = await getState('exodus0', 'ethereum')
|
|
106
|
+
expectSameValue(accountState0.balance, ethereum.currency.defaultUnit(0))
|
|
107
|
+
expect(accountState0.tokenBalances.steth).toBeDefined()
|
|
108
|
+
const accountState1 = await getState('exodus1', 'ethereum')
|
|
109
|
+
expectSameValue(accountState1.balance, ethereum.currency.defaultUnit(0))
|
|
110
|
+
expect(accountState1.tokenBalances.steth).toBeDefined()
|
|
111
|
+
expect(size(assetClientInterface.states)).toEqual(2)
|
|
112
|
+
} finally {
|
|
113
|
+
await monitor.stop()
|
|
114
|
+
}
|
|
115
|
+
expect(logger.error).not.toBeCalled()
|
|
116
|
+
expect(logger.warn).not.toBeCalled()
|
|
117
|
+
expect(monitor.timer.isRunning).toEqual(false)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('can start and update gas price', async () => {
|
|
121
|
+
// ws is not reliable enough to finish on time.
|
|
122
|
+
const server = create(EXODUS_ETH_SERVER_URL)
|
|
123
|
+
const assetClientInterface = new InMemoryAssetClientInterface({
|
|
124
|
+
logger,
|
|
125
|
+
assets: assetMap,
|
|
126
|
+
addressResolver: new PublicKeyAddressResolver({ walletPublicKeys }),
|
|
127
|
+
})
|
|
128
|
+
const monitor = new EthereumMonitor({
|
|
129
|
+
interval: 10000,
|
|
130
|
+
asset: ethereum,
|
|
131
|
+
assetClientInterface,
|
|
132
|
+
logger,
|
|
133
|
+
server,
|
|
134
|
+
})
|
|
135
|
+
try {
|
|
136
|
+
expect(await assetClientInterface.getFeeData({ assetName: ethereum.name })).toEqual(
|
|
137
|
+
feeDataMock
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const oncePromise = once(assetClientInterface, 'fee-config-updated')
|
|
141
|
+
await monitor.start()
|
|
142
|
+
await oncePromise
|
|
143
|
+
// Once started, feeData gets updated
|
|
144
|
+
expect(await assetClientInterface.getFeeData({ assetName: ethereum.name })).not.toEqual(
|
|
145
|
+
feeDataMock
|
|
146
|
+
)
|
|
147
|
+
// gas price and origin are the changed ones.
|
|
148
|
+
expect(
|
|
149
|
+
omit(
|
|
150
|
+
await assetClientInterface.getFeeData({ assetName: ethereum.name }),
|
|
151
|
+
'gasPrice',
|
|
152
|
+
'origin'
|
|
153
|
+
)
|
|
154
|
+
).toEqual(omit(feeDataMock, 'gasPrice', 'origin'))
|
|
155
|
+
} finally {
|
|
156
|
+
await monitor.stop()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
expect(logger.error).not.toBeCalled()
|
|
160
|
+
expect(logger.warn).not.toBeCalled()
|
|
161
|
+
expect(monitor.timer.isRunning).toEqual(false)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { EthereumMonitor } from '../ethereum-monitor'
|
|
2
|
+
import { encodePublic } from '@exodus/ethereum-lib'
|
|
3
|
+
import { toBalanceFromTx, expectSameValue, mockServer } from './monitor-test-helper'
|
|
4
|
+
import assetMap from './assets-for-test-helper'
|
|
5
|
+
import { AccountState } from '@exodus/models'
|
|
6
|
+
import { ethereum as feeData } from '@exodus/ethereum-lib/src/fee-data'
|
|
7
|
+
import historyReturnValuesForTest from './ethereum-history-return-values-for-test-helper'
|
|
8
|
+
import historyReturnValuesUnknownToken from './ethereum-history-unknown-token-helper'
|
|
9
|
+
import { size, cloneDeep } from 'lodash'
|
|
10
|
+
import {
|
|
11
|
+
PublicKeyAddressResolver,
|
|
12
|
+
logger,
|
|
13
|
+
InMemoryAssetClientInterface,
|
|
14
|
+
} from '@exodus/assets-testing'
|
|
15
|
+
|
|
16
|
+
const { ethereum } = assetMap
|
|
17
|
+
|
|
18
|
+
export default class EthereumAccountState extends AccountState {
|
|
19
|
+
static defaults = {
|
|
20
|
+
cursor: '',
|
|
21
|
+
balance: ethereum.currency.ZERO,
|
|
22
|
+
tokenBalances: {},
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
ethereum.api = {
|
|
27
|
+
createAccountState: () => EthereumAccountState,
|
|
28
|
+
getConfirmationsNumber: () => 2,
|
|
29
|
+
getFeeData: () => feeData,
|
|
30
|
+
}
|
|
31
|
+
// Fernando's dev wallet. Profile 1 and Profile 2
|
|
32
|
+
const walletPublicKeys = {
|
|
33
|
+
ethereum: [
|
|
34
|
+
Buffer.from('0273c38a3c31c31b361dc8d6b93e56c316e34991c478d3a14ea3fcd1ab552bc25e', 'hex'),
|
|
35
|
+
Buffer.from('02f33b1edf1016f6720518c776da30567fc7b7052ae6b596dd22909e0a87164f8d', 'hex'),
|
|
36
|
+
],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('ethereum monitor', () => {
|
|
40
|
+
test('ethereum can create monitor', () => {
|
|
41
|
+
const assetClientInterface = new InMemoryAssetClientInterface({
|
|
42
|
+
logger,
|
|
43
|
+
assets: assetMap,
|
|
44
|
+
addressResolver: new PublicKeyAddressResolver({ walletPublicKeys }),
|
|
45
|
+
})
|
|
46
|
+
const server = mockServer({})
|
|
47
|
+
const monitor = new EthereumMonitor({
|
|
48
|
+
interval: 10000,
|
|
49
|
+
asset: ethereum,
|
|
50
|
+
assetClientInterface,
|
|
51
|
+
logger,
|
|
52
|
+
server: server,
|
|
53
|
+
})
|
|
54
|
+
expect(monitor.server.getURL()).toEqual('https://mockMe')
|
|
55
|
+
|
|
56
|
+
expect(logger.warn).not.toBeCalled()
|
|
57
|
+
expect(logger.error).not.toBeCalled()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('can start and stop monitor using simulated data', async () => {
|
|
61
|
+
const assetClientInterface = new InMemoryAssetClientInterface({
|
|
62
|
+
logger,
|
|
63
|
+
assets: assetMap,
|
|
64
|
+
addressResolver: new PublicKeyAddressResolver({ walletPublicKeys }),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const getHistoryV2Returns = cloneDeep(historyReturnValuesForTest)
|
|
68
|
+
const server = mockServer({ getHistoryV2Returns })
|
|
69
|
+
|
|
70
|
+
const monitor = new EthereumMonitor({
|
|
71
|
+
interval: 10000,
|
|
72
|
+
asset: ethereum,
|
|
73
|
+
assetClientInterface,
|
|
74
|
+
logger,
|
|
75
|
+
server: server,
|
|
76
|
+
})
|
|
77
|
+
await monitor.start()
|
|
78
|
+
await monitor.stop()
|
|
79
|
+
expect(logger.warn).not.toBeCalled()
|
|
80
|
+
expect(logger.error).not.toBeCalled()
|
|
81
|
+
|
|
82
|
+
// Sintax sugar
|
|
83
|
+
const getTxLog = (walletAccount, assetName) => {
|
|
84
|
+
return assetClientInterface.getTxLog({ walletAccount, assetName })
|
|
85
|
+
}
|
|
86
|
+
const getState = (walletAccount, assetName) => {
|
|
87
|
+
return assetClientInterface.getAccountState({ walletAccount, assetName })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
expect((await getTxLog('exodus0', 'ethereum')).size).toEqual(7)
|
|
91
|
+
expect((await getTxLog('exodus0', 'bat')).size).toEqual(1)
|
|
92
|
+
expect((await getTxLog('exodus0', 'tetherusd')).size).toEqual(3)
|
|
93
|
+
expect((await getTxLog('exodus1', 'ethereum')).size).toEqual(1)
|
|
94
|
+
|
|
95
|
+
expectSameValue(
|
|
96
|
+
toBalanceFromTx(await getTxLog('exodus0', 'ethereum')),
|
|
97
|
+
ethereum.currency.defaultUnit('0.051201488965893697') // string due to floating error
|
|
98
|
+
)
|
|
99
|
+
expectSameValue(
|
|
100
|
+
toBalanceFromTx(await getTxLog('exodus0', 'bat')),
|
|
101
|
+
assetMap.bat.currency.defaultUnit(8.42277112)
|
|
102
|
+
)
|
|
103
|
+
expectSameValue(
|
|
104
|
+
toBalanceFromTx(await getTxLog('exodus0', 'tetherusd')),
|
|
105
|
+
assetMap.tetherusd.currency.defaultUnit(7.414404)
|
|
106
|
+
)
|
|
107
|
+
expectSameValue(
|
|
108
|
+
toBalanceFromTx(await getTxLog('exodus1', 'ethereum')),
|
|
109
|
+
ethereum.currency.defaultUnit(0.00821921)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// It seems eth balances and tokenBalances are not being filled. Clients needs to read tx like above
|
|
113
|
+
expectSameValue(
|
|
114
|
+
(await getState('exodus0', 'ethereum')).balance,
|
|
115
|
+
ethereum.currency.defaultUnit(0)
|
|
116
|
+
)
|
|
117
|
+
expect((await getState('exodus0', 'ethereum')).tokenBalances).toEqual({})
|
|
118
|
+
expectSameValue(
|
|
119
|
+
(await getState('exodus1', 'ethereum')).balance,
|
|
120
|
+
ethereum.currency.defaultUnit(0)
|
|
121
|
+
)
|
|
122
|
+
expect((await getState('exodus1', 'ethereum')).tokenBalances).toEqual({})
|
|
123
|
+
expect(size(assetClientInterface.states)).toEqual(2)
|
|
124
|
+
|
|
125
|
+
expect(logger.error).not.toBeCalled()
|
|
126
|
+
expect(logger.warn).not.toBeCalled()
|
|
127
|
+
expect(monitor.timer.isRunning).toEqual(false)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('emits unknown-tokens events', async () => {
|
|
131
|
+
const walletPublicKeys = {
|
|
132
|
+
ethereum: [
|
|
133
|
+
Buffer.from('03f583b2ccd8cc072baabecf9e124783d8112e0ee76b893e8b341e54d910729fbe', 'hex'),
|
|
134
|
+
Buffer.from('03ab984a086ae1c31207e642ce7c17b9f0811a1cdc66cbe8f78f940a646462b3a8', 'hex'),
|
|
135
|
+
],
|
|
136
|
+
}
|
|
137
|
+
const assetClientInterface = new InMemoryAssetClientInterface({
|
|
138
|
+
logger,
|
|
139
|
+
assets: assetMap,
|
|
140
|
+
addressResolver: new PublicKeyAddressResolver({ walletPublicKeys }),
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const getHistoryV2Returns = cloneDeep(historyReturnValuesUnknownToken)
|
|
144
|
+
const server = mockServer({ getHistoryV2Returns })
|
|
145
|
+
|
|
146
|
+
const monitor = new EthereumMonitor({
|
|
147
|
+
interval: 10000,
|
|
148
|
+
asset: ethereum,
|
|
149
|
+
assetClientInterface,
|
|
150
|
+
logger,
|
|
151
|
+
server: server,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const handler = jest.fn(() => {})
|
|
155
|
+
monitor.on('unknown-tokens', handler)
|
|
156
|
+
|
|
157
|
+
await monitor.start()
|
|
158
|
+
await monitor.stop()
|
|
159
|
+
expect(logger.warn).not.toBeCalled()
|
|
160
|
+
expect(logger.error).not.toBeCalled()
|
|
161
|
+
|
|
162
|
+
const toBalanceFromTx = (txSet) => {
|
|
163
|
+
return txSet.getMutations().slice(-1)[0].balance
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Sintax sugar
|
|
167
|
+
const getTxLog = (walletAccount, assetName) => {
|
|
168
|
+
return assetClientInterface.getTxLog({ walletAccount, assetName })
|
|
169
|
+
}
|
|
170
|
+
const getState = (walletAccount, assetName) => {
|
|
171
|
+
return assetClientInterface.getAccountState({ walletAccount, assetName })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const expectSameValue = (actual, expected) => {
|
|
175
|
+
expect(actual).toEqual(expected)
|
|
176
|
+
expect(actual.equals(expected)).toEqual(true)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
expect((await getTxLog('exodus0', 'ethereum')).size).toEqual(25)
|
|
180
|
+
|
|
181
|
+
expectSameValue(
|
|
182
|
+
toBalanceFromTx(await getTxLog('exodus0', 'ethereum')),
|
|
183
|
+
ethereum.currency.defaultUnit('0.040838166275376413') // string due to floating error
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// It seems eth balances and tokenBalances are not being filled. Clients needs to read tx like above
|
|
187
|
+
expectSameValue(
|
|
188
|
+
(await getState('exodus0', 'ethereum')).balance,
|
|
189
|
+
ethereum.currency.defaultUnit(0)
|
|
190
|
+
)
|
|
191
|
+
expect((await getState('exodus0', 'ethereum')).tokenBalances).toEqual({})
|
|
192
|
+
expect(size(assetClientInterface.states)).toEqual(2)
|
|
193
|
+
|
|
194
|
+
expect(logger.error).not.toBeCalled()
|
|
195
|
+
expect(logger.warn).not.toBeCalled()
|
|
196
|
+
expect(monitor.timer.isRunning).toEqual(false)
|
|
197
|
+
|
|
198
|
+
// unknown token call checks
|
|
199
|
+
expect(handler.mock.calls.length).toBe(1)
|
|
200
|
+
expect(handler.mock.calls[0][0]).toEqual(['0xa2cd3d43c775978a96bdbf12d733d5a1ed94fb18'])
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test('validate address', () => {
|
|
204
|
+
expect(encodePublic(walletPublicKeys.ethereum[0])).toEqual(
|
|
205
|
+
'0x90E481d9A664ebbE4Be180d9501962255463036d'
|
|
206
|
+
)
|
|
207
|
+
expect(encodePublic(walletPublicKeys.ethereum[1])).toEqual(
|
|
208
|
+
'0xf6c138C36341138dDFC314a11038dA8264B7Ef09'
|
|
209
|
+
)
|
|
210
|
+
})
|
|
211
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const toBalanceFromTx = (txSet) => {
|
|
2
|
+
return txSet.getMutations().slice(-1)[0].balance
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const expectSameValue = (actual, expected) => {
|
|
6
|
+
expect(actual.toDefault().toString()).toEqual(expected.toDefault().toString())
|
|
7
|
+
expect(actual.equals(expected)).toEqual(true)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const mockServer = ({ getHistoryV2Returns = {}, balances = {}, balancesOf = {} }) => {
|
|
11
|
+
return {
|
|
12
|
+
getURL() {
|
|
13
|
+
return 'https://mockMe'
|
|
14
|
+
},
|
|
15
|
+
ws: { watch: jest.fn(), events: { on: jest.fn() }, open: jest.fn() },
|
|
16
|
+
|
|
17
|
+
getHistoryV2: jest.fn(async (address) => {
|
|
18
|
+
const history = getHistoryV2Returns[address.toLowerCase()]
|
|
19
|
+
const txPage = history?.shift()
|
|
20
|
+
return txPage || []
|
|
21
|
+
}),
|
|
22
|
+
|
|
23
|
+
balanceOf: jest.fn(async (walletAddress, contractAddress) => {
|
|
24
|
+
// jest doesn't to have when-some-arg-then-some-value kind of mocking
|
|
25
|
+
// https://github.com/facebook/jest/issues/6180
|
|
26
|
+
const balance = balancesOf[walletAddress.toLowerCase()]?.[contractAddress.toLowerCase()] || 0
|
|
27
|
+
return {
|
|
28
|
+
confirmed: {
|
|
29
|
+
[contractAddress]: balance,
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
}),
|
|
33
|
+
|
|
34
|
+
getBalance: jest.fn(async (walletAddress) => {
|
|
35
|
+
const balance = `${balances[walletAddress.toLowerCase()] || 0}`
|
|
36
|
+
return { confirmed: { value: balance } }
|
|
37
|
+
}),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { EthereumMonitor } from '../ethereum-monitor'
|
|
2
|
+
import { AccountState } from '@exodus/models'
|
|
3
|
+
import { ethereum as feeData } from '@exodus/ethereum-lib/src/fee-data'
|
|
4
|
+
import assetMap from './assets-for-test-helper'
|
|
5
|
+
import { toBalanceFromTx } from './monitor-test-helper'
|
|
6
|
+
import { create } from '../../exodus-eth-server/api'
|
|
7
|
+
import { PlainAddressResolver, logger, InMemoryAssetClientInterface } from '@exodus/assets-testing'
|
|
8
|
+
import { getBalances } from '../../get-balances'
|
|
9
|
+
|
|
10
|
+
const EXODUS_ETH_SERVER_URL = 'https://geth.a.exodus.io/wallet/v1/'
|
|
11
|
+
|
|
12
|
+
jest.setTimeout(20000)
|
|
13
|
+
|
|
14
|
+
const { ethereum } = assetMap
|
|
15
|
+
|
|
16
|
+
// Replacing default gasPrice to be sure WS will change it
|
|
17
|
+
const dummyGasPrice = ethereum.currency.Gwei(1) // 1 is the min fee...
|
|
18
|
+
const feeDataMock = feeData.update({ gasPrice: dummyGasPrice.toString() })
|
|
19
|
+
expect(feeDataMock.gasPrice).toEqual(dummyGasPrice)
|
|
20
|
+
|
|
21
|
+
export default class EthereumAccountState extends AccountState {
|
|
22
|
+
static defaults = {
|
|
23
|
+
cursor: '',
|
|
24
|
+
balance: ethereum.currency.ZERO,
|
|
25
|
+
tokenBalances: {},
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
ethereum.api = {
|
|
30
|
+
createAccountState: () => EthereumAccountState,
|
|
31
|
+
getConfirmationsNumber: () => 2,
|
|
32
|
+
getFeeData: () => feeDataMock,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const walletAddresses = {
|
|
36
|
+
ethereum: ['0xed0eebb4d520a6b0eccc4df8e5214e7a6697c111'],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('steth ethereum monitor', () => {
|
|
40
|
+
test('can start monitor and validate steth offchain rewards', async () => {
|
|
41
|
+
const assetClientInterface = new InMemoryAssetClientInterface({
|
|
42
|
+
logger,
|
|
43
|
+
assets: assetMap,
|
|
44
|
+
addressResolver: new PlainAddressResolver({ walletAddresses }),
|
|
45
|
+
})
|
|
46
|
+
const server = create(EXODUS_ETH_SERVER_URL)
|
|
47
|
+
|
|
48
|
+
const monitor = new EthereumMonitor({
|
|
49
|
+
interval: 100000,
|
|
50
|
+
asset: ethereum,
|
|
51
|
+
assetClientInterface,
|
|
52
|
+
logger,
|
|
53
|
+
server,
|
|
54
|
+
})
|
|
55
|
+
try {
|
|
56
|
+
await monitor.start()
|
|
57
|
+
|
|
58
|
+
expect(logger.error).not.toBeCalled()
|
|
59
|
+
expect(logger.warn).not.toBeCalled()
|
|
60
|
+
// Sintax sugar
|
|
61
|
+
const getTxLog = (walletAccount, assetName) => {
|
|
62
|
+
return assetClientInterface.getTxLog({ walletAccount, assetName })
|
|
63
|
+
}
|
|
64
|
+
const accountState = await assetClientInterface.getAccountState({
|
|
65
|
+
walletAccount: 'exodus0',
|
|
66
|
+
assetName: 'ethereum',
|
|
67
|
+
})
|
|
68
|
+
const { balance: stEthBalance } = getBalances({
|
|
69
|
+
asset: assetMap.steth,
|
|
70
|
+
txLog: await getTxLog('exodus0', 'steth'),
|
|
71
|
+
accountState,
|
|
72
|
+
})
|
|
73
|
+
const { balance: ethereumBalance } = getBalances({
|
|
74
|
+
asset: assetMap.ethereum,
|
|
75
|
+
txLog: await getTxLog('exodus0', 'ethereum'),
|
|
76
|
+
accountState,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const stEthBalanceFromTx = toBalanceFromTx(await getTxLog('exodus0', 'steth'))
|
|
80
|
+
|
|
81
|
+
expect(ethereumBalance.gt(assetMap.ethereum.currency.ZERO)).toEqual(true)
|
|
82
|
+
expect(stEthBalanceFromTx.lt(stEthBalance)).toEqual(true)
|
|
83
|
+
} finally {
|
|
84
|
+
await monitor.stop()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
expect(logger.error).not.toBeCalled()
|
|
88
|
+
expect(logger.warn).not.toBeCalled()
|
|
89
|
+
expect(monitor.timer.isRunning).toEqual(false)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { EthereumMonitor } from '../ethereum-monitor'
|
|
2
|
+
import { AccountState } from '@exodus/models'
|
|
3
|
+
import { ethereum as feeData } from '@exodus/ethereum-lib/src/fee-data'
|
|
4
|
+
import assetMap from './assets-for-test-helper'
|
|
5
|
+
import { expectSameValue, toBalanceFromTx } from './monitor-test-helper'
|
|
6
|
+
import { create } from '../../exodus-eth-server/api'
|
|
7
|
+
import { PlainAddressResolver, logger, InMemoryAssetClientInterface } from '@exodus/assets-testing'
|
|
8
|
+
|
|
9
|
+
const EXODUS_ETH_SERVER_URL = 'https://geth.a.exodus.io/wallet/v1/'
|
|
10
|
+
|
|
11
|
+
jest.setTimeout(20000)
|
|
12
|
+
|
|
13
|
+
const { ethereum } = assetMap
|
|
14
|
+
|
|
15
|
+
// Replacing default gasPrice to be sure WS will change it
|
|
16
|
+
const dummyGasPrice = ethereum.currency.Gwei(1) // 1 is the min fee...
|
|
17
|
+
const feeDataMock = feeData.update({ gasPrice: dummyGasPrice.toString() })
|
|
18
|
+
expect(feeDataMock.gasPrice).toEqual(dummyGasPrice)
|
|
19
|
+
|
|
20
|
+
export default class EthereumAccountState extends AccountState {
|
|
21
|
+
static defaults = {
|
|
22
|
+
cursor: '',
|
|
23
|
+
balance: ethereum.currency.ZERO,
|
|
24
|
+
tokenBalances: {},
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ethereum.api = {
|
|
29
|
+
createAccountState: () => EthereumAccountState,
|
|
30
|
+
getConfirmationsNumber: () => 2,
|
|
31
|
+
getFeeData: () => feeDataMock,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Taking address from https://etherscan.io/txs?a=0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
|
|
35
|
+
export const walletAddresses = {
|
|
36
|
+
ethereum: ['0x1f6890cba6150c9f650efd7ee08d7978cfaba0dd'],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('uniswap ethereum monitor', () => {
|
|
40
|
+
test('can start monitor and update txs', async () => {
|
|
41
|
+
const assetClientInterface = new InMemoryAssetClientInterface({
|
|
42
|
+
logger,
|
|
43
|
+
assets: assetMap,
|
|
44
|
+
addressResolver: new PlainAddressResolver({ walletAddresses }),
|
|
45
|
+
})
|
|
46
|
+
const server = create(EXODUS_ETH_SERVER_URL)
|
|
47
|
+
|
|
48
|
+
const getHistoryV2 = jest.fn(async (...args) => {
|
|
49
|
+
const transactions = await server.getHistoryV2(...args)
|
|
50
|
+
return transactions.filter((tx) => {
|
|
51
|
+
return parseInt(tx.blockNumber, 16) < 14932172
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
server.getHistoryV2 = jest.fn(server.getHistoryV2)
|
|
55
|
+
const monitor = new EthereumMonitor({
|
|
56
|
+
interval: 100000,
|
|
57
|
+
asset: ethereum,
|
|
58
|
+
assetClientInterface,
|
|
59
|
+
logger,
|
|
60
|
+
server: { ...server, getHistoryV2 },
|
|
61
|
+
})
|
|
62
|
+
try {
|
|
63
|
+
await monitor.start()
|
|
64
|
+
|
|
65
|
+
expect(logger.error).not.toBeCalled()
|
|
66
|
+
expect(logger.warn).not.toBeCalled()
|
|
67
|
+
// Sintax sugar
|
|
68
|
+
const getTxLog = (walletAccount, assetName) => {
|
|
69
|
+
return assetClientInterface.getTxLog({ walletAccount, assetName })
|
|
70
|
+
}
|
|
71
|
+
const allTxs = await getTxLog('exodus0', 'ethereum')
|
|
72
|
+
expect(allTxs.size).toEqual(47)
|
|
73
|
+
|
|
74
|
+
expectSameValue(
|
|
75
|
+
toBalanceFromTx(await getTxLog('exodus0', 'ethereum')),
|
|
76
|
+
ethereum.currency.defaultUnit('0.082018694406864644')
|
|
77
|
+
) // string due to floating error
|
|
78
|
+
} finally {
|
|
79
|
+
await monitor.stop()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
expect(logger.error).not.toBeCalled()
|
|
83
|
+
expect(logger.warn).not.toBeCalled()
|
|
84
|
+
expect(monitor.timer.isRunning).toEqual(false)
|
|
85
|
+
})
|
|
86
|
+
})
|