@exodus/ethereum-api 9.0.0-alpha.3 → 9.0.1
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 +2139 -0
- package/LICENSE +7 -0
- package/README.md +124 -0
- package/package.json +34 -33
- package/src/address-has-history.js +6 -2
- package/src/allowance/constants.js +4 -5
- package/src/allowance/index.js +159 -40
- package/src/check-tx/create-assess-transaction.js +23 -0
- package/src/check-tx/create-check-tx.js +104 -0
- package/src/check-tx/index.js +1 -0
- package/src/create-asset-plugin-factory.js +115 -0
- package/src/create-asset-utils.js +520 -0
- package/src/create-asset.js +273 -97
- package/src/create-token-factory.js +47 -17
- package/src/custom-fees.js +57 -0
- package/src/eip7702-whitelist.js +62 -0
- package/src/ens/index.js +12 -17
- package/src/error-wrapper.js +383 -0
- package/src/eth-like-util.js +172 -33
- package/src/exodus-eth-server/api-coin-nodes.js +27 -196
- package/src/exodus-eth-server/api.js +23 -14
- package/src/exodus-eth-server/clarity-v2.js +255 -0
- package/src/exodus-eth-server/clarity.js +67 -265
- package/src/exodus-eth-server/errors.js +40 -0
- package/src/exodus-eth-server/eth-like-server.js +385 -0
- package/src/exodus-eth-server/fetch-json.js +48 -0
- package/src/exodus-eth-server/index.js +77 -0
- package/src/exodus-eth-server/utils.js +31 -0
- package/src/exodus-eth-server/ws-gateway.js +306 -0
- package/src/exodus-eth-server/ws.js +1 -2
- package/src/fee-data/EthLikeFeeData.js +81 -0
- package/src/fee-data/index.js +54 -0
- package/src/fee-utils.js +229 -0
- package/src/gas-estimation.js +183 -66
- package/src/get-balance-for-address.js +1 -1
- package/src/get-balances.js +285 -27
- package/src/get-fee.js +92 -56
- package/src/get-historical-balance.js +58 -0
- package/src/hooks/index.js +1 -1
- package/src/hooks/monitor.js +182 -43
- package/src/hooks/staking-freshness.js +58 -0
- package/src/index.js +118 -12
- package/src/move-funds.js +153 -0
- package/src/multicall3/index.js +212 -0
- package/src/multisig-address.js +146 -0
- package/src/nft-utils.js +60 -0
- package/src/number-utils.js +34 -2
- package/src/optimism-gas/index.js +13 -5
- package/src/send-validations.js +58 -0
- package/src/server-based-fee-monitor.js +12 -19
- package/src/simulate-tx/fetch-tx-preview.js +0 -2
- package/src/simulate-tx/index.js +2 -2
- package/src/simulate-tx/simulate-eth-tx.js +24 -11
- package/src/simulation/common.js +34 -0
- package/src/simulation/create-simulate-message.js +49 -0
- package/src/simulation/create-simulate-transactions.js +108 -0
- package/src/simulation/estimate-fee.js +14 -0
- package/src/simulation/estimate-simple-transfer.js +15 -0
- package/src/simulation/get-message-type.js +18 -0
- package/src/simulation/simulate-message-api.js +68 -0
- package/src/simulation/simulate-transactions-api.js +266 -0
- package/src/simulation/simulate-transactions.js +16 -0
- package/src/simulation/transactions.js +52 -0
- package/src/simulation/try-estimating-changes-locally.js +26 -0
- package/src/staking/api/index.js +43 -0
- package/src/staking/cached-read.js +33 -0
- package/src/staking/ethereum/api.js +166 -34
- package/src/staking/ethereum/deps.js +37 -0
- package/src/staking/ethereum/everstake.js +113 -0
- package/src/staking/ethereum/index.js +4 -3
- package/src/staking/ethereum/service.js +682 -181
- package/src/staking/ethereum/staking-utils.js +275 -4
- package/src/staking/fantom-staking.js +9 -9
- package/src/staking/index.js +4 -4
- package/src/staking/matic/addresses.js +2 -0
- package/src/staking/matic/api.js +69 -49
- package/src/staking/matic/index.js +3 -3
- package/src/staking/matic/matic-staking-utils.js +243 -5
- package/src/staking/matic/matic-staking.md +53 -0
- package/src/staking/staking-provider-client.js +1 -1
- package/src/staking/utils/index.js +39 -0
- package/src/tx-create.js +666 -0
- package/src/tx-log/clarity-monitor.js +276 -103
- package/src/tx-log/clarity-truncated-history-monitor.js +130 -0
- package/src/tx-log/clarity-truncated-history-monitor.md +219 -0
- package/src/tx-log/clarity-utils/absolute.js +96 -0
- package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +94 -31
- package/src/tx-log/clarity-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
- package/src/tx-log/clarity-utils/index.js +9 -3
- package/src/tx-log/clarity-utils/is-spam-pending-tx.js +13 -0
- package/src/tx-log/clarity-utils/normalize-transactions-response.js +65 -0
- package/src/tx-log/ethereum-monitor.js +93 -47
- package/src/tx-log/ethereum-no-history-monitor.js +52 -41
- package/src/tx-log/get-optimistic-txlog-effects.js +379 -0
- package/src/tx-log/index.js +5 -3
- package/src/tx-log/monitor-utils/check-pending-transactions.js +14 -5
- package/src/tx-log/monitor-utils/exclude-unchanged-token-balances.js +1 -1
- package/src/tx-log/monitor-utils/extract-balance-from-clarity-account-info.js +108 -0
- package/src/tx-log/monitor-utils/get-balance-updates.js +44 -0
- package/src/tx-log/monitor-utils/get-batched-rpc-balances.js +47 -0
- package/src/tx-log/monitor-utils/get-current-blacklist-status.js +38 -0
- package/src/tx-log/monitor-utils/get-current-eip7702-delegation.js +79 -0
- package/src/tx-log/monitor-utils/get-derive-transactions-to-check.js +10 -5
- package/src/tx-log/monitor-utils/get-fee-amount.js +1 -1
- package/src/tx-log/monitor-utils/get-history-from-server.js +1 -1
- package/src/tx-log/monitor-utils/get-log-items-from-server-tx.js +75 -33
- package/src/tx-log/monitor-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
- package/src/tx-log/monitor-utils/get-raw-transfer-amount.js +29 -0
- package/src/tx-log/monitor-utils/get-transfers-by-token-name.js +3 -2
- package/src/tx-log/monitor-utils/get-value-of-transfers.js +10 -12
- package/src/tx-log/monitor-utils/index.js +12 -7
- package/src/tx-log/monitor-utils/is-unverifiable-pending-tx-expired.js +19 -0
- package/src/tx-log/monitor-utils/verify-pending-tx-status-rpc.js +90 -0
- package/src/tx-log-staking-processor/asset-staking-tx-data.js +82 -24
- package/src/tx-log-staking-processor/index.js +28 -14
- package/src/tx-log-staking-processor/utils.js +21 -20
- package/src/tx-send/broadcast-error-handler.js +63 -0
- package/src/tx-send/index.js +1 -2
- package/src/tx-send/nonce-utils.js +97 -0
- package/src/tx-send/tx-send.js +94 -288
- package/src/tx-sign/index.js +17 -0
- package/src/tx-type/index.js +280 -0
- package/src/watch-tx.js +36 -0
- package/src/web3/createSimulateMessage.js +3 -2
- package/src/web3/createSimulateTransactions.js +11 -0
- package/src/web3/index.js +3 -1
- package/src/etherscan/account.js +0 -49
- package/src/etherscan/index.js +0 -28
- package/src/etherscan/logs.js +0 -16
- package/src/etherscan/proxy.js +0 -47
- package/src/etherscan/request.js +0 -26
- package/src/etherscan/ws.js +0 -88
- package/src/exodus-eth-server/server-factory.js +0 -28
- package/src/exodus-eth-server/server-map.js +0 -21
- package/src/get-fee-async.js +0 -97
- package/src/staking/matic/service.js +0 -437
- package/src/staking-api.js +0 -5
- package/src/tx-log/clarity-utils/get-derive-data-needed-for-tick.js +0 -12
- package/src/tx-log-staking-processor/get-asset-tx-amount.js +0 -26
- package/src/tx-send/get-fee-info.js +0 -41
- package/src/websocket/index.android.js +0 -2
- package/src/websocket/index.ios.js +0 -2
- package/src/websocket/index.js +0 -21
|
@@ -1,21 +1,46 @@
|
|
|
1
1
|
import { BaseMonitor } from '@exodus/asset-lib'
|
|
2
|
-
import { getAssetAddresses
|
|
3
|
-
import
|
|
2
|
+
import { getAssetAddresses } from '@exodus/ethereum-lib'
|
|
3
|
+
import lodash from 'lodash'
|
|
4
|
+
import assert from 'minimalistic-assert'
|
|
4
5
|
|
|
6
|
+
import { applyRemoteConfigEip7702Whitelist, createEip7702Whitelist } from '../eip7702-whitelist.js'
|
|
7
|
+
import { executeEthLikeFeeMonitorUpdate } from '../fee-utils.js'
|
|
8
|
+
import {
|
|
9
|
+
filterEffects,
|
|
10
|
+
getLogItemsFromServerTx,
|
|
11
|
+
normalizeTransactionsResponse,
|
|
12
|
+
} from './clarity-utils/index.js'
|
|
13
|
+
import { getBatchedRpcBalances } from './monitor-utils/get-batched-rpc-balances.js'
|
|
5
14
|
import {
|
|
6
|
-
getAllLogItemsByAsset,
|
|
7
15
|
checkPendingTransactions,
|
|
8
|
-
getDeriveTransactionsToCheck,
|
|
9
16
|
excludeUnchangedTokenBalances,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
getAllLogItemsByAsset,
|
|
18
|
+
getCurrentBlackListStatus,
|
|
19
|
+
getCurrentEIP7702Delegation,
|
|
20
|
+
getDeriveDataNeededForTick,
|
|
21
|
+
getDeriveTransactionsToCheck,
|
|
22
|
+
isUnverifiablePendingTxExpired,
|
|
23
|
+
verifyRpcPendingTxStatusBatch,
|
|
24
|
+
} from './monitor-utils/index.js'
|
|
25
|
+
|
|
26
|
+
const { isEmpty } = lodash
|
|
13
27
|
|
|
14
28
|
export class ClarityMonitor extends BaseMonitor {
|
|
15
|
-
constructor({
|
|
29
|
+
constructor({
|
|
30
|
+
server,
|
|
31
|
+
config,
|
|
32
|
+
rpcBalanceAssetNames,
|
|
33
|
+
eip7702Supported,
|
|
34
|
+
eip7702Whitelist,
|
|
35
|
+
getBlackListStatus,
|
|
36
|
+
...args
|
|
37
|
+
}) {
|
|
16
38
|
super(args)
|
|
17
39
|
this.config = { GAS_PRICE_FROM_WEBSOCKET: true, ...config }
|
|
18
40
|
this.server = server
|
|
41
|
+
this.rpcBalanceAssetNames = rpcBalanceAssetNames
|
|
42
|
+
this.eip7702Whitelist = eip7702Whitelist ?? createEip7702Whitelist(eip7702Supported)
|
|
43
|
+
this.getBlackListStatus = getBlackListStatus
|
|
19
44
|
this.getAllLogItemsByAsset = getAllLogItemsByAsset
|
|
20
45
|
this.deriveDataNeededForTick = getDeriveDataNeededForTick(this.aci)
|
|
21
46
|
this.deriveTransactionsToCheck = getDeriveTransactionsToCheck({
|
|
@@ -26,6 +51,12 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
26
51
|
}
|
|
27
52
|
|
|
28
53
|
setServer(config) {
|
|
54
|
+
applyRemoteConfigEip7702Whitelist({
|
|
55
|
+
eip7702Whitelist: this.eip7702Whitelist,
|
|
56
|
+
remoteConfig: config,
|
|
57
|
+
logger: this.logger,
|
|
58
|
+
})
|
|
59
|
+
|
|
29
60
|
const uri = config?.server || this.server.defaultUri
|
|
30
61
|
|
|
31
62
|
if (uri === this.server.uri) {
|
|
@@ -60,19 +91,23 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
60
91
|
|
|
61
92
|
// eslint-disable-next-line no-undef
|
|
62
93
|
async checkPendingTransactions(params) {
|
|
63
|
-
const {
|
|
64
|
-
|
|
94
|
+
const {
|
|
95
|
+
pendingTransactionsToCheck,
|
|
96
|
+
pendingTransactionsGroupedByAddressAndNonce,
|
|
97
|
+
recentPendingTransactions,
|
|
98
|
+
} = checkPendingTransactions(params)
|
|
65
99
|
const txsToRemove = []
|
|
66
100
|
const { walletAccount } = params
|
|
67
101
|
|
|
68
|
-
const updateTx = (tx, asset, { error, remove }) => {
|
|
102
|
+
const updateTx = (tx, asset, { error, remove, preserve, isTxConfirmed }) => {
|
|
69
103
|
if (remove) {
|
|
70
104
|
txsToRemove.push({ tx, assetSource: { asset, walletAccount } })
|
|
105
|
+
} else if (preserve) {
|
|
106
|
+
params.logItemsByAsset[asset].push(tx)
|
|
71
107
|
} else {
|
|
72
108
|
params.logItemsByAsset[asset].push({
|
|
73
109
|
...tx,
|
|
74
|
-
dropped: true,
|
|
75
|
-
error,
|
|
110
|
+
...(isTxConfirmed ? { confirmations: 1 } : { dropped: true, error }),
|
|
76
111
|
})
|
|
77
112
|
}
|
|
78
113
|
|
|
@@ -85,11 +120,15 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
85
120
|
assetSource: { asset: assetName, walletAccount },
|
|
86
121
|
})
|
|
87
122
|
} else if (tokenTxSet && tokenTxSet.has(tx.txId)) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
123
|
+
// Optional: the stored tx may name a token since removed from the registry
|
|
124
|
+
params.logItemsByAsset[assetName]?.push(
|
|
125
|
+
preserve
|
|
126
|
+
? tokenTxSet.get(tx.txId)
|
|
127
|
+
: {
|
|
128
|
+
...tokenTxSet.get(tx.txId),
|
|
129
|
+
...(isTxConfirmed ? { confirmations: 1 } : { dropped: true, error }),
|
|
130
|
+
}
|
|
131
|
+
)
|
|
93
132
|
}
|
|
94
133
|
})
|
|
95
134
|
return Promise.all(promises)
|
|
@@ -101,19 +140,82 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
101
140
|
if (replaced) {
|
|
102
141
|
await updateTx(tx, assetName, { remove: true })
|
|
103
142
|
delete pendingTransactionsToCheck[tx.txId]
|
|
143
|
+
delete recentPendingTransactions[tx.txId]
|
|
104
144
|
}
|
|
105
145
|
}
|
|
106
146
|
|
|
147
|
+
// Batch verify all pending txs with a single RPC call
|
|
148
|
+
const txIds = Object.keys(pendingTransactionsToCheck)
|
|
149
|
+
const statuses = await verifyRpcPendingTxStatusBatch({
|
|
150
|
+
server: this.server,
|
|
151
|
+
logger: this.logger,
|
|
152
|
+
txIds,
|
|
153
|
+
})
|
|
154
|
+
|
|
107
155
|
for (const { tx, assetName } of Object.values(pendingTransactionsToCheck)) {
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
const txStatus = statuses[tx.txId]
|
|
157
|
+
if (params.refresh) {
|
|
158
|
+
if (txStatus?.status === 'dropped') {
|
|
159
|
+
await updateTx(tx, assetName, { remove: true })
|
|
160
|
+
} else if (txStatus?.status === 'confirmed') {
|
|
161
|
+
// Confirmed on-chain but missing from the indexer response: mark it
|
|
162
|
+
// confirmed so it stops being a pending entry that would otherwise be
|
|
163
|
+
// preserved (exempt from the age ceiling) on every refresh forever.
|
|
164
|
+
await updateTx(tx, assetName, { isTxConfirmed: true })
|
|
165
|
+
} else if (
|
|
166
|
+
// No txStatus at all means the whole verification batch failed - evidence
|
|
167
|
+
// about nothing, so preserve unconditionally; the age ceiling only applies
|
|
168
|
+
// to a per-entry 'unknown' verdict (evidence about that one hash).
|
|
169
|
+
!txStatus ||
|
|
170
|
+
txStatus.status === 'pending' ||
|
|
171
|
+
!isUnverifiablePendingTxExpired(tx)
|
|
172
|
+
) {
|
|
173
|
+
// Still live on-chain (or not verifiable yet) but missing from the indexer
|
|
174
|
+
// response: re-add it so the refresh overwrite cannot wipe it and break
|
|
175
|
+
// tx-log-based nonce resolution.
|
|
176
|
+
await updateTx(tx, assetName, { preserve: true })
|
|
177
|
+
} else {
|
|
178
|
+
// Unverifiable past the age ceiling: purge so a hash the node keeps
|
|
179
|
+
// erroring on cannot pin nonce derivation forever.
|
|
180
|
+
await updateTx(tx, assetName, { remove: true })
|
|
181
|
+
}
|
|
182
|
+
} else if (txStatus?.status === 'dropped') {
|
|
183
|
+
await updateTx(tx, assetName, { error: 'Dropped' })
|
|
184
|
+
}
|
|
185
|
+
// non-refresh: 'confirmed', 'pending' or unknown - tx is fine, wait for Clarity to confirm
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (params.refresh) {
|
|
189
|
+
// Too recent for a reliable on-chain verdict - always survive the refresh overwrite
|
|
190
|
+
for (const { tx, assetName } of Object.values(recentPendingTransactions)) {
|
|
191
|
+
await updateTx(tx, assetName, { preserve: true })
|
|
192
|
+
}
|
|
110
193
|
}
|
|
111
194
|
|
|
112
195
|
return { txsToRemove }
|
|
113
196
|
}
|
|
114
197
|
|
|
198
|
+
async persistSecurityState({ walletAccount, accountState, isBlacklisted, eip7702Delegation }) {
|
|
199
|
+
const securityStatePatch = {
|
|
200
|
+
...(isBlacklisted !== undefined && { isBlacklisted }),
|
|
201
|
+
...(eip7702Delegation !== undefined && { eip7702Delegation }),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (isEmpty(securityStatePatch)) {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await this.updateAccountState({
|
|
209
|
+
walletAccount,
|
|
210
|
+
accountState,
|
|
211
|
+
newData: securityStatePatch,
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
115
215
|
async tick({ walletAccount, refresh }) {
|
|
116
216
|
await this.subscribeWalletAddresses()
|
|
217
|
+
const tickCount = this.tickCount[walletAccount]
|
|
218
|
+
const shouldCheckBlacklist = tickCount === 0
|
|
117
219
|
|
|
118
220
|
const assets = await this.aci.getAssetsForNetwork({ baseAssetName: this.asset.name })
|
|
119
221
|
const tokens = Object.values(assets).filter((asset) => asset.baseAsset.name !== asset.name)
|
|
@@ -123,44 +225,116 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
123
225
|
return map
|
|
124
226
|
}, new Map())
|
|
125
227
|
const assetName = this.asset.name
|
|
126
|
-
|
|
127
228
|
const derivedData = await this.deriveData({ assetName, walletAccount, tokens })
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const logItemsByAsset = this.getAllLogItemsByAsset({
|
|
133
|
-
getLogItemsFromServerTx,
|
|
134
|
-
ourWalletAddress: derivedData.ourWalletAddress,
|
|
135
|
-
allTransactionsFromServer: allTxs,
|
|
136
|
-
asset: this.asset,
|
|
137
|
-
tokensByAddress,
|
|
138
|
-
assets,
|
|
229
|
+
const { eip7702Delegation, isBlacklisted } = await this.getSecurityAccountState({
|
|
230
|
+
derivedData,
|
|
231
|
+
shouldCheckBlacklist,
|
|
139
232
|
})
|
|
140
233
|
|
|
141
|
-
|
|
142
|
-
txlist: allTxs,
|
|
234
|
+
await this.persistSecurityState({
|
|
143
235
|
walletAccount,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
...derivedData,
|
|
236
|
+
accountState: derivedData.currentAccountState,
|
|
237
|
+
isBlacklisted,
|
|
238
|
+
eip7702Delegation,
|
|
148
239
|
})
|
|
149
240
|
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
241
|
+
const batch = this.aci.createOperationsBatch()
|
|
242
|
+
let allTxs = []
|
|
243
|
+
let hasNewTxs = false
|
|
244
|
+
let historyResponse
|
|
245
|
+
let historyError
|
|
246
|
+
let processedLogItemsByAsset
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const response = await this.getHistoryFromServer({ walletAccount, derivedData, refresh })
|
|
250
|
+
|
|
251
|
+
;({ allTxs } = await normalizeTransactionsResponse({
|
|
252
|
+
asset: this.asset,
|
|
253
|
+
fromAddress: derivedData.ourWalletAddress,
|
|
254
|
+
response,
|
|
255
|
+
walletAccount,
|
|
256
|
+
}))
|
|
257
|
+
|
|
258
|
+
hasNewTxs = allTxs.length > 0
|
|
259
|
+
|
|
260
|
+
const logItemsByAsset = this.getAllLogItemsByAsset({
|
|
261
|
+
getLogItemsFromServerTx,
|
|
262
|
+
ourWalletAddress: derivedData.ourWalletAddress,
|
|
263
|
+
allTransactionsFromServer: allTxs,
|
|
264
|
+
asset: this.asset,
|
|
265
|
+
tokensByAddress,
|
|
266
|
+
assets,
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const { txsToRemove } = await this.checkPendingTransactions({
|
|
270
|
+
txlist: allTxs,
|
|
271
|
+
walletAccount,
|
|
272
|
+
refresh,
|
|
273
|
+
logItemsByAsset,
|
|
274
|
+
asset: this.asset,
|
|
275
|
+
...derivedData,
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
this.aci.removeTxLogBatch({
|
|
279
|
+
assetName,
|
|
280
|
+
walletAccount,
|
|
281
|
+
txs: txsToRemove,
|
|
282
|
+
batch,
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
for (const [assetName, txs] of Object.entries(logItemsByAsset)) {
|
|
286
|
+
this.aci.updateTxLogAndNotifyBatch({
|
|
287
|
+
assetName,
|
|
288
|
+
walletAccount,
|
|
289
|
+
txs,
|
|
290
|
+
refresh,
|
|
291
|
+
batch,
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
historyResponse = response
|
|
296
|
+
processedLogItemsByAsset = logItemsByAsset
|
|
297
|
+
} catch (error) {
|
|
298
|
+
historyError = error
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const newData = {
|
|
302
|
+
...(await this.resolveAccountState({
|
|
303
|
+
derivedData,
|
|
304
|
+
tokens,
|
|
305
|
+
tokensByAddress,
|
|
306
|
+
historyResponse,
|
|
307
|
+
logItemsByAsset: processedLogItemsByAsset,
|
|
308
|
+
refresh,
|
|
309
|
+
})),
|
|
310
|
+
...(historyResponse && { clarityCursor: historyResponse.cursor }),
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
// Persist balance and account metadata even when the history path fails
|
|
315
|
+
// during startup bursts or other temporary backend pressure.
|
|
316
|
+
this.aci.updateAccountStateBatch({
|
|
317
|
+
assetName,
|
|
318
|
+
walletAccount,
|
|
319
|
+
accountState: derivedData.currentAccountState,
|
|
320
|
+
newData,
|
|
321
|
+
batch,
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
await this.aci.executeOperationsBatch(batch)
|
|
325
|
+
} catch (batchError) {
|
|
326
|
+
if (!historyError) throw batchError
|
|
327
|
+
this.logger.warn('error persisting account state after history failure', batchError)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (historyError) {
|
|
331
|
+
throw historyError instanceof Error
|
|
332
|
+
? historyError
|
|
333
|
+
: new Error(String(historyError), { cause: historyError })
|
|
334
|
+
}
|
|
159
335
|
|
|
160
|
-
await this.removeFromTxLog(txsToRemove)
|
|
161
|
-
await this.updateTxLogByAsset({ logItemsByAsset, walletAccount, refresh })
|
|
162
336
|
if (refresh || hasNewTxs) {
|
|
163
|
-
const unknownTokenAddresses = this.
|
|
337
|
+
const unknownTokenAddresses = this.getUnknownTokenAddresses({
|
|
164
338
|
transactions: allTxs,
|
|
165
339
|
tokensByAddress,
|
|
166
340
|
})
|
|
@@ -170,24 +344,40 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
170
344
|
}
|
|
171
345
|
}
|
|
172
346
|
|
|
173
|
-
async
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
347
|
+
async resolveAccountState({ derivedData, tokens }) {
|
|
348
|
+
return this.getNewAccountState({
|
|
349
|
+
tokens,
|
|
350
|
+
currentTokenBalances: derivedData.currentAccountState?.tokenBalances,
|
|
351
|
+
ourWalletAddress: derivedData.ourWalletAddress,
|
|
178
352
|
})
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async getSecurityAccountState({ derivedData, shouldCheckBlacklist }) {
|
|
356
|
+
const eip7702Delegation = await getCurrentEIP7702Delegation({
|
|
357
|
+
server: this.server,
|
|
358
|
+
address: derivedData.ourWalletAddress,
|
|
359
|
+
eip7702Supported: this.eip7702Whitelist.get(),
|
|
360
|
+
currentDelegation: derivedData.currentAccountState?.eip7702Delegation,
|
|
361
|
+
logger: this.logger,
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
const isBlacklisted = shouldCheckBlacklist
|
|
365
|
+
? await getCurrentBlackListStatus({
|
|
366
|
+
getBlackListStatus: this.getBlackListStatus,
|
|
367
|
+
address: derivedData.ourWalletAddress,
|
|
368
|
+
currentIsBlacklisted: derivedData.currentAccountState?.isBlacklisted,
|
|
369
|
+
logger: this.logger,
|
|
370
|
+
})
|
|
371
|
+
: undefined
|
|
372
|
+
|
|
373
|
+
return { eip7702Delegation, isBlacklisted }
|
|
184
374
|
}
|
|
185
375
|
|
|
186
376
|
async getNewAccountState({ tokens, currentTokenBalances, ourWalletAddress }) {
|
|
187
377
|
const asset = this.asset
|
|
188
|
-
const newAccountState =
|
|
378
|
+
const newAccountState = Object.create(null)
|
|
189
379
|
const balances = await this.getBalances({ tokens, ourWalletAddress })
|
|
190
|
-
if (
|
|
380
|
+
if (this.rpcBalanceAssetNames.includes(asset.name)) {
|
|
191
381
|
const balance = balances[asset.name]
|
|
192
382
|
newAccountState.balance = asset.currency.baseUnit(balance)
|
|
193
383
|
}
|
|
@@ -208,7 +398,7 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
208
398
|
|
|
209
399
|
async getReceiveAddressesByWalletAccount() {
|
|
210
400
|
const walletAccounts = await this.aci.getWalletAccounts({ assetName: this.asset.name })
|
|
211
|
-
const addressesByAccount =
|
|
401
|
+
const addressesByAccount = Object.create(null)
|
|
212
402
|
for (const walletAccount of walletAccounts) {
|
|
213
403
|
addressesByAccount[walletAccount] = await this.aci.getReceiveAddresses({
|
|
214
404
|
assetName: this.asset.name,
|
|
@@ -223,42 +413,28 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
223
413
|
async subscribeWalletAddresses() {
|
|
224
414
|
const addressesByWalletAccount = await this.getReceiveAddressesByWalletAccount()
|
|
225
415
|
Object.entries(addressesByWalletAccount).forEach(([walletAccount, addresses]) => {
|
|
226
|
-
|
|
227
|
-
|
|
416
|
+
try {
|
|
417
|
+
const [rawAddress] = addresses
|
|
418
|
+
assert(rawAddress, 'rawAddress should be defined')
|
|
419
|
+
|
|
420
|
+
const address = String(rawAddress).toLowerCase() // Only check m/0/0
|
|
421
|
+
this.server.connectTransactions({ walletAccount, address })
|
|
422
|
+
} catch (e) {
|
|
423
|
+
this.logger.error(e)
|
|
424
|
+
}
|
|
228
425
|
})
|
|
229
426
|
}
|
|
230
427
|
|
|
231
428
|
async getBalances({ tokens, ourWalletAddress }) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
for (const token of tokens) {
|
|
239
|
-
if (isRpcBalanceAsset(token) && token.contract.address) {
|
|
240
|
-
const request = this.server.balanceOfRequest(ourWalletAddress, token.contract.address)
|
|
241
|
-
batch[token.name] = request
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const pairs = Object.entries(batch)
|
|
246
|
-
if (pairs.length === 0) {
|
|
247
|
-
return {}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const requests = pairs.map((pair) => pair[1])
|
|
251
|
-
const responses = await this.server.sendBatchRequest(requests)
|
|
252
|
-
const entries = pairs.map((pair, idx) => {
|
|
253
|
-
const balanceHex = responses[idx]
|
|
254
|
-
const name = pair[0]
|
|
255
|
-
const balance = fromHexToString(balanceHex)
|
|
256
|
-
return [name, balance]
|
|
429
|
+
return getBatchedRpcBalances({
|
|
430
|
+
baseAsset: this.asset,
|
|
431
|
+
ourWalletAddress,
|
|
432
|
+
tokens,
|
|
433
|
+
rpcBalanceAssetNames: this.rpcBalanceAssetNames,
|
|
257
434
|
})
|
|
258
|
-
return Object.fromEntries(entries)
|
|
259
435
|
}
|
|
260
436
|
|
|
261
|
-
|
|
437
|
+
getUnknownTokenAddresses({ transactions, tokensByAddress }) {
|
|
262
438
|
const set = transactions.reduce((acc, txn) => {
|
|
263
439
|
const transfers = filterEffects(txn.effects, 'erc20') || []
|
|
264
440
|
transfers.forEach((transfer) => {
|
|
@@ -272,24 +448,21 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
272
448
|
return [...set]
|
|
273
449
|
}
|
|
274
450
|
|
|
275
|
-
|
|
451
|
+
// NOTE: Here, fetchedGasPrices is the result of a call to `ClarityMonitor.getFee()`.
|
|
452
|
+
async updateGasPrice(fetchedGasPrices) {
|
|
276
453
|
try {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
this.logger.debug(
|
|
283
|
-
`Update ${this.asset.name} gas price: ${feeConfig.gasPrice}, baseFeePerGas: ${feeConfig.baseFeePerGas}`
|
|
284
|
-
)
|
|
285
|
-
await this.aci.updateFeeConfig({ assetName: this.asset.name, feeConfig })
|
|
454
|
+
await executeEthLikeFeeMonitorUpdate({
|
|
455
|
+
assetClientInterface: this.aci,
|
|
456
|
+
feeAsset: this.asset,
|
|
457
|
+
fetchedGasPrices,
|
|
458
|
+
})
|
|
286
459
|
} catch (e) {
|
|
287
460
|
this.logger.warn('error updating gasPrice', e)
|
|
288
461
|
}
|
|
289
462
|
}
|
|
290
463
|
|
|
291
464
|
async onTransaction({ walletAccount }) {
|
|
292
|
-
return this.
|
|
465
|
+
return this.tickWalletAccounts({ walletAccount })
|
|
293
466
|
}
|
|
294
467
|
|
|
295
468
|
async onFeeUpdated(fee) {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { ClarityMonitor } from './clarity-monitor.js'
|
|
2
|
+
import { extractBalanceFromClarityAccountInfo } from './monitor-utils/index.js'
|
|
3
|
+
|
|
4
|
+
const omitTokensWithConfirmedBalanceChange = ({ tokenBalances, logItemsByAsset }) => {
|
|
5
|
+
if (!tokenBalances || !logItemsByAsset) return tokenBalances
|
|
6
|
+
const result = { ...tokenBalances }
|
|
7
|
+
for (const [assetName, txs] of Object.entries(logItemsByAsset)) {
|
|
8
|
+
if (txs.some((tx) => tx.confirmations > 0 && tx.data?.balanceChange)) delete result[assetName]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return result
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Clarity v2 + HTTP `getAllTransactions` with `truncated: true` so the backend can attach
|
|
16
|
+
* `accountInfo` (pre-cursor balance/nonce/token snapshots). Merges that data in
|
|
17
|
+
* `resolveAccountState` before optional RPC follow-up; does not use `rpcBalanceAssetNames`
|
|
18
|
+
* (forced empty).
|
|
19
|
+
*/
|
|
20
|
+
export class ClarityTruncatedHistoryMonitor extends ClarityMonitor {
|
|
21
|
+
constructor({ rpcBalanceAssetNames, ...args } = {}) {
|
|
22
|
+
super({ ...args, rpcBalanceAssetNames: [] })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getStateUpdate(_args) {
|
|
26
|
+
return { accountState: Object.create(null) }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async resolveAccountState({
|
|
30
|
+
derivedData,
|
|
31
|
+
tokens,
|
|
32
|
+
tokensByAddress,
|
|
33
|
+
historyResponse,
|
|
34
|
+
logItemsByAsset,
|
|
35
|
+
refresh,
|
|
36
|
+
}) {
|
|
37
|
+
let currentAccountState = derivedData.currentAccountState
|
|
38
|
+
let resetBalances
|
|
39
|
+
|
|
40
|
+
// When refreshing, we need to clear out old asset
|
|
41
|
+
// balances in `accountState` since these can surface
|
|
42
|
+
// values populated from alternative monitors i.e.
|
|
43
|
+
// `no-history`, which can be written at arbitrary
|
|
44
|
+
// points in time.
|
|
45
|
+
//
|
|
46
|
+
// Since the `truncated-history-monitor` does not
|
|
47
|
+
// guarantee an `accountState` write for an asset,
|
|
48
|
+
// (these are only provided when the history is
|
|
49
|
+
// truncated), we must ensure older checkpoints cannot
|
|
50
|
+
// be inadvertently relied upon.
|
|
51
|
+
//
|
|
52
|
+
if (refresh) {
|
|
53
|
+
const defaultAccountState = this.asset.api.createAccountState().create()
|
|
54
|
+
resetBalances = {
|
|
55
|
+
balance: defaultAccountState.balance,
|
|
56
|
+
tokenBalances: defaultAccountState.tokenBalances,
|
|
57
|
+
}
|
|
58
|
+
currentAccountState = (currentAccountState ?? defaultAccountState).merge(resetBalances)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const balanceFromAccountInfo = extractBalanceFromClarityAccountInfo({
|
|
62
|
+
accountInfo: historyResponse?.accountInfo,
|
|
63
|
+
asset: this.asset,
|
|
64
|
+
tokensByAddress,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const prevTokenBalances = omitTokensWithConfirmedBalanceChange({
|
|
68
|
+
tokenBalances: currentAccountState?.tokenBalances,
|
|
69
|
+
logItemsByAsset,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const { accountState } = await this.getStateUpdate({
|
|
73
|
+
derivedData,
|
|
74
|
+
tokens,
|
|
75
|
+
currentTokenBalances: {
|
|
76
|
+
...prevTokenBalances,
|
|
77
|
+
...balanceFromAccountInfo.tokenBalances,
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Clarity `accountInfo`, then RPC `accountState` (empty here) overlays.
|
|
82
|
+
const prevTruncatedAccountState = currentAccountState?.truncatedAccountState
|
|
83
|
+
const newData = {
|
|
84
|
+
...resetBalances,
|
|
85
|
+
...balanceFromAccountInfo,
|
|
86
|
+
...accountState,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (prevTokenBalances || balanceFromAccountInfo.tokenBalances || accountState.tokenBalances) {
|
|
90
|
+
newData.tokenBalances = {
|
|
91
|
+
...prevTokenBalances,
|
|
92
|
+
...balanceFromAccountInfo.tokenBalances,
|
|
93
|
+
...accountState.tokenBalances,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (
|
|
98
|
+
prevTruncatedAccountState ||
|
|
99
|
+
balanceFromAccountInfo.truncatedAccountState ||
|
|
100
|
+
accountState.truncatedAccountState
|
|
101
|
+
) {
|
|
102
|
+
newData.truncatedAccountState = {
|
|
103
|
+
...prevTruncatedAccountState,
|
|
104
|
+
...balanceFromAccountInfo.truncatedAccountState,
|
|
105
|
+
...accountState.truncatedAccountState,
|
|
106
|
+
tokenBalances: {
|
|
107
|
+
...prevTruncatedAccountState?.tokenBalances,
|
|
108
|
+
...balanceFromAccountInfo.truncatedAccountState?.tokenBalances,
|
|
109
|
+
...accountState.truncatedAccountState?.tokenBalances,
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return newData
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async getHistoryFromServer({ walletAccount, derivedData, refresh }) {
|
|
118
|
+
const address = derivedData.ourWalletAddress
|
|
119
|
+
const currentCursor = derivedData.currentAccountState?.clarityCursor
|
|
120
|
+
const cursor = currentCursor && !refresh ? currentCursor : null
|
|
121
|
+
return this.server.getAllTransactions({
|
|
122
|
+
walletAccount,
|
|
123
|
+
address,
|
|
124
|
+
cursor,
|
|
125
|
+
refresh,
|
|
126
|
+
withInput: true,
|
|
127
|
+
truncated: true,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|