@exodus/ethereum-api 8.76.2 → 8.76.3
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/CHANGELOG.md +8 -0
- package/package.json +2 -2
- package/src/create-asset-utils.js +4 -14
- package/src/tx-log/clarity-monitor-v2.js +0 -637
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [8.76.3](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.76.2...@exodus/ethereum-api@8.76.3) (2026-06-01)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @exodus/ethereum-api
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
## [8.76.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.76.1...@exodus/ethereum-api@8.76.2) (2026-05-27)
|
|
7
15
|
|
|
8
16
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.76.
|
|
3
|
+
"version": "8.76.3",
|
|
4
4
|
"description": "Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Ethereum and EVM-based blockchains",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"type": "git",
|
|
71
71
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "aac7087b928f14da94a1a071b5abaf0983136c2a"
|
|
74
74
|
}
|
|
@@ -5,10 +5,9 @@ import assert from 'minimalistic-assert'
|
|
|
5
5
|
import ms from 'ms'
|
|
6
6
|
|
|
7
7
|
import { EVM_ERROR_REASONS, withErrorReason } from './error-wrapper.js'
|
|
8
|
-
import { createEvmServer,
|
|
8
|
+
import { createEvmServer, ValidMonitorTypes } from './exodus-eth-server/index.js'
|
|
9
9
|
import { createEthereumHooks } from './hooks/index.js'
|
|
10
10
|
import { ClarityMonitor } from './tx-log/clarity-monitor.js'
|
|
11
|
-
import { ClarityMonitorV2 } from './tx-log/clarity-monitor-v2.js'
|
|
12
11
|
import { ClarityTruncatedHistoryMonitor } from './tx-log/clarity-truncated-history-monitor.js'
|
|
13
12
|
import { EthereumMonitor } from './tx-log/ethereum-monitor.js'
|
|
14
13
|
import { EthereumNoHistoryMonitor } from './tx-log/ethereum-no-history-monitor.js'
|
|
@@ -284,6 +283,9 @@ export const createHistoryMonitorFactory = ({
|
|
|
284
283
|
return (args) => {
|
|
285
284
|
let monitor
|
|
286
285
|
switch (monitorType) {
|
|
286
|
+
case 'clarity-v3':
|
|
287
|
+
console.log('clarity-v3 is no longer supported, falling back to clarity-v2')
|
|
288
|
+
// eslint-disable-next-line no-fallthrough
|
|
287
289
|
case 'clarity':
|
|
288
290
|
case 'clarity-v2':
|
|
289
291
|
monitor = new ClarityMonitor({
|
|
@@ -306,18 +308,6 @@ export const createHistoryMonitorFactory = ({
|
|
|
306
308
|
...args,
|
|
307
309
|
})
|
|
308
310
|
break
|
|
309
|
-
case 'clarity-v3':
|
|
310
|
-
monitor = new ClarityMonitorV2({
|
|
311
|
-
assetClientInterface,
|
|
312
|
-
interval: ms(monitorInterval || '5m'),
|
|
313
|
-
server,
|
|
314
|
-
rpcBalanceAssetNames,
|
|
315
|
-
wsGatewayClient: createWsGateway({ uri: wsGatewayUri }),
|
|
316
|
-
eip7702Supported,
|
|
317
|
-
getBlackListStatus,
|
|
318
|
-
...args,
|
|
319
|
-
})
|
|
320
|
-
break
|
|
321
311
|
case 'no-history':
|
|
322
312
|
monitor = new EthereumNoHistoryMonitor({
|
|
323
313
|
assetClientInterface,
|
|
@@ -1,637 +0,0 @@
|
|
|
1
|
-
import { BaseMonitor } from '@exodus/asset-lib'
|
|
2
|
-
import { getAssetAddresses } from '@exodus/ethereum-lib'
|
|
3
|
-
import lodash from 'lodash'
|
|
4
|
-
import assert from 'minimalistic-assert'
|
|
5
|
-
|
|
6
|
-
import WsGateway from '../exodus-eth-server/ws-gateway.js'
|
|
7
|
-
import { executeEthLikeFeeMonitorUpdate } from '../fee-utils.js'
|
|
8
|
-
import { fromHexToString } from '../number-utils.js'
|
|
9
|
-
import {
|
|
10
|
-
filterEffects,
|
|
11
|
-
getLogItemsFromServerTx,
|
|
12
|
-
normalizeTransactionsResponse,
|
|
13
|
-
} from './clarity-utils/index.js'
|
|
14
|
-
import {
|
|
15
|
-
checkPendingTransactions,
|
|
16
|
-
excludeUnchangedTokenBalances,
|
|
17
|
-
getAllLogItemsByAsset,
|
|
18
|
-
getCurrentBlackListStatus,
|
|
19
|
-
getCurrentEIP7702Delegation,
|
|
20
|
-
getDeriveDataNeededForTick,
|
|
21
|
-
getDeriveTransactionsToCheck,
|
|
22
|
-
verifyRpcPendingTxStatusBatch,
|
|
23
|
-
} from './monitor-utils/index.js'
|
|
24
|
-
|
|
25
|
-
const { isEmpty } = lodash
|
|
26
|
-
|
|
27
|
-
export class ClarityMonitorV2 extends BaseMonitor {
|
|
28
|
-
#wsClient = null
|
|
29
|
-
#walletAccountByAddress = new Map()
|
|
30
|
-
#walletAccountInfo = new Map()
|
|
31
|
-
#rpcBalanceAssetNames = []
|
|
32
|
-
constructor({
|
|
33
|
-
server,
|
|
34
|
-
wsGatewayClient,
|
|
35
|
-
rpcBalanceAssetNames,
|
|
36
|
-
eip7702Supported,
|
|
37
|
-
getBlackListStatus,
|
|
38
|
-
config,
|
|
39
|
-
...args
|
|
40
|
-
} = {}) {
|
|
41
|
-
super(args)
|
|
42
|
-
assert(wsGatewayClient instanceof WsGateway, 'expected WsGateway wsGatewayClient')
|
|
43
|
-
|
|
44
|
-
this.config = { GAS_PRICE_FROM_WEBSOCKET: true, ...config }
|
|
45
|
-
this.server = server
|
|
46
|
-
this.#wsClient = wsGatewayClient
|
|
47
|
-
this.#rpcBalanceAssetNames = rpcBalanceAssetNames
|
|
48
|
-
this.eip7702Supported = eip7702Supported
|
|
49
|
-
this.getBlackListStatus = getBlackListStatus
|
|
50
|
-
this.getAllLogItemsByAsset = getAllLogItemsByAsset
|
|
51
|
-
this.deriveDataNeededForTick = getDeriveDataNeededForTick(this.aci)
|
|
52
|
-
this.deriveTransactionsToCheck = getDeriveTransactionsToCheck({
|
|
53
|
-
getTxLog: (...args) => this.aci.getTxLog(...args),
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
this.addHook('before-start', (...args) => this.beforeStart(...args))
|
|
57
|
-
this.addHook('after-stop', (...args) => this.afterStop(...args))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
setServer(config) {
|
|
61
|
-
const uri = config?.server || this.server.defaultUri
|
|
62
|
-
|
|
63
|
-
this.#wsClient.on('connected', () => this.subscribeAllWalletAccounts())
|
|
64
|
-
this.#wsClient.start()
|
|
65
|
-
|
|
66
|
-
if (uri === this.server.uri) {
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this.server.setURI(uri)
|
|
71
|
-
if (this.config.GAS_PRICE_FROM_WEBSOCKET) {
|
|
72
|
-
this.server.connectFee()
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async deriveData({ assetName, walletAccount, tokens }) {
|
|
77
|
-
const { ourWalletAddress, currentAccountState } = await this.deriveDataNeededForTick({
|
|
78
|
-
assetName,
|
|
79
|
-
walletAccount,
|
|
80
|
-
})
|
|
81
|
-
const transactionsToCheck = await this.deriveTransactionsToCheck({
|
|
82
|
-
assetName,
|
|
83
|
-
walletAccount,
|
|
84
|
-
tokens,
|
|
85
|
-
ourWalletAddress,
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
ourWalletAddress,
|
|
90
|
-
currentAccountState,
|
|
91
|
-
...transactionsToCheck,
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// eslint-disable-next-line no-undef
|
|
96
|
-
async checkPendingTransactions(params) {
|
|
97
|
-
const { pendingTransactionsToCheck, pendingTransactionsGroupedByAddressAndNonce } =
|
|
98
|
-
checkPendingTransactions(params)
|
|
99
|
-
const txsToRemove = []
|
|
100
|
-
const { walletAccount } = params
|
|
101
|
-
|
|
102
|
-
const updateTx = (tx, asset, { error, remove }) => {
|
|
103
|
-
if (remove) {
|
|
104
|
-
txsToRemove.push({ tx, assetSource: { asset, walletAccount } })
|
|
105
|
-
} else {
|
|
106
|
-
params.logItemsByAsset[asset].push({
|
|
107
|
-
...tx,
|
|
108
|
-
dropped: true,
|
|
109
|
-
error,
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// in case this is an ETH fee tx that has associated ERC20 send txs
|
|
114
|
-
const promises = tx.tokens.map(async (assetName) => {
|
|
115
|
-
const tokenTxSet = await this.aci.getTxLog({ assetName, walletAccount })
|
|
116
|
-
if (remove) {
|
|
117
|
-
txsToRemove.push({
|
|
118
|
-
tx: tokenTxSet.get(tx.txId),
|
|
119
|
-
assetSource: { asset: assetName, walletAccount },
|
|
120
|
-
})
|
|
121
|
-
} else if (tokenTxSet && tokenTxSet.has(tx.txId)) {
|
|
122
|
-
params.logItemsByAsset[assetName].push({
|
|
123
|
-
...tokenTxSet.get(tx.txId),
|
|
124
|
-
error,
|
|
125
|
-
dropped: true,
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
return Promise.all(promises)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
for (const { tx, assetName, replaced = false } of Object.values(
|
|
133
|
-
pendingTransactionsGroupedByAddressAndNonce
|
|
134
|
-
)) {
|
|
135
|
-
if (replaced) {
|
|
136
|
-
await updateTx(tx, assetName, { remove: true })
|
|
137
|
-
delete pendingTransactionsToCheck[tx.txId]
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Batch verify all pending txs with a single RPC call (skip if refresh)
|
|
142
|
-
const txIds = Object.keys(pendingTransactionsToCheck)
|
|
143
|
-
const statuses = params.refresh
|
|
144
|
-
? {}
|
|
145
|
-
: await verifyRpcPendingTxStatusBatch({
|
|
146
|
-
server: this.server,
|
|
147
|
-
logger: this.logger,
|
|
148
|
-
txIds,
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
for (const { tx, assetName } of Object.values(pendingTransactionsToCheck)) {
|
|
152
|
-
if (params.refresh) {
|
|
153
|
-
await updateTx(tx, assetName, { remove: true })
|
|
154
|
-
} else {
|
|
155
|
-
const txStatus = statuses[tx.txId]
|
|
156
|
-
if (txStatus?.status === 'dropped') {
|
|
157
|
-
await updateTx(tx, assetName, { error: 'Dropped' })
|
|
158
|
-
}
|
|
159
|
-
// status === 'confirmed' or 'pending' - tx is fine, wait for Clarity to confirm
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return { txsToRemove }
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async persistSecurityState({ walletAccount, accountState, isBlacklisted, eip7702Delegation }) {
|
|
167
|
-
const securityStatePatch = {
|
|
168
|
-
...(isBlacklisted !== undefined && { isBlacklisted }),
|
|
169
|
-
...(eip7702Delegation !== undefined && { eip7702Delegation }),
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (isEmpty(securityStatePatch)) {
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
await this.updateAccountState({
|
|
177
|
-
walletAccount,
|
|
178
|
-
accountState,
|
|
179
|
-
newData: securityStatePatch,
|
|
180
|
-
})
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async tick({ walletAccount, refresh }) {
|
|
184
|
-
await this.subscribeWalletAddresses(walletAccount)
|
|
185
|
-
|
|
186
|
-
const walletAccountInfo = this.#walletAccountInfo.get(walletAccount)
|
|
187
|
-
|
|
188
|
-
if (!walletAccountInfo) {
|
|
189
|
-
return this.logger.warn('walletAccountInfo is empty', { walletAccount })
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const { derivedData, tokensByAddress, assets, tokens, assetName } = walletAccountInfo
|
|
193
|
-
const { eip7702Delegation, isBlacklisted } = await this.getSecurityAccountState({
|
|
194
|
-
derivedData,
|
|
195
|
-
walletAccount,
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
await this.persistSecurityState({
|
|
199
|
-
walletAccount,
|
|
200
|
-
accountState: derivedData.currentAccountState,
|
|
201
|
-
isBlacklisted,
|
|
202
|
-
eip7702Delegation,
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
const accountState = await this.getNewAccountState({
|
|
206
|
-
tokens,
|
|
207
|
-
currentTokenBalances: derivedData.currentAccountState?.tokenBalances,
|
|
208
|
-
ourWalletAddress: derivedData.ourWalletAddress,
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
const batch = this.aci.createOperationsBatch()
|
|
212
|
-
const newData = { ...accountState }
|
|
213
|
-
let allTxs = []
|
|
214
|
-
let hasNewTxs = false
|
|
215
|
-
let historyError
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
const response = await this.getHistoryFromServer({ walletAccount, derivedData, refresh })
|
|
219
|
-
|
|
220
|
-
;({ allTxs } = await normalizeTransactionsResponse({
|
|
221
|
-
asset: this.asset,
|
|
222
|
-
fromAddress: derivedData.ourWalletAddress,
|
|
223
|
-
response,
|
|
224
|
-
walletAccount,
|
|
225
|
-
}))
|
|
226
|
-
|
|
227
|
-
hasNewTxs = allTxs.length > 0
|
|
228
|
-
|
|
229
|
-
const logItemsByAsset = this.getAllLogItemsByAsset({
|
|
230
|
-
getLogItemsFromServerTx,
|
|
231
|
-
ourWalletAddress: derivedData.ourWalletAddress,
|
|
232
|
-
allTransactionsFromServer: allTxs,
|
|
233
|
-
asset: this.asset,
|
|
234
|
-
tokensByAddress,
|
|
235
|
-
assets,
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
const { txsToRemove } = await this.checkPendingTransactions({
|
|
239
|
-
txlist: allTxs,
|
|
240
|
-
walletAccount,
|
|
241
|
-
refresh,
|
|
242
|
-
logItemsByAsset,
|
|
243
|
-
asset: this.asset,
|
|
244
|
-
...derivedData,
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
this.aci.removeTxLogBatch({
|
|
248
|
-
assetName,
|
|
249
|
-
walletAccount,
|
|
250
|
-
txs: txsToRemove,
|
|
251
|
-
batch,
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
for (const [assetName, txs] of Object.entries(logItemsByAsset)) {
|
|
255
|
-
this.aci.updateTxLogAndNotifyBatch({
|
|
256
|
-
assetName,
|
|
257
|
-
walletAccount,
|
|
258
|
-
txs,
|
|
259
|
-
refresh,
|
|
260
|
-
batch,
|
|
261
|
-
})
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (response.cursor) {
|
|
265
|
-
newData.clarityCursor = response.cursor
|
|
266
|
-
}
|
|
267
|
-
} catch (error) {
|
|
268
|
-
historyError = error
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
this.aci.updateAccountStateBatch({
|
|
273
|
-
assetName,
|
|
274
|
-
walletAccount,
|
|
275
|
-
accountState,
|
|
276
|
-
newData,
|
|
277
|
-
batch,
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
await this.aci.executeOperationsBatch(batch)
|
|
281
|
-
} catch (batchError) {
|
|
282
|
-
if (!historyError) throw batchError
|
|
283
|
-
this.logger.warn('error persisting account state after history failure', batchError)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (historyError) {
|
|
287
|
-
throw historyError
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (refresh || hasNewTxs) {
|
|
291
|
-
const unknownTokenAddresses = this.getUnknownTokenAddresses({
|
|
292
|
-
transactions: allTxs,
|
|
293
|
-
tokensByAddress,
|
|
294
|
-
})
|
|
295
|
-
if (unknownTokenAddresses.length > 0) {
|
|
296
|
-
this.emit('unknown-tokens', unknownTokenAddresses)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async processAndFillTransactionsToState({
|
|
302
|
-
allTxs,
|
|
303
|
-
derivedData,
|
|
304
|
-
tokensByAddress,
|
|
305
|
-
assets,
|
|
306
|
-
tokens,
|
|
307
|
-
assetName,
|
|
308
|
-
walletAccount,
|
|
309
|
-
refresh,
|
|
310
|
-
cursor,
|
|
311
|
-
}) {
|
|
312
|
-
const hasNewTxs = allTxs.length > 0
|
|
313
|
-
|
|
314
|
-
const { eip7702Delegation, isBlacklisted } = await this.getSecurityAccountState({
|
|
315
|
-
derivedData,
|
|
316
|
-
walletAccount,
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
await this.persistSecurityState({
|
|
320
|
-
walletAccount,
|
|
321
|
-
accountState: derivedData.currentAccountState,
|
|
322
|
-
isBlacklisted,
|
|
323
|
-
eip7702Delegation,
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
const accountState = await this.getNewAccountState({
|
|
327
|
-
tokens,
|
|
328
|
-
currentTokenBalances: derivedData.currentAccountState?.tokenBalances,
|
|
329
|
-
ourWalletAddress: derivedData.ourWalletAddress,
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
const logItemsByAsset = this.getAllLogItemsByAsset({
|
|
333
|
-
getLogItemsFromServerTx,
|
|
334
|
-
ourWalletAddress: derivedData.ourWalletAddress,
|
|
335
|
-
allTransactionsFromServer: allTxs,
|
|
336
|
-
asset: this.asset,
|
|
337
|
-
tokensByAddress,
|
|
338
|
-
assets,
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
const { txsToRemove } = await this.checkPendingTransactions({
|
|
342
|
-
txlist: allTxs,
|
|
343
|
-
walletAccount,
|
|
344
|
-
refresh,
|
|
345
|
-
logItemsByAsset,
|
|
346
|
-
asset: this.asset,
|
|
347
|
-
...derivedData,
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
const batch = this.aci.createOperationsBatch()
|
|
351
|
-
|
|
352
|
-
this.aci.removeTxLogBatch({
|
|
353
|
-
assetName,
|
|
354
|
-
walletAccount,
|
|
355
|
-
txs: txsToRemove,
|
|
356
|
-
batch,
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
for (const [assetName, txs] of Object.entries(logItemsByAsset)) {
|
|
360
|
-
this.aci.updateTxLogAndNotifyBatch({
|
|
361
|
-
assetName,
|
|
362
|
-
walletAccount,
|
|
363
|
-
txs,
|
|
364
|
-
refresh,
|
|
365
|
-
batch,
|
|
366
|
-
})
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// All updates must go through newData (accountState param is only used for mem merging)
|
|
370
|
-
const newData = { ...accountState }
|
|
371
|
-
|
|
372
|
-
if (cursor) {
|
|
373
|
-
newData.clarityCursor = cursor
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
this.aci.updateAccountStateBatch({
|
|
377
|
-
assetName,
|
|
378
|
-
walletAccount,
|
|
379
|
-
accountState,
|
|
380
|
-
newData,
|
|
381
|
-
batch,
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
await this.aci.executeOperationsBatch(batch)
|
|
385
|
-
|
|
386
|
-
if (refresh || hasNewTxs) {
|
|
387
|
-
const unknownTokenAddresses = this.getUnknownTokenAddresses({
|
|
388
|
-
transactions: allTxs,
|
|
389
|
-
tokensByAddress,
|
|
390
|
-
})
|
|
391
|
-
if (unknownTokenAddresses.length > 0) {
|
|
392
|
-
this.emit('unknown-tokens', unknownTokenAddresses)
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
async getSecurityAccountState({ derivedData, walletAccount }) {
|
|
398
|
-
const shouldCheckBlacklist = this.tickCount[walletAccount] === 0
|
|
399
|
-
const eip7702Delegation = await getCurrentEIP7702Delegation({
|
|
400
|
-
server: this.server,
|
|
401
|
-
address: derivedData.ourWalletAddress,
|
|
402
|
-
eip7702Supported: this.eip7702Supported,
|
|
403
|
-
currentDelegation: derivedData.currentAccountState?.eip7702Delegation,
|
|
404
|
-
logger: this.logger,
|
|
405
|
-
})
|
|
406
|
-
const isBlacklisted = shouldCheckBlacklist
|
|
407
|
-
? await getCurrentBlackListStatus({
|
|
408
|
-
getBlackListStatus: this.getBlackListStatus,
|
|
409
|
-
address: derivedData.ourWalletAddress,
|
|
410
|
-
currentIsBlacklisted: derivedData.currentAccountState?.isBlacklisted,
|
|
411
|
-
logger: this.logger,
|
|
412
|
-
})
|
|
413
|
-
: undefined
|
|
414
|
-
|
|
415
|
-
return { eip7702Delegation, isBlacklisted }
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
async addSingleTx({ tx, address, cursor }) {
|
|
419
|
-
const walletAccounts = this.#walletAccountByAddress.get(address)
|
|
420
|
-
|
|
421
|
-
if (!walletAccounts || walletAccounts.length === 0) {
|
|
422
|
-
return
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
for (const walletAccount of walletAccounts) {
|
|
426
|
-
const walletAccountInfo = this.#walletAccountInfo.get(walletAccount)
|
|
427
|
-
|
|
428
|
-
if (!walletAccountInfo) {
|
|
429
|
-
continue
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const { derivedData, tokensByAddress, assets, tokens, assetName } = walletAccountInfo
|
|
433
|
-
|
|
434
|
-
await this.processAndFillTransactionsToState({
|
|
435
|
-
allTxs: [tx],
|
|
436
|
-
derivedData,
|
|
437
|
-
tokensByAddress,
|
|
438
|
-
assets,
|
|
439
|
-
tokens,
|
|
440
|
-
assetName,
|
|
441
|
-
walletAccount,
|
|
442
|
-
refresh: false,
|
|
443
|
-
cursor,
|
|
444
|
-
})
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
async getNewAccountState({ tokens, currentTokenBalances, ourWalletAddress }) {
|
|
449
|
-
const asset = this.asset
|
|
450
|
-
const newAccountState = Object.create(null)
|
|
451
|
-
const balances = await this.getBalances({ tokens, ourWalletAddress })
|
|
452
|
-
if (this.#rpcBalanceAssetNames.includes(asset.name)) {
|
|
453
|
-
const balance = balances[asset.name]
|
|
454
|
-
newAccountState.balance = asset.currency.baseUnit(balance)
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const tokenBalancePairs = Object.entries(balances).filter((entry) => entry[0] !== asset.name)
|
|
458
|
-
const tokenBalanceEntries = tokenBalancePairs
|
|
459
|
-
.map((pair) => {
|
|
460
|
-
const token = tokens.find((token) => token.name === pair[0])
|
|
461
|
-
const value = token.currency.baseUnit(pair[1] || 0)
|
|
462
|
-
return [token.name, value]
|
|
463
|
-
})
|
|
464
|
-
.filter(Boolean)
|
|
465
|
-
|
|
466
|
-
const tokenBalances = excludeUnchangedTokenBalances(currentTokenBalances, tokenBalanceEntries)
|
|
467
|
-
if (!isEmpty(tokenBalances)) newAccountState.tokenBalances = tokenBalances
|
|
468
|
-
return newAccountState
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
async getReceiveAddressesByWalletAccount() {
|
|
472
|
-
const walletAccounts = await this.aci.getWalletAccounts({ assetName: this.asset.name })
|
|
473
|
-
const addressesByAccount = Object.create(null)
|
|
474
|
-
for (const walletAccount of walletAccounts) {
|
|
475
|
-
addressesByAccount[walletAccount] = await this.aci.getReceiveAddresses({
|
|
476
|
-
assetName: this.asset.name,
|
|
477
|
-
walletAccount,
|
|
478
|
-
useCache: true,
|
|
479
|
-
})
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
return addressesByAccount
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
async fillAssetsTokensAndData({ walletAccount }) {
|
|
486
|
-
const assetName = this.asset.name
|
|
487
|
-
const assets = await this.aci.getAssetsForNetwork({ baseAssetName: assetName })
|
|
488
|
-
const tokens = Object.values(assets).filter((asset) => assetName !== asset.name)
|
|
489
|
-
|
|
490
|
-
const tokensByAddress = tokens.reduce((map, token) => {
|
|
491
|
-
const addresses = getAssetAddresses(token)
|
|
492
|
-
for (const address of addresses) map.set(address.toLowerCase(), token)
|
|
493
|
-
return map
|
|
494
|
-
}, new Map())
|
|
495
|
-
|
|
496
|
-
const derivedData = await this.deriveData({ assetName, walletAccount, tokens })
|
|
497
|
-
|
|
498
|
-
this.#walletAccountInfo.set(walletAccount, {
|
|
499
|
-
assets,
|
|
500
|
-
tokens,
|
|
501
|
-
tokensByAddress,
|
|
502
|
-
derivedData,
|
|
503
|
-
assetName,
|
|
504
|
-
})
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
async subscribeAllWalletAccounts() {
|
|
508
|
-
const addressesByWalletAccount = await this.getReceiveAddressesByWalletAccount()
|
|
509
|
-
const entriesAddressesByWalletAccount = Object.entries(addressesByWalletAccount)
|
|
510
|
-
|
|
511
|
-
for (const [walletAccount] of entriesAddressesByWalletAccount) {
|
|
512
|
-
await this.subscribeWalletAddresses(walletAccount)
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
async subscribeWalletAddresses(walletAccount) {
|
|
517
|
-
const addressesByWalletAccount = await this.aci.getReceiveAddresses({
|
|
518
|
-
assetName: this.asset.name,
|
|
519
|
-
walletAccount,
|
|
520
|
-
useCache: true,
|
|
521
|
-
})
|
|
522
|
-
|
|
523
|
-
const address = addressesByWalletAccount[0].toLowerCase() // Only check m/0/0
|
|
524
|
-
await this.fillAssetsTokensAndData({ walletAccount })
|
|
525
|
-
|
|
526
|
-
if (!this.#walletAccountByAddress.has(address)) {
|
|
527
|
-
this.#walletAccountByAddress.set(address, [])
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const walletAccounts = this.#walletAccountByAddress.get(address)
|
|
531
|
-
|
|
532
|
-
if (!walletAccounts.includes(walletAccount)) {
|
|
533
|
-
walletAccounts.push(walletAccount)
|
|
534
|
-
this.#walletAccountByAddress.set(address, walletAccounts)
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
this.server.connectTransactions({ walletAccount, address })
|
|
538
|
-
|
|
539
|
-
this.#wsClient.subscribeWalletAddresses({
|
|
540
|
-
network: this.asset.name,
|
|
541
|
-
addresses: [address],
|
|
542
|
-
})
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
async getBalances({ tokens, ourWalletAddress }) {
|
|
546
|
-
const batch = Object.create(null)
|
|
547
|
-
if (this.#rpcBalanceAssetNames.includes(this.asset.name)) {
|
|
548
|
-
const request = this.server.getBalanceRequest(ourWalletAddress)
|
|
549
|
-
batch[this.asset.name] = request
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
for (const token of tokens) {
|
|
553
|
-
if (this.#rpcBalanceAssetNames.includes(token.name) && token.contract.address) {
|
|
554
|
-
const request = this.server.balanceOfRequest(ourWalletAddress, token.contract.address)
|
|
555
|
-
batch[token.name] = request
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const pairs = Object.entries(batch)
|
|
560
|
-
if (pairs.length === 0) {
|
|
561
|
-
return {}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const requests = pairs.map((pair) => pair[1])
|
|
565
|
-
const responses = await this.server.sendBatchRequest(requests)
|
|
566
|
-
const entries = pairs.map((pair, idx) => {
|
|
567
|
-
const balanceHex = responses[idx]
|
|
568
|
-
const name = pair[0]
|
|
569
|
-
const balance = fromHexToString(balanceHex)
|
|
570
|
-
return [name, balance]
|
|
571
|
-
})
|
|
572
|
-
return Object.fromEntries(entries)
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
getUnknownTokenAddresses({ transactions, tokensByAddress }) {
|
|
576
|
-
const set = transactions.reduce((acc, txn) => {
|
|
577
|
-
const transfers = filterEffects(txn.effects, 'erc20') || []
|
|
578
|
-
transfers.forEach((transfer) => {
|
|
579
|
-
const addr = transfer.address.toLowerCase()
|
|
580
|
-
if (!tokensByAddress.has(addr)) {
|
|
581
|
-
acc.add(addr)
|
|
582
|
-
}
|
|
583
|
-
})
|
|
584
|
-
return acc
|
|
585
|
-
}, new Set())
|
|
586
|
-
return [...set]
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// NOTE: Here, fetchedGasPrices is the result of a call to `ClarityMonitor.getFee()`.
|
|
590
|
-
async updateGasPrice(fetchedGasPrices) {
|
|
591
|
-
try {
|
|
592
|
-
await executeEthLikeFeeMonitorUpdate({
|
|
593
|
-
assetClientInterface: this.aci,
|
|
594
|
-
feeAsset: this.asset,
|
|
595
|
-
fetchedGasPrices,
|
|
596
|
-
})
|
|
597
|
-
} catch (e) {
|
|
598
|
-
this.logger.warn('error updating gasPrice', e)
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
async onFeeUpdated(fee) {
|
|
603
|
-
return this.updateGasPrice(fee)
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
async beforeStart() {
|
|
607
|
-
this.listenToServerEvents()
|
|
608
|
-
if (this.config.GAS_PRICE_FROM_WEBSOCKET) {
|
|
609
|
-
this.server.connectFee()
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
async afterStop() {
|
|
614
|
-
this.server.dispose()
|
|
615
|
-
this.#wsClient.dispose(this.asset.name)
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
async getHistoryFromServer({ walletAccount, derivedData, refresh }) {
|
|
619
|
-
const address = derivedData.ourWalletAddress
|
|
620
|
-
const currentCursor = derivedData.currentAccountState?.clarityCursor
|
|
621
|
-
const cursor = currentCursor && !refresh ? currentCursor : null
|
|
622
|
-
return this.server.getAllTransactions({ walletAccount, address, cursor })
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
listenToServerEvents() {
|
|
626
|
-
this.server.on('feeUpdated', (...args) => this.onFeeUpdated(...args))
|
|
627
|
-
this.#wsClient.on(
|
|
628
|
-
`${this.asset.name}:new_transaction`,
|
|
629
|
-
async ({ transaction, address, cursor }) =>
|
|
630
|
-
this.addSingleTx({
|
|
631
|
-
tx: transaction,
|
|
632
|
-
address,
|
|
633
|
-
cursor,
|
|
634
|
-
})
|
|
635
|
-
)
|
|
636
|
-
}
|
|
637
|
-
}
|