@exodus/ethereum-api 8.76.1 → 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 +18 -0
- package/package.json +2 -2
- package/src/create-asset-utils.js +4 -14
- package/src/tx-create.js +18 -3
- package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +23 -0
- package/src/tx-log/get-optimistic-txlog-effects.js +2 -0
- package/src/tx-log/monitor-utils/get-log-items-from-server-tx.js +33 -5
- package/src/tx-log/monitor-utils/get-raw-transfer-amount.js +29 -0
- package/src/tx-log/clarity-monitor-v2.js +0 -637
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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
|
+
|
|
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)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
* fix(ethereum-api): persist rawCoinAmount in tx-log monitor readers (#8131)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## [8.76.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.76.0...@exodus/ethereum-api@8.76.1) (2026-05-26)
|
|
7
25
|
|
|
8
26
|
|
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,
|
package/src/tx-create.js
CHANGED
|
@@ -257,14 +257,29 @@ const createBumpUnsignedTx = async ({
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
const
|
|
260
|
+
const sourceTx = replacedTokenTx || replacedTx
|
|
261
|
+
const toAddress = sourceTx.to
|
|
262
|
+
|
|
263
|
+
// For self-sends `coinAmount` is `0` (correct for balance accounting, the
|
|
264
|
+
// wallet's net delta really is zero), so we can't reconstruct the on-chain
|
|
265
|
+
// value from it. Recover from `data.rawCoinAmount`, which the tx-log
|
|
266
|
+
// writers persist alongside `coinAmount` for exactly this purpose.
|
|
267
|
+
if (sourceTx.selfSend && !sourceTx.data?.rawCoinAmount) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Cannot bump self-send transaction ${bumpTxId}: original transfer amount is unrecoverable from the tx log (data.rawCoinAmount missing). Re-broadcast the send with a higher fee instead.`
|
|
270
|
+
)
|
|
271
|
+
}
|
|
261
272
|
|
|
262
|
-
const amount =
|
|
273
|
+
const amount = sourceTx.data?.rawCoinAmount
|
|
274
|
+
? asset.currency.baseUnit(sourceTx.data.rawCoinAmount)
|
|
275
|
+
: sourceTx.coinAmount.negate()
|
|
263
276
|
|
|
264
277
|
const txValue = assertValidTxValue({
|
|
265
278
|
asset,
|
|
266
279
|
amount,
|
|
267
|
-
txValue: replacedTx.
|
|
280
|
+
txValue: replacedTx.data?.rawCoinAmount
|
|
281
|
+
? baseAsset.currency.baseUnit(replacedTx.data.rawCoinAmount)
|
|
282
|
+
: replacedTx.coinAmount.negate(),
|
|
268
283
|
})
|
|
269
284
|
|
|
270
285
|
const isDuplex = Boolean(replacedTokenTx && txValue.gt(baseAsset.currency.ZERO))
|
|
@@ -2,6 +2,7 @@ import lodash from 'lodash'
|
|
|
2
2
|
|
|
3
3
|
import { getWalletUpdates } from '../monitor-utils/get-balance-updates.js'
|
|
4
4
|
import getFeeAmount from '../monitor-utils/get-fee-amount.js'
|
|
5
|
+
import getRawTransferAmount from '../monitor-utils/get-raw-transfer-amount.js'
|
|
5
6
|
import getTransfersByTokenName from '../monitor-utils/get-transfers-by-token-name.js'
|
|
6
7
|
import getValueOfTransfers from '../monitor-utils/get-value-of-transfers.js'
|
|
7
8
|
import isConfirmedServerTx from '../monitor-utils/is-confirmed-server-tx.js'
|
|
@@ -62,6 +63,18 @@ export default function getLogItemsFromServerTx({
|
|
|
62
63
|
|
|
63
64
|
if (shouldAttachTx) {
|
|
64
65
|
const coinAmount = getValueOfTransfers(ourWalletAddress, asset, ethereumTransfers)
|
|
66
|
+
// `rawCoinAmount` mirrors the field written by `getOptimisticTxLogEffects`
|
|
67
|
+
// and is consumed by the bump path (`tx-create.js`) to recover the
|
|
68
|
+
// user-signed on-chain `value` when accelerating a self-send (where
|
|
69
|
+
// `coinAmount` is zero). For the base-asset entry the raw amount is the
|
|
70
|
+
// top-level `value` field of the tx itself, not the sum of internal
|
|
71
|
+
// transfers — that's what the user signed and what we must restore on
|
|
72
|
+
// bump.
|
|
73
|
+
const rawCoinAmount = getRawTransferAmount({
|
|
74
|
+
ourWalletAddress,
|
|
75
|
+
asset,
|
|
76
|
+
transfers: [serverTx],
|
|
77
|
+
}).toBaseString()
|
|
65
78
|
const selfSend = isSelfSendTx({
|
|
66
79
|
coinAmount,
|
|
67
80
|
ourWalletWasSender,
|
|
@@ -90,6 +103,7 @@ export default function getLogItemsFromServerTx({
|
|
|
90
103
|
data,
|
|
91
104
|
nonce,
|
|
92
105
|
gasLimit,
|
|
106
|
+
rawCoinAmount,
|
|
93
107
|
balanceChange: baseBalanceUpdate,
|
|
94
108
|
nonceChange: nonceUpdate,
|
|
95
109
|
...methodId,
|
|
@@ -130,6 +144,14 @@ export default function getLogItemsFromServerTx({
|
|
|
130
144
|
|
|
131
145
|
const tokenTransferToAddress = tryFindExternalRecipient(tokenTransfers, ourWalletAddress)
|
|
132
146
|
const coinAmount = getValueOfTransfers(ourWalletAddress, token, tokenTransfers)
|
|
147
|
+
// See base-entry comment above; for token entries the raw amount is the
|
|
148
|
+
// absolute total tokens our wallet moved in this tx (sum of outgoing, or
|
|
149
|
+
// incoming for receive-only entries).
|
|
150
|
+
const rawCoinAmount = getRawTransferAmount({
|
|
151
|
+
ourWalletAddress,
|
|
152
|
+
asset: token,
|
|
153
|
+
transfers: tokenTransfers,
|
|
154
|
+
}).toBaseString()
|
|
133
155
|
|
|
134
156
|
const tokenFromAddresses = lodash.uniq(
|
|
135
157
|
tokenTransfers.filter(({ to }) => to === ourWalletAddress).map(({ from }) => from)
|
|
@@ -158,6 +180,7 @@ export default function getLogItemsFromServerTx({
|
|
|
158
180
|
data,
|
|
159
181
|
nonce,
|
|
160
182
|
gasLimit,
|
|
183
|
+
rawCoinAmount,
|
|
161
184
|
balanceChange,
|
|
162
185
|
...methodId,
|
|
163
186
|
...(isFiniteInteger(blockNumber) ? { blockNumber } : null),
|
|
@@ -158,6 +158,7 @@ export const getOptimisticTxLogEffects = async ({
|
|
|
158
158
|
txs: [
|
|
159
159
|
{
|
|
160
160
|
...sharedProps,
|
|
161
|
+
data: { ...sharedProps.data, rawCoinAmount: amount.abs().toBaseString() },
|
|
161
162
|
coinAmount: selfSend ? asset.currency.ZERO : amount.abs().negate(),
|
|
162
163
|
coinName: asset.name,
|
|
163
164
|
currencies: {
|
|
@@ -173,6 +174,7 @@ export const getOptimisticTxLogEffects = async ({
|
|
|
173
174
|
txs: [
|
|
174
175
|
{
|
|
175
176
|
...sharedProps,
|
|
177
|
+
data: { ...sharedProps.data, rawCoinAmount: txValue.abs().toBaseString() },
|
|
176
178
|
coinAmount: selfSend ? baseAsset.currency.ZERO : txValue.abs().negate(),
|
|
177
179
|
coinName: baseAsset.name,
|
|
178
180
|
currencies: {
|
|
@@ -2,6 +2,7 @@ import lodash from 'lodash'
|
|
|
2
2
|
|
|
3
3
|
import getFeeAmount from './get-fee-amount.js'
|
|
4
4
|
import getNamesOfTokensTransferredByServerTx from './get-names-of-tokens-transferred-by-server-tx.js'
|
|
5
|
+
import getRawTransferAmount from './get-raw-transfer-amount.js'
|
|
5
6
|
import getTransfersByTokenName from './get-transfers-by-token-name.js'
|
|
6
7
|
import getValueOfTransfers from './get-value-of-transfers.js'
|
|
7
8
|
import isConfirmedServerTx from './is-confirmed-server-tx.js'
|
|
@@ -50,6 +51,18 @@ export default function getLogItemsFromServerTx({
|
|
|
50
51
|
|
|
51
52
|
if (sendingTransferPresent || receivingTransferPresent || nftTransferPresent) {
|
|
52
53
|
const coinAmount = getValueOfTransfers(ourWalletAddress, asset, ethereumTransfers)
|
|
54
|
+
// `rawCoinAmount` mirrors the field written by `getOptimisticTxLogEffects`
|
|
55
|
+
// and is consumed by the bump path (`tx-create.js`) to recover the
|
|
56
|
+
// user-signed on-chain `value` when accelerating a self-send (where
|
|
57
|
+
// `coinAmount` is zero). For the base-asset entry the raw amount is the
|
|
58
|
+
// top-level `value` field of the tx itself, not the sum of internal
|
|
59
|
+
// transfers — that's what the user signed and what we must restore on
|
|
60
|
+
// bump.
|
|
61
|
+
const rawCoinAmount = getRawTransferAmount({
|
|
62
|
+
ourWalletAddress,
|
|
63
|
+
asset,
|
|
64
|
+
transfers: [serverTx],
|
|
65
|
+
}).toBaseString()
|
|
53
66
|
const selfSend = isSelfSendTx({
|
|
54
67
|
coinAmount,
|
|
55
68
|
ourWalletWasSender,
|
|
@@ -78,6 +91,7 @@ export default function getLogItemsFromServerTx({
|
|
|
78
91
|
data,
|
|
79
92
|
nonce,
|
|
80
93
|
gasLimit,
|
|
94
|
+
rawCoinAmount,
|
|
81
95
|
...methodId,
|
|
82
96
|
...(sent?.length > 0 ? { sent } : undefined),
|
|
83
97
|
},
|
|
@@ -108,11 +122,25 @@ export default function getLogItemsFromServerTx({
|
|
|
108
122
|
|
|
109
123
|
const token = assets[tokenName]
|
|
110
124
|
const tokenTransferToAddress = tryFindExternalRecipient(tokenTransfers, ourWalletAddress)
|
|
111
|
-
|
|
125
|
+
// Transfers from this reader's server source are tagged with
|
|
126
|
+
// `events: true` once the tx is included in a block (event-log derived)
|
|
127
|
+
// and `events: false` while still pending (tx-data derived). Only one
|
|
128
|
+
// flavor is real for a given lifecycle state; filtering avoids
|
|
129
|
+
// double-counting if both ever coexist for the same transfer. Both
|
|
130
|
+
// `coinAmount` and `rawCoinAmount` must see the same set so they stay
|
|
131
|
+
// consistent.
|
|
132
|
+
const filteredTokenTransfers = lodash.filter(tokenTransfers, {
|
|
133
|
+
events: confirmations > 0,
|
|
134
|
+
})
|
|
135
|
+
const coinAmount = getValueOfTransfers(ourWalletAddress, token, filteredTokenTransfers)
|
|
136
|
+
// See base-entry comment above; for token entries the raw amount is the
|
|
137
|
+
// absolute total tokens our wallet moved in this tx (sum of outgoing, or
|
|
138
|
+
// incoming for receive-only entries).
|
|
139
|
+
const rawCoinAmount = getRawTransferAmount({
|
|
112
140
|
ourWalletAddress,
|
|
113
|
-
token,
|
|
114
|
-
|
|
115
|
-
)
|
|
141
|
+
asset: token,
|
|
142
|
+
transfers: filteredTokenTransfers,
|
|
143
|
+
}).toBaseString()
|
|
116
144
|
const tokenFromAddresses = lodash.uniq(
|
|
117
145
|
tokenTransfers.filter(({ to }) => to === ourWalletAddress).map(({ from }) => from)
|
|
118
146
|
)
|
|
@@ -136,7 +164,7 @@ export default function getLogItemsFromServerTx({
|
|
|
136
164
|
...logItemCommonProperties,
|
|
137
165
|
coinAmount,
|
|
138
166
|
coinName: tokenName,
|
|
139
|
-
data: { data, nonce, gasLimit, ...methodId },
|
|
167
|
+
data: { data, nonce, gasLimit, rawCoinAmount, ...methodId },
|
|
140
168
|
...(isConsideredSent
|
|
141
169
|
? { from: [], to: tokenTransferToAddress, feeAmount, feeCoinName: asset.feeAsset.name }
|
|
142
170
|
: { from: tokenFromAddresses }),
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Returns the absolute raw transferred amount for `ourWalletAddress`, in
|
|
2
|
+
// `asset` base units, as a NumberUnit. Unlike `getValueOfTransfers` (which
|
|
3
|
+
// returns the signed net delta), this preserves the raw transfer value even
|
|
4
|
+
// when sender and receiver net to zero — e.g. self-sends. Used by tx-log
|
|
5
|
+
// writers to persist `data.rawCoinAmount` so the bump path can reconstruct
|
|
6
|
+
// the original on-chain value when accelerating a self-send.
|
|
7
|
+
//
|
|
8
|
+
// Each transfer is expected to have the shape `{ from, to, value }`. For the
|
|
9
|
+
// base-asset entry, pass `[serverTx]` (the tx itself has the same shape at
|
|
10
|
+
// its top level); for token entries, pass the array of token transfers for
|
|
11
|
+
// that token.
|
|
12
|
+
//
|
|
13
|
+
// Outgoing transfers (from === ourWalletAddress) take precedence: when our
|
|
14
|
+
// wallet sent anything, the raw amount is the sum of what we sent. We fall
|
|
15
|
+
// back to incoming so receiver-only entries still report a non-zero raw
|
|
16
|
+
// amount for consistency, even though the bump path only consults it on the
|
|
17
|
+
// sender side.
|
|
18
|
+
|
|
19
|
+
export default function getRawTransferAmount({ ourWalletAddress, asset, transfers }) {
|
|
20
|
+
const outgoing = transfers
|
|
21
|
+
.filter(({ from }) => from === ourWalletAddress)
|
|
22
|
+
.reduce((sum, { value }) => sum.add(asset.currency.baseUnit(value)), asset.currency.ZERO)
|
|
23
|
+
|
|
24
|
+
if (!outgoing.isZero) return outgoing
|
|
25
|
+
|
|
26
|
+
return transfers
|
|
27
|
+
.filter(({ to }) => to === ourWalletAddress)
|
|
28
|
+
.reduce((sum, { value }) => sum.add(asset.currency.baseUnit(value)), asset.currency.ZERO)
|
|
29
|
+
}
|
|
@@ -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
|
-
}
|