@exodus/ethereum-api 3.4.0 → 3.5.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 -2
- package/src/exodus-eth-server/clarity.js +313 -0
- package/src/exodus-eth-server/index.js +4 -1
- package/src/tx-log/clarity-monitor.js +324 -0
- package/src/tx-log/clarity-utils/filter-effects.js +3 -0
- package/src/tx-log/clarity-utils/get-derive-data-needed-for-tick.js +12 -0
- package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +143 -0
- package/src/tx-log/clarity-utils/get-names-of-tokens-transferred-by-server-tx.js +41 -0
- package/src/tx-log/clarity-utils/index.js +3 -0
- package/src/tx-log/ethereum-monitor.js +2 -0
- package/src/tx-log/index.js +1 -0
- package/src/tx-log/monitor-utils/get-all-log-items-by-asset.js +1 -2
- package/src/tx-log/monitor-utils/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Ethereum Api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"make-concurrent": "4.0.0",
|
|
31
31
|
"minimalistic-assert": "^1.0.1",
|
|
32
32
|
"ms": "^2.1.1",
|
|
33
|
+
"socket.io-client": "2.1.1",
|
|
33
34
|
"url": "0.10.3",
|
|
34
35
|
"url-join": "4.0.0",
|
|
35
36
|
"ws": "^6.1.0"
|
|
@@ -37,5 +38,5 @@
|
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@exodus/models": "^8.10.4"
|
|
39
40
|
},
|
|
40
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "adb3bcb3d3c538706a0fd7fe3cf11ba89712905b"
|
|
41
42
|
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { bufferToHex } from '@exodus/ethereumjs-util'
|
|
2
|
+
import SolidityContract from '@exodus/solidity-contract'
|
|
3
|
+
import EventEmitter from 'events'
|
|
4
|
+
import io from 'socket.io-client'
|
|
5
|
+
|
|
6
|
+
export default class ClarityServer extends EventEmitter {
|
|
7
|
+
constructor({ baseAssetName, uri }) {
|
|
8
|
+
super()
|
|
9
|
+
this.baseAssetName = baseAssetName
|
|
10
|
+
this.uri = uri
|
|
11
|
+
this.baseNamespace = `/v1/${this.baseAssetName}`
|
|
12
|
+
this.sockets = {}
|
|
13
|
+
this.id = 0
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
setURI(uri) {
|
|
17
|
+
this.dispose()
|
|
18
|
+
this.uri = uri
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
connectTransactions({ walletAccount, address }) {
|
|
22
|
+
const namespace = this.formatTransactionsNamespace(address)
|
|
23
|
+
if (!this.sockets[namespace]) {
|
|
24
|
+
this.sockets[namespace] = this.createSocket(namespace)
|
|
25
|
+
this.sockets[namespace].on('transaction', (isPending, transaction) => {
|
|
26
|
+
this.emit('transaction', { walletAccount, isPending, transaction })
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
return this.sockets[namespace]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
connectRpc() {
|
|
33
|
+
const namespace = this.formatRpcNamespace()
|
|
34
|
+
if (!this.sockets[namespace]) {
|
|
35
|
+
this.sockets[namespace] = this.createSocket(namespace)
|
|
36
|
+
}
|
|
37
|
+
return this.sockets[namespace]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
connectFee() {
|
|
41
|
+
const namespace = this.formatFeeNamespace()
|
|
42
|
+
if (!this.sockets[namespace]) {
|
|
43
|
+
this.sockets[namespace] = this.createSocket(namespace)
|
|
44
|
+
this.sockets[namespace].on('feeUpdated', (fee) => this.emit('feeUpdated', fee))
|
|
45
|
+
}
|
|
46
|
+
return this.sockets[namespace]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
createSocket(namespace) {
|
|
50
|
+
return io(`${this.uri}${namespace}`, { transports: ['websocket', 'polling'] })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
disconnectTransactions(address) {
|
|
54
|
+
const namespace = this.formatTransactionsNamespace(address)
|
|
55
|
+
this.disconnectSocket(namespace)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
disconnectRpc() {
|
|
59
|
+
this.disconnectSocket(this.rpcNamespace)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
disconnectFee() {
|
|
63
|
+
this.disconnectSocket(this.feeNamespace)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
disconnectSocket(namespace) {
|
|
67
|
+
if (this.sockets[namespace]) {
|
|
68
|
+
this.sockets[namespace].disconnect()
|
|
69
|
+
delete this.sockets[namespace]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
dispose() {
|
|
74
|
+
const namespaces = Object.keys(this.sockets)
|
|
75
|
+
for (const namespace of namespaces) {
|
|
76
|
+
this.disconnectSocket(namespace)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async getAllTransactions(params) {
|
|
81
|
+
const transactions = { pending: [], confirmed: [] }
|
|
82
|
+
const cursor = await this.getTransactions({
|
|
83
|
+
...params,
|
|
84
|
+
onChunk: (isPending, chunk) => {
|
|
85
|
+
if (isPending) {
|
|
86
|
+
transactions.pending.push(...chunk)
|
|
87
|
+
} else {
|
|
88
|
+
transactions.confirmed.push(...chunk)
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
return { cursor, transactions }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async getTransactions({ walletAccount, address, cursor, onChunk }) {
|
|
96
|
+
const socket = this.connectTransactions({ walletAccount, address })
|
|
97
|
+
const listener = (isPending, chunk, callback) => {
|
|
98
|
+
onChunk(isPending, chunk)
|
|
99
|
+
callback()
|
|
100
|
+
}
|
|
101
|
+
socket.on('transactionsChunk', listener)
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
const timeout = setTimeout(() => reject(new Error('Transactions Timeout')), 300000)
|
|
104
|
+
socket.emit('getTransactions', cursor, (nextCursor) => {
|
|
105
|
+
clearTimeout(timeout)
|
|
106
|
+
resolve(nextCursor)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
.catch((error) => console.error(error))
|
|
110
|
+
.finally(() => socket.off('transactionsChunk', listener))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async getFee() {
|
|
114
|
+
const socket = this.connectFee()
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
const timeout = setTimeout(() => reject(new Error('Fee Timeout')), 30000)
|
|
117
|
+
socket.emit('getFee', (fee) => {
|
|
118
|
+
clearTimeout(timeout)
|
|
119
|
+
if (!fee) {
|
|
120
|
+
const error = new Error('Unable to get fee')
|
|
121
|
+
return reject(error)
|
|
122
|
+
}
|
|
123
|
+
resolve(fee)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async sendRpcRequest(rpcRequest) {
|
|
129
|
+
const rpcSocket = this.connectRpc()
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const timeout = setTimeout(() => reject(new Error('Rpc Timeout')), 30000)
|
|
132
|
+
rpcSocket.emit('request', rpcRequest, (response) => {
|
|
133
|
+
clearTimeout(timeout)
|
|
134
|
+
resolve(response)
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async sendBatchRequest(batch) {
|
|
140
|
+
const responses = await this.sendRpcRequest(batch)
|
|
141
|
+
const isValid = responses.every((response) => {
|
|
142
|
+
return !isNaN(response?.id) && response?.result
|
|
143
|
+
})
|
|
144
|
+
if (responses.length !== batch.length || !isValid) {
|
|
145
|
+
throw new Error('Bad Response')
|
|
146
|
+
}
|
|
147
|
+
const keyed = responses.reduce((acc, response) => {
|
|
148
|
+
return { ...acc, [`${response.id}`]: response.result }
|
|
149
|
+
}, {})
|
|
150
|
+
return batch.map((request) => keyed[`${request.id}`])
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async sendRequest(request) {
|
|
154
|
+
const response = await this.sendRpcRequest(request)
|
|
155
|
+
if (!response?.result) {
|
|
156
|
+
throw new Error('Bad Response')
|
|
157
|
+
}
|
|
158
|
+
return response.result
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async isContract(address) {
|
|
162
|
+
const code = await this.getCode(address)
|
|
163
|
+
return code.length > 2
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
buildRequest({ method, params = [] }) {
|
|
167
|
+
return { jsonrpc: '2.0', id: this.id++, method, params }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
balanceOfRequest(address, tokenAddress, tag = 'latest') {
|
|
171
|
+
const contract = SolidityContract.simpleErc20(tokenAddress)
|
|
172
|
+
const callData = contract.balanceOf.build(address)
|
|
173
|
+
const data = {
|
|
174
|
+
data: bufferToHex(callData),
|
|
175
|
+
to: tokenAddress,
|
|
176
|
+
}
|
|
177
|
+
return this.ethCallRequest(data, tag)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getBalanceRequest(address, tag = 'latest') {
|
|
181
|
+
return this.buildRequest({ method: 'eth_getBalance', params: [address, tag] })
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
gasPriceRequest() {
|
|
185
|
+
return this.buildRequest({ method: 'eth_gasPrice' })
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
estimateGasRequest(data, tag = 'latest') {
|
|
189
|
+
return this.buildRequest({ method: 'eth_estimateGas', params: [data, tag] })
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
sendRawTransactionRequest(data) {
|
|
193
|
+
const hex = data.startsWith('0x') ? data : '0x' + data
|
|
194
|
+
return this.buildRequest({ method: 'eth_sendRawTransaction', params: [hex] })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getCodeRequest(address, tag = 'latest') {
|
|
198
|
+
return this.buildRequest({ method: 'eth_getCode', params: [address, tag] })
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getTransactionCountRequest(address, tag = 'latest') {
|
|
202
|
+
return this.buildRequest({ method: 'eth_getTransactionCount', params: [address, tag] })
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
getTransactionByHashRequest(hash) {
|
|
206
|
+
return this.buildRequest({ method: 'eth_getTransactionByHash', params: [hash] })
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getTransactionReceiptRequest(txhash) {
|
|
210
|
+
return this.buildRequest({ method: 'eth_getTransactionReceipt', params: [txhash] })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
ethCallRequest(data, tag) {
|
|
214
|
+
return this.buildRequest({ method: 'eth_call', params: [data, tag] })
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
blockNumberRequest() {
|
|
218
|
+
return this.buildRequest({ method: 'eth_blockNumber' })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
getBlockByNumberRequest(numberHex, isFullTxs = false) {
|
|
222
|
+
return this.buildRequest({ method: 'eth_getBlockByNumber', params: [numberHex, isFullTxs] })
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
simulateRawTransactionRequest(rawTx, applyPending = true) {
|
|
226
|
+
const replaced = rawTx.replace('0x', '')
|
|
227
|
+
return this.buildRequest({
|
|
228
|
+
method: 'debug_simulateRawTransaction',
|
|
229
|
+
params: [replaced, applyPending],
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async balanceOf(...params) {
|
|
234
|
+
const request = this.balanceOfRequest(...params)
|
|
235
|
+
return this.sendRequest(request)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async getBalance(...params) {
|
|
239
|
+
const request = this.getBalanceRequest(...params)
|
|
240
|
+
return this.sendRequest(request)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async getBalanceProxied(...params) {
|
|
244
|
+
return this.getBalance(...params)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async gasPrice(...params) {
|
|
248
|
+
const request = this.gasPriceRequest(...params)
|
|
249
|
+
return this.sendRequest(request)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async estimateGas(...params) {
|
|
253
|
+
const request = this.estimateGasRequest(...params)
|
|
254
|
+
return this.sendRequest(request)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async sendRawTransaction(...params) {
|
|
258
|
+
const request = this.sendRawTransactionRequest(...params)
|
|
259
|
+
return this.sendRequest(request)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async getCode(...params) {
|
|
263
|
+
const request = this.getCodeRequest(...params)
|
|
264
|
+
return this.sendRequest(request)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async getTransactionCount(...params) {
|
|
268
|
+
const request = this.getTransactionCountRequest(...params)
|
|
269
|
+
return this.sendRequest(request)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async getTransactionByHash(...params) {
|
|
273
|
+
const request = this.getTransactionByHashRequest(...params)
|
|
274
|
+
return this.sendRequest(request)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async getTransactionReceipt(...params) {
|
|
278
|
+
const request = this.getTransactionReceiptRequest(...params)
|
|
279
|
+
return this.sendRequest(request)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async ethCall(...params) {
|
|
283
|
+
const request = this.ethCallRequest(...params)
|
|
284
|
+
return this.sendRequest(request)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async blockNumber(...params) {
|
|
288
|
+
const request = this.blockNumberRequest(...params)
|
|
289
|
+
return this.sendRequest(request)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async getBlockByNumber(...params) {
|
|
293
|
+
const request = this.getBlockByNumberRequest(...params)
|
|
294
|
+
return this.sendRequest(request)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async simulateRawTransaction(...params) {
|
|
298
|
+
const request = this.simulateRawTransactionRequest(...params)
|
|
299
|
+
return this.sendRequest(request)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
formatTransactionsNamespace(address) {
|
|
303
|
+
return `${this.baseNamespace}/addresses/${address}/transactions`
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
formatRpcNamespace() {
|
|
307
|
+
return `${this.baseNamespace}/rpc`
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
formatFeeNamespace() {
|
|
311
|
+
return `${this.baseNamespace}/fee`
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { DEFAULT_SERVER_URLS, ETHEREUM_LIKE_ASSETS } from '@exodus/ethereum-lib'
|
|
2
2
|
|
|
3
3
|
import { create } from './api'
|
|
4
|
+
import ClarityServer from './clarity'
|
|
4
5
|
|
|
5
6
|
const serverMap = Object.fromEntries(
|
|
6
7
|
ETHEREUM_LIKE_ASSETS.map((assetName) => [
|
|
7
8
|
assetName,
|
|
8
|
-
|
|
9
|
+
DEFAULT_SERVER_URLS[assetName].includes('clarity')
|
|
10
|
+
? new ClarityServer({ baseAssetName: assetName, uri: DEFAULT_SERVER_URLS[assetName] })
|
|
11
|
+
: create(DEFAULT_SERVER_URLS[assetName], assetName),
|
|
9
12
|
])
|
|
10
13
|
)
|
|
11
14
|
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import BN from 'bn.js'
|
|
2
|
+
import { BaseMonitor } from '@exodus/asset-lib'
|
|
3
|
+
import { getAssetAddresses, isRpcBalanceAsset } from '@exodus/ethereum-lib'
|
|
4
|
+
import { isEmpty } from 'lodash'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type PendingTransactionsDictionary,
|
|
8
|
+
getAllLogItemsByAsset,
|
|
9
|
+
checkPendingTransactions,
|
|
10
|
+
getDeriveTransactionsToCheck,
|
|
11
|
+
} from './monitor-utils'
|
|
12
|
+
import { getLogItemsFromServerTx, getDeriveDataNeededForTick, filterEffects } from './clarity-utils'
|
|
13
|
+
|
|
14
|
+
import { type Tx } from '@exodus/models'
|
|
15
|
+
|
|
16
|
+
type DerivedData = {
|
|
17
|
+
ourWalletAddress: string,
|
|
18
|
+
currentAccountState: Object,
|
|
19
|
+
unconfirmedTransactions: PendingTransactionsDictionary,
|
|
20
|
+
pendingTransactionsGroupedByAddressAndNonce: PendingTransactionsDictionary,
|
|
21
|
+
simulatedTransactions: any,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class ClarityMonitor extends BaseMonitor {
|
|
25
|
+
constructor({ server, config, ...args }) {
|
|
26
|
+
super(args)
|
|
27
|
+
this.config = { GAS_PRICE_FROM_WEBSOCKET: true, ...config }
|
|
28
|
+
this.server = server
|
|
29
|
+
this.getAllLogItemsByAsset = getAllLogItemsByAsset
|
|
30
|
+
this.deriveDataNeededForTick = getDeriveDataNeededForTick(this.aci)
|
|
31
|
+
this.deriveTransactionsToCheck = getDeriveTransactionsToCheck({
|
|
32
|
+
getTxLog: (...args) => this.aci.getTxLog(...args),
|
|
33
|
+
})
|
|
34
|
+
this.addHook('before-start', (...args) => this.beforeStart(...args))
|
|
35
|
+
this.addHook('after-stop', (...args) => this.afterStop(...args))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setServer(config) {
|
|
39
|
+
if (!config?.server || config.server === this.server.uri) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
this.server.setURI(config.server)
|
|
43
|
+
this.subscribeWalletAddresses()
|
|
44
|
+
if (this.config.GAS_PRICE_FROM_WEBSOCKET) {
|
|
45
|
+
this.server.connectFee()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async deriveData({ assetName, walletAccount, tokens }: Object): DerivedData {
|
|
50
|
+
const { ourWalletAddress, currentAccountState } = await this.deriveDataNeededForTick({
|
|
51
|
+
assetName,
|
|
52
|
+
walletAccount,
|
|
53
|
+
})
|
|
54
|
+
const transactionsToCheck = await this.deriveTransactionsToCheck({
|
|
55
|
+
assetName,
|
|
56
|
+
walletAccount,
|
|
57
|
+
tokens,
|
|
58
|
+
ourWalletAddress,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
ourWalletAddress,
|
|
63
|
+
currentAccountState,
|
|
64
|
+
...transactionsToCheck,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// eslint-disable-next-line no-undef
|
|
69
|
+
async checkPendingTransactions(params): { txsToRemove: { tx: Tx, assetSource: AssetSource }[] } {
|
|
70
|
+
const {
|
|
71
|
+
pendingTransactionsToCheck,
|
|
72
|
+
pendingTransactionsGroupedByAddressAndNonce,
|
|
73
|
+
} = checkPendingTransactions(params)
|
|
74
|
+
const txsToRemove = []
|
|
75
|
+
const { walletAccount } = params
|
|
76
|
+
|
|
77
|
+
const updateTx = (tx, asset, { error, remove }) => {
|
|
78
|
+
if (remove) {
|
|
79
|
+
txsToRemove.push({ tx, assetSource: { asset, walletAccount } })
|
|
80
|
+
} else {
|
|
81
|
+
params.logItemsByAsset[asset].push({
|
|
82
|
+
...tx,
|
|
83
|
+
dropped: true,
|
|
84
|
+
error,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// in case this is an ETH fee tx that has associated ERC20 send txs
|
|
89
|
+
const promises = tx.tokens.map(async (assetName) => {
|
|
90
|
+
const tokenTxSet = await this.aci.getTxLog({ assetName, walletAccount })
|
|
91
|
+
if (remove) {
|
|
92
|
+
txsToRemove.push({
|
|
93
|
+
tx: tokenTxSet.get(tx.txId),
|
|
94
|
+
assetSource: { asset: assetName, walletAccount },
|
|
95
|
+
})
|
|
96
|
+
} else if (tokenTxSet && tokenTxSet.has(tx.txId)) {
|
|
97
|
+
params.logItemsByAsset[assetName].push({
|
|
98
|
+
...tokenTxSet.get(tx.txId),
|
|
99
|
+
error,
|
|
100
|
+
dropped: true,
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
return Promise.all(promises)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const { tx, assetName, replaced = false } of Object.values(
|
|
108
|
+
pendingTransactionsGroupedByAddressAndNonce
|
|
109
|
+
)) {
|
|
110
|
+
if (replaced) {
|
|
111
|
+
await updateTx(tx, assetName, { remove: true })
|
|
112
|
+
delete pendingTransactionsToCheck[tx.txId]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const { tx, assetName } of Object.values(pendingTransactionsToCheck)) {
|
|
117
|
+
if (params.refresh) await updateTx(tx, assetName, { remove: true })
|
|
118
|
+
else await updateTx(tx, assetName, { error: 'Dropped' })
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { txsToRemove }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async tick({ walletAccount, refresh }) {
|
|
125
|
+
await this.subscribeWalletAddresses()
|
|
126
|
+
|
|
127
|
+
const assets = await this.aci.getAssetsForNetwork({ baseAssetName: this.asset.name })
|
|
128
|
+
const tokens = Object.values(assets).filter((asset) => asset.baseAsset.name !== asset.name)
|
|
129
|
+
const tokensByAddress = tokens.reduce((map, token) => {
|
|
130
|
+
const addresses = getAssetAddresses(token)
|
|
131
|
+
for (const address of addresses) map.set(address.toLowerCase(), token)
|
|
132
|
+
return map
|
|
133
|
+
}, new Map())
|
|
134
|
+
const assetName = this.asset.name
|
|
135
|
+
|
|
136
|
+
const derivedData = await this.deriveData({ assetName, walletAccount, tokens })
|
|
137
|
+
const response = await this.getHistoryFromServer({ walletAccount, derivedData, refresh })
|
|
138
|
+
const allTxs = [...response.transactions.pending, ...response.transactions.confirmed]
|
|
139
|
+
const hasNewTxs = allTxs.length > 0
|
|
140
|
+
|
|
141
|
+
const logItemsByAsset = this.getAllLogItemsByAsset({
|
|
142
|
+
getLogItemsFromServerTx,
|
|
143
|
+
ourWalletAddress: derivedData.ourWalletAddress,
|
|
144
|
+
allTransactionsFromServer: allTxs,
|
|
145
|
+
asset: this.asset,
|
|
146
|
+
tokensByAddress,
|
|
147
|
+
assets,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const { txsToRemove } = await this.checkPendingTransactions({
|
|
151
|
+
txlist: allTxs,
|
|
152
|
+
walletAccount,
|
|
153
|
+
refresh,
|
|
154
|
+
logItemsByAsset,
|
|
155
|
+
asset: this.asset,
|
|
156
|
+
...derivedData,
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const accountState = await this.getNewAccountState({
|
|
160
|
+
tokens,
|
|
161
|
+
ourWalletAddress: derivedData.ourWalletAddress,
|
|
162
|
+
})
|
|
163
|
+
await this.updateAccountState({
|
|
164
|
+
walletAccount,
|
|
165
|
+
newData: { clarityCursor: response.cursor, ...accountState },
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
await this.removeFromTxLog(txsToRemove)
|
|
169
|
+
await this.updateTxLogByAsset({ logItemsByAsset, walletAccount, refresh })
|
|
170
|
+
if (refresh || hasNewTxs) {
|
|
171
|
+
const unknownTokenAddresses = this._getUnknownTokenAddresses({
|
|
172
|
+
transactions: allTxs,
|
|
173
|
+
tokensByAddress,
|
|
174
|
+
})
|
|
175
|
+
if (unknownTokenAddresses.length > 0) {
|
|
176
|
+
this.emit('unknown-tokens', unknownTokenAddresses)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async deriveDataNeededForTick({ assetName, walletAccount }) {
|
|
182
|
+
const receiveAddress = await this.aci.getReceiveAddress({ assetName, walletAccount })
|
|
183
|
+
const currentAccountState = await this.aci.getAccountState({ assetName, walletAccount })
|
|
184
|
+
return {
|
|
185
|
+
ourWalletAddress: receiveAddress.toLowerCase(),
|
|
186
|
+
currentAccountState,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async getNewAccountState({ tokens, ourWalletAddress }) {
|
|
191
|
+
const asset = this.asset
|
|
192
|
+
const newAccountState = {}
|
|
193
|
+
const balances = await this.getBalances({ tokens, ourWalletAddress })
|
|
194
|
+
if (isRpcBalanceAsset(asset)) {
|
|
195
|
+
const balance = balances[asset.name]
|
|
196
|
+
newAccountState.balance = asset.currency.baseUnit(balance)
|
|
197
|
+
}
|
|
198
|
+
const tokenBalancePairs = Object.entries(balances).filter((entry) => entry[0] !== asset.name)
|
|
199
|
+
const entries = tokenBalancePairs
|
|
200
|
+
.map((pair) => {
|
|
201
|
+
const token = tokens.find((token) => token.name === pair[0])
|
|
202
|
+
const value = token.currency.baseUnit(pair[1] || 0)
|
|
203
|
+
return value.isZero ? null : [token.name, value]
|
|
204
|
+
})
|
|
205
|
+
.filter((pair) => pair)
|
|
206
|
+
const tokenBalances = Object.fromEntries(entries)
|
|
207
|
+
if (!isEmpty(tokenBalances)) newAccountState.tokenBalances = tokenBalances
|
|
208
|
+
return newAccountState
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async getReceiveAddressesByWalletAccount() {
|
|
212
|
+
const walletAccounts = await this.aci.getWalletAccounts({ assetName: this.asset.name })
|
|
213
|
+
const addressesByAccount = {}
|
|
214
|
+
for (const walletAccount of walletAccounts) {
|
|
215
|
+
addressesByAccount[walletAccount] = await this.aci.getReceiveAddresses({
|
|
216
|
+
assetName: this.asset.name,
|
|
217
|
+
walletAccount,
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
return addressesByAccount
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async subscribeWalletAddresses() {
|
|
224
|
+
const addressesByWalletAccount = await this.getReceiveAddressesByWalletAccount()
|
|
225
|
+
Object.entries(addressesByWalletAccount).forEach(([walletAccount, addresses]) => {
|
|
226
|
+
const address = String(Array.from(addresses)[0]).toLowerCase() // Only check m/0/0
|
|
227
|
+
this.server.connectTransactions({ walletAccount, address })
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getBalances({ tokens, ourWalletAddress }) {
|
|
232
|
+
const batch = {}
|
|
233
|
+
if (isRpcBalanceAsset(this.asset)) {
|
|
234
|
+
const request = this.server.getBalanceRequest(ourWalletAddress)
|
|
235
|
+
batch[this.asset.name] = request
|
|
236
|
+
}
|
|
237
|
+
for (const token of tokens) {
|
|
238
|
+
if (isRpcBalanceAsset(token) && token.contract.address) {
|
|
239
|
+
const request = this.server.balanceOfRequest(ourWalletAddress, token.contract.address)
|
|
240
|
+
batch[token.name] = request
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const pairs = Object.entries(batch)
|
|
244
|
+
if (!pairs.length) {
|
|
245
|
+
return {}
|
|
246
|
+
}
|
|
247
|
+
const requests = pairs.map((pair) => pair[1])
|
|
248
|
+
const responses = await this.server.sendBatchRequest(requests)
|
|
249
|
+
const entries = pairs.map((pair, idx) => {
|
|
250
|
+
const balanceHex = responses[idx]
|
|
251
|
+
const name = pair[0]
|
|
252
|
+
const hex = balanceHex.startsWith('0x') ? balanceHex.slice(2) : balanceHex
|
|
253
|
+
const balance = new BN(hex, 'hex').toString()
|
|
254
|
+
return [name, balance]
|
|
255
|
+
})
|
|
256
|
+
return Object.fromEntries(entries)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_getUnknownTokenAddresses({ transactions, tokensByAddress }) {
|
|
260
|
+
const set = transactions.reduce((acc, txn) => {
|
|
261
|
+
const transfers = filterEffects(txn.effects, 'erc20') || []
|
|
262
|
+
transfers.forEach((transfer) => {
|
|
263
|
+
const addr = transfer.address.toLowerCase()
|
|
264
|
+
if (!tokensByAddress.has(addr)) {
|
|
265
|
+
acc.add(addr)
|
|
266
|
+
}
|
|
267
|
+
}, acc)
|
|
268
|
+
return acc
|
|
269
|
+
}, new Set())
|
|
270
|
+
return Array.from(set)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async updateGasPrice({ gasPrice, baseFeePerGas }) {
|
|
274
|
+
try {
|
|
275
|
+
const gasPriceHex = gasPrice?.startsWith('0x') ? gasPrice.slice(2) : gasPrice
|
|
276
|
+
const feeConfig = { gasPrice: `${new BN(gasPriceHex, 'hex').toString()} wei` }
|
|
277
|
+
if (baseFeePerGas) {
|
|
278
|
+
const baseFeePerGasHex = baseFeePerGas?.startsWith('0x')
|
|
279
|
+
? baseFeePerGas.slice(2)
|
|
280
|
+
: baseFeePerGas
|
|
281
|
+
feeConfig.baseFeePerGas = `${new BN(baseFeePerGasHex, 'hex').toString()} wei`
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.logger.debug(
|
|
285
|
+
`Update ${this.asset.name} gas price: ${feeConfig.gasPrice}, baseFeePerGas: ${feeConfig.baseFeePerGas}`
|
|
286
|
+
)
|
|
287
|
+
await this.aci.updateFeeConfig({ assetName: this.asset.name, feeConfig })
|
|
288
|
+
} catch (e) {
|
|
289
|
+
this.logger.warn('error updating gasPrice', e)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async onTransaction({ walletAccount }) {
|
|
294
|
+
return this.tick({ walletAccount })
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async onFeeUpdated(fee) {
|
|
298
|
+
return this.updateGasPrice(fee)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async beforeStart() {
|
|
302
|
+
this.listenToServerEvents()
|
|
303
|
+
if (this.config.GAS_PRICE_FROM_WEBSOCKET) {
|
|
304
|
+
this.server.connectFee()
|
|
305
|
+
}
|
|
306
|
+
return this.subscribeWalletAddresses()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async afterStop() {
|
|
310
|
+
this.server.dispose()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async getHistoryFromServer({ walletAccount, derivedData, refresh }) {
|
|
314
|
+
const address = derivedData.ourWalletAddress
|
|
315
|
+
const currentCursor = derivedData.currentAccountState?.clarityCursor
|
|
316
|
+
const cursor = currentCursor && !refresh ? currentCursor : null
|
|
317
|
+
return this.server.getAllTransactions({ walletAccount, address, cursor })
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
listenToServerEvents() {
|
|
321
|
+
this.server.on('transaction', (...args) => this.onTransaction(...args))
|
|
322
|
+
this.server.on('feeUpdated', (...args) => this.onFeeUpdated(...args))
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// A super-selector that returns all the current data needed for a tick of the ETH monitor.
|
|
2
|
+
|
|
3
|
+
export default function getDeriveDataNeededForTick(aci) {
|
|
4
|
+
return async function({ assetName, walletAccount }) {
|
|
5
|
+
const receiveAddress = await aci.getReceiveAddress({ assetName, walletAccount })
|
|
6
|
+
const currentAccountState = await aci.getAccountState({ assetName, walletAccount })
|
|
7
|
+
return {
|
|
8
|
+
ourWalletAddress: receiveAddress.toLowerCase(),
|
|
9
|
+
currentAccountState,
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import getValueOfTransfers from '../monitor-utils/get-value-of-transfers'
|
|
2
|
+
import getTransfersByTokenName from '../monitor-utils/get-transfers-by-token-name'
|
|
3
|
+
import getFeeAmount from '../monitor-utils/get-fee-amount'
|
|
4
|
+
import isConsideredSentTokenTx from '../monitor-utils/is-considered-sent-token-tx'
|
|
5
|
+
import isConfirmedServerTx from '../monitor-utils/is-confirmed-server-tx'
|
|
6
|
+
import getNamesOfTokensTransferredByServerTx from './get-names-of-tokens-transferred-by-server-tx'
|
|
7
|
+
import filterEffects from './filter-effects'
|
|
8
|
+
import lodash from 'lodash'
|
|
9
|
+
|
|
10
|
+
// This function takes a server transaction object fetched from magnifier,
|
|
11
|
+
// and transforms it into Tx models to update the exodus state.
|
|
12
|
+
|
|
13
|
+
export default function getLogItemsFromServerTx({
|
|
14
|
+
serverTx,
|
|
15
|
+
asset,
|
|
16
|
+
ourWalletAddress,
|
|
17
|
+
tokensByAddress,
|
|
18
|
+
assets,
|
|
19
|
+
}) {
|
|
20
|
+
const confirmations = isConfirmedServerTx(serverTx) ? 1 : 0
|
|
21
|
+
const date = parseServerTxDate(serverTx.timestamp) // included even for unconfirmed txs
|
|
22
|
+
const txId = serverTx.hash
|
|
23
|
+
const nonce = parseInt(serverTx.nonce, 16)
|
|
24
|
+
const gasLimit = parseInt(serverTx.gas, 16)
|
|
25
|
+
const error = serverTx.error || (serverTx.status === '0' ? 'Failed' : null)
|
|
26
|
+
const feeAmount = getFeeAmount(asset, serverTx)
|
|
27
|
+
const internalTransfers = filterEffects(serverTx.effects, 'internal') || []
|
|
28
|
+
const ethereumTransfers = [serverTx, ...internalTransfers]
|
|
29
|
+
const erc20Transfers = filterEffects(serverTx.effects, 'erc20') || []
|
|
30
|
+
const tokenTransfersByTokenName = getTransfersByTokenName(erc20Transfers, tokensByAddress)
|
|
31
|
+
const toAddress = tryFindExternalRecipient(ethereumTransfers, ourWalletAddress)
|
|
32
|
+
const ourWalletWasSender = serverTx.from === ourWalletAddress
|
|
33
|
+
|
|
34
|
+
const logItemCommonProperties = {
|
|
35
|
+
confirmations,
|
|
36
|
+
date,
|
|
37
|
+
error,
|
|
38
|
+
txId,
|
|
39
|
+
dropped: false,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const logItemsForServerTxEntries = []
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
const sendingTransferPresent = ethereumTransfers.some(({ from }) => from === ourWalletAddress)
|
|
46
|
+
const receivingTransferPresent = ethereumTransfers.some(({ to }) => to === ourWalletAddress)
|
|
47
|
+
|
|
48
|
+
if (sendingTransferPresent || receivingTransferPresent) {
|
|
49
|
+
const coinAmount = getValueOfTransfers(ourWalletAddress, asset, ethereumTransfers)
|
|
50
|
+
const selfSend = isSelfSendTx({
|
|
51
|
+
coinAmount,
|
|
52
|
+
ourWalletWasSender,
|
|
53
|
+
sendingTransferPresent,
|
|
54
|
+
receivingTransferPresent,
|
|
55
|
+
})
|
|
56
|
+
logItemsForServerTxEntries.push([
|
|
57
|
+
asset.name,
|
|
58
|
+
{
|
|
59
|
+
...logItemCommonProperties,
|
|
60
|
+
coinAmount,
|
|
61
|
+
coinName: asset.name,
|
|
62
|
+
data: {
|
|
63
|
+
data: serverTx.data || '0x',
|
|
64
|
+
nonce,
|
|
65
|
+
gasLimit,
|
|
66
|
+
},
|
|
67
|
+
from: ourWalletWasSender ? [] : [serverTx.from],
|
|
68
|
+
to: ourWalletWasSender ? toAddress : undefined,
|
|
69
|
+
feeAmount: ourWalletWasSender ? feeAmount : undefined,
|
|
70
|
+
selfSend,
|
|
71
|
+
tokens: getNamesOfTokensTransferredByServerTx({
|
|
72
|
+
asset,
|
|
73
|
+
tokensByAddress,
|
|
74
|
+
ourWalletAddress,
|
|
75
|
+
serverTx,
|
|
76
|
+
internalTransfers,
|
|
77
|
+
erc20Transfers,
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
])
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// handle erc20
|
|
84
|
+
Object.entries(tokenTransfersByTokenName).forEach(([tokenName, tokenTransfers]) => {
|
|
85
|
+
const sendingTransferPresent = tokenTransfers.some(({ from }) => from === ourWalletAddress)
|
|
86
|
+
const receivingTransferPresent = tokenTransfers.some(({ to }) => to === ourWalletAddress)
|
|
87
|
+
if (!sendingTransferPresent && !receivingTransferPresent) return
|
|
88
|
+
|
|
89
|
+
const token = assets[tokenName]
|
|
90
|
+
const tokenTransferToAddress = tryFindExternalRecipient(tokenTransfers, ourWalletAddress)
|
|
91
|
+
|
|
92
|
+
const coinAmount = getValueOfTransfers(ourWalletAddress, token, tokenTransfers)
|
|
93
|
+
const tokenFromAddresses = lodash.uniq(
|
|
94
|
+
tokenTransfers.filter(({ to }) => to === ourWalletAddress).map(({ from }) => from)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const isConsideredSent = isConsideredSentTokenTx({
|
|
98
|
+
coinAmount,
|
|
99
|
+
ourWalletWasSender,
|
|
100
|
+
sendingTransferPresent,
|
|
101
|
+
receivingTransferPresent,
|
|
102
|
+
})
|
|
103
|
+
const selfSend = isSelfSendTx({
|
|
104
|
+
coinAmount,
|
|
105
|
+
ourWalletWasSender,
|
|
106
|
+
sendingTransferPresent,
|
|
107
|
+
receivingTransferPresent,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
logItemsForServerTxEntries.push([
|
|
111
|
+
tokenName,
|
|
112
|
+
{
|
|
113
|
+
...logItemCommonProperties,
|
|
114
|
+
coinAmount,
|
|
115
|
+
coinName: tokenName,
|
|
116
|
+
data: { nonce, gasLimit },
|
|
117
|
+
from: isConsideredSent ? [] : tokenFromAddresses,
|
|
118
|
+
to: isConsideredSent ? tokenTransferToAddress : undefined,
|
|
119
|
+
feeAmount: isConsideredSent ? feeAmount : undefined,
|
|
120
|
+
selfSend,
|
|
121
|
+
},
|
|
122
|
+
])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return Object.fromEntries(logItemsForServerTxEntries)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const tryFindExternalRecipient = (transfers, ourWalletAddress) =>
|
|
129
|
+
transfers.find(({ to }) => to !== ourWalletAddress)?.to || ourWalletAddress
|
|
130
|
+
|
|
131
|
+
// If the timestamp is in the future, use the current time.
|
|
132
|
+
const parseServerTxDate = (timestamp) => new Date(Math.min(Date.now(), parseInt(timestamp, 16)))
|
|
133
|
+
|
|
134
|
+
function isSelfSendTx({
|
|
135
|
+
coinAmount,
|
|
136
|
+
ourWalletWasSender,
|
|
137
|
+
sendingTransferPresent,
|
|
138
|
+
receivingTransferPresent,
|
|
139
|
+
}) {
|
|
140
|
+
return (
|
|
141
|
+
coinAmount.isZero && sendingTransferPresent && receivingTransferPresent && ourWalletWasSender
|
|
142
|
+
)
|
|
143
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import lodash from 'lodash'
|
|
2
|
+
import getValueOfTransfers from '../monitor-utils/get-value-of-transfers'
|
|
3
|
+
import { FEE_PAYMENT_PREFIX } from '@exodus/ethereum-lib'
|
|
4
|
+
|
|
5
|
+
// For ETH transactions, we store an array of token names inside
|
|
6
|
+
// the TX model. This array is used to display text in the UI for
|
|
7
|
+
// transactions which pay fees for ERC20 token transfers. This
|
|
8
|
+
// function is used to determine that array, choosing which
|
|
9
|
+
// tokens a transaction ostensibly 'paid fees' for.
|
|
10
|
+
|
|
11
|
+
export default function getNamesOfTokensTransferredByServerTx({
|
|
12
|
+
asset,
|
|
13
|
+
tokensByAddress,
|
|
14
|
+
ourWalletAddress,
|
|
15
|
+
serverTx,
|
|
16
|
+
internalTransfers,
|
|
17
|
+
erc20Transfers,
|
|
18
|
+
}) {
|
|
19
|
+
// Treat smart contract ETH transfer transactions as ETH transfers, not token transfers
|
|
20
|
+
if (!getValueOfTransfers(ourWalletAddress, asset, internalTransfers).isZero) {
|
|
21
|
+
return []
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const tokenAddresses = erc20Transfers.map((event) => event.address)
|
|
25
|
+
if (
|
|
26
|
+
tokenAddresses.length === 0 &&
|
|
27
|
+
serverTx.data &&
|
|
28
|
+
serverTx.data !== '0x' &&
|
|
29
|
+
!serverTx.data.startsWith(FEE_PAYMENT_PREFIX)
|
|
30
|
+
) {
|
|
31
|
+
// We may still be transacting with tokens even though we found no `tokenAddresses`. This includes
|
|
32
|
+
// failed token transactions as well as other transactions sent to a token contract. If we do not
|
|
33
|
+
// recognize the `to` address in the next step, then we will not treat it as a token contract address.
|
|
34
|
+
tokenAddresses.push(serverTx.to)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return lodash
|
|
38
|
+
.uniq(tokenAddresses)
|
|
39
|
+
.map((address) => tokensByAddress.get(address)?.name)
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type PendingTransactionsDictionary,
|
|
8
8
|
checkPendingTransactions,
|
|
9
9
|
getAllLogItemsByAsset,
|
|
10
|
+
getLogItemsFromServerTx,
|
|
10
11
|
getDeriveDataNeededForTick,
|
|
11
12
|
getDeriveTransactionsToCheck,
|
|
12
13
|
getHistoryFromServer,
|
|
@@ -165,6 +166,7 @@ export class EthereumMonitor extends BaseMonitor {
|
|
|
165
166
|
const hasNewIndex = !derivedIndex || index > derivedIndex
|
|
166
167
|
|
|
167
168
|
const logItemsByAsset = getAllLogItemsByAsset({
|
|
169
|
+
getLogItemsFromServerTx,
|
|
168
170
|
ourWalletAddress: derivedData.ourWalletAddress,
|
|
169
171
|
allTransactionsFromServer,
|
|
170
172
|
asset: this.asset,
|
package/src/tx-log/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import getLogItemsFromServerTx from './get-log-items-from-server-tx'
|
|
2
|
-
|
|
3
1
|
// Iterates through an array of server transactions and formats them into log item
|
|
4
2
|
// objects (in Tx class fields format). It returns an object of arrays of log
|
|
5
3
|
// items, keyed by asset name.
|
|
6
4
|
|
|
7
5
|
export default function getAllLogItemsByAsset({
|
|
6
|
+
getLogItemsFromServerTx,
|
|
8
7
|
allTransactionsFromServer,
|
|
9
8
|
ourWalletAddress,
|
|
10
9
|
asset,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as getDeriveDataNeededForTick } from './get-derive-data-needed-for-tick'
|
|
2
2
|
export { default as getAllLogItemsByAsset } from './get-all-log-items-by-asset'
|
|
3
|
+
export { default as getLogItemsFromServerTx } from './get-log-items-from-server-tx'
|
|
3
4
|
export { default as getHistoryFromServer } from './get-history-from-server'
|
|
4
5
|
export { default as checkPendingTransactions } from './check-pending-transactions'
|
|
5
6
|
export { default as getDeriveTransactionsToCheck } from './get-derive-transactions-to-check'
|