@exodus/ethereum-api 9.0.0-alpha.3 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2121 -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 +272 -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 +273 -103
- package/src/tx-log/clarity-truncated-history-monitor.js +109 -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,113 @@ 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
|
+
|
|
247
|
+
try {
|
|
248
|
+
const response = await this.getHistoryFromServer({ walletAccount, derivedData, refresh })
|
|
249
|
+
|
|
250
|
+
;({ allTxs } = await normalizeTransactionsResponse({
|
|
251
|
+
asset: this.asset,
|
|
252
|
+
fromAddress: derivedData.ourWalletAddress,
|
|
253
|
+
response,
|
|
254
|
+
walletAccount,
|
|
255
|
+
}))
|
|
256
|
+
|
|
257
|
+
hasNewTxs = allTxs.length > 0
|
|
258
|
+
|
|
259
|
+
const logItemsByAsset = this.getAllLogItemsByAsset({
|
|
260
|
+
getLogItemsFromServerTx,
|
|
261
|
+
ourWalletAddress: derivedData.ourWalletAddress,
|
|
262
|
+
allTransactionsFromServer: allTxs,
|
|
263
|
+
asset: this.asset,
|
|
264
|
+
tokensByAddress,
|
|
265
|
+
assets,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const { txsToRemove } = await this.checkPendingTransactions({
|
|
269
|
+
txlist: allTxs,
|
|
270
|
+
walletAccount,
|
|
271
|
+
refresh,
|
|
272
|
+
logItemsByAsset,
|
|
273
|
+
asset: this.asset,
|
|
274
|
+
...derivedData,
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
this.aci.removeTxLogBatch({
|
|
278
|
+
assetName,
|
|
279
|
+
walletAccount,
|
|
280
|
+
txs: txsToRemove,
|
|
281
|
+
batch,
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
for (const [assetName, txs] of Object.entries(logItemsByAsset)) {
|
|
285
|
+
this.aci.updateTxLogAndNotifyBatch({
|
|
286
|
+
assetName,
|
|
287
|
+
walletAccount,
|
|
288
|
+
txs,
|
|
289
|
+
refresh,
|
|
290
|
+
batch,
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
historyResponse = response
|
|
295
|
+
} catch (error) {
|
|
296
|
+
historyError = error
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const newData = {
|
|
300
|
+
...(await this.resolveAccountState({
|
|
301
|
+
derivedData,
|
|
302
|
+
tokens,
|
|
303
|
+
tokensByAddress,
|
|
304
|
+
historyResponse,
|
|
305
|
+
refresh,
|
|
306
|
+
})),
|
|
307
|
+
...(historyResponse && { clarityCursor: historyResponse.cursor }),
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
// Persist balance and account metadata even when the history path fails
|
|
312
|
+
// during startup bursts or other temporary backend pressure.
|
|
313
|
+
this.aci.updateAccountStateBatch({
|
|
314
|
+
assetName,
|
|
315
|
+
walletAccount,
|
|
316
|
+
accountState: derivedData.currentAccountState,
|
|
317
|
+
newData,
|
|
318
|
+
batch,
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
await this.aci.executeOperationsBatch(batch)
|
|
322
|
+
} catch (batchError) {
|
|
323
|
+
if (!historyError) throw batchError
|
|
324
|
+
this.logger.warn('error persisting account state after history failure', batchError)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (historyError) {
|
|
328
|
+
throw historyError instanceof Error
|
|
329
|
+
? historyError
|
|
330
|
+
: new Error(String(historyError), { cause: historyError })
|
|
331
|
+
}
|
|
159
332
|
|
|
160
|
-
await this.removeFromTxLog(txsToRemove)
|
|
161
|
-
await this.updateTxLogByAsset({ logItemsByAsset, walletAccount, refresh })
|
|
162
333
|
if (refresh || hasNewTxs) {
|
|
163
|
-
const unknownTokenAddresses = this.
|
|
334
|
+
const unknownTokenAddresses = this.getUnknownTokenAddresses({
|
|
164
335
|
transactions: allTxs,
|
|
165
336
|
tokensByAddress,
|
|
166
337
|
})
|
|
@@ -170,24 +341,40 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
170
341
|
}
|
|
171
342
|
}
|
|
172
343
|
|
|
173
|
-
async
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
344
|
+
async resolveAccountState({ derivedData, tokens }) {
|
|
345
|
+
return this.getNewAccountState({
|
|
346
|
+
tokens,
|
|
347
|
+
currentTokenBalances: derivedData.currentAccountState?.tokenBalances,
|
|
348
|
+
ourWalletAddress: derivedData.ourWalletAddress,
|
|
178
349
|
})
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async getSecurityAccountState({ derivedData, shouldCheckBlacklist }) {
|
|
353
|
+
const eip7702Delegation = await getCurrentEIP7702Delegation({
|
|
354
|
+
server: this.server,
|
|
355
|
+
address: derivedData.ourWalletAddress,
|
|
356
|
+
eip7702Supported: this.eip7702Whitelist.get(),
|
|
357
|
+
currentDelegation: derivedData.currentAccountState?.eip7702Delegation,
|
|
358
|
+
logger: this.logger,
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
const isBlacklisted = shouldCheckBlacklist
|
|
362
|
+
? await getCurrentBlackListStatus({
|
|
363
|
+
getBlackListStatus: this.getBlackListStatus,
|
|
364
|
+
address: derivedData.ourWalletAddress,
|
|
365
|
+
currentIsBlacklisted: derivedData.currentAccountState?.isBlacklisted,
|
|
366
|
+
logger: this.logger,
|
|
367
|
+
})
|
|
368
|
+
: undefined
|
|
369
|
+
|
|
370
|
+
return { eip7702Delegation, isBlacklisted }
|
|
184
371
|
}
|
|
185
372
|
|
|
186
373
|
async getNewAccountState({ tokens, currentTokenBalances, ourWalletAddress }) {
|
|
187
374
|
const asset = this.asset
|
|
188
|
-
const newAccountState =
|
|
375
|
+
const newAccountState = Object.create(null)
|
|
189
376
|
const balances = await this.getBalances({ tokens, ourWalletAddress })
|
|
190
|
-
if (
|
|
377
|
+
if (this.rpcBalanceAssetNames.includes(asset.name)) {
|
|
191
378
|
const balance = balances[asset.name]
|
|
192
379
|
newAccountState.balance = asset.currency.baseUnit(balance)
|
|
193
380
|
}
|
|
@@ -208,7 +395,7 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
208
395
|
|
|
209
396
|
async getReceiveAddressesByWalletAccount() {
|
|
210
397
|
const walletAccounts = await this.aci.getWalletAccounts({ assetName: this.asset.name })
|
|
211
|
-
const addressesByAccount =
|
|
398
|
+
const addressesByAccount = Object.create(null)
|
|
212
399
|
for (const walletAccount of walletAccounts) {
|
|
213
400
|
addressesByAccount[walletAccount] = await this.aci.getReceiveAddresses({
|
|
214
401
|
assetName: this.asset.name,
|
|
@@ -223,42 +410,28 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
223
410
|
async subscribeWalletAddresses() {
|
|
224
411
|
const addressesByWalletAccount = await this.getReceiveAddressesByWalletAccount()
|
|
225
412
|
Object.entries(addressesByWalletAccount).forEach(([walletAccount, addresses]) => {
|
|
226
|
-
|
|
227
|
-
|
|
413
|
+
try {
|
|
414
|
+
const [rawAddress] = addresses
|
|
415
|
+
assert(rawAddress, 'rawAddress should be defined')
|
|
416
|
+
|
|
417
|
+
const address = String(rawAddress).toLowerCase() // Only check m/0/0
|
|
418
|
+
this.server.connectTransactions({ walletAccount, address })
|
|
419
|
+
} catch (e) {
|
|
420
|
+
this.logger.error(e)
|
|
421
|
+
}
|
|
228
422
|
})
|
|
229
423
|
}
|
|
230
424
|
|
|
231
425
|
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]
|
|
426
|
+
return getBatchedRpcBalances({
|
|
427
|
+
baseAsset: this.asset,
|
|
428
|
+
ourWalletAddress,
|
|
429
|
+
tokens,
|
|
430
|
+
rpcBalanceAssetNames: this.rpcBalanceAssetNames,
|
|
257
431
|
})
|
|
258
|
-
return Object.fromEntries(entries)
|
|
259
432
|
}
|
|
260
433
|
|
|
261
|
-
|
|
434
|
+
getUnknownTokenAddresses({ transactions, tokensByAddress }) {
|
|
262
435
|
const set = transactions.reduce((acc, txn) => {
|
|
263
436
|
const transfers = filterEffects(txn.effects, 'erc20') || []
|
|
264
437
|
transfers.forEach((transfer) => {
|
|
@@ -272,24 +445,21 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
272
445
|
return [...set]
|
|
273
446
|
}
|
|
274
447
|
|
|
275
|
-
|
|
448
|
+
// NOTE: Here, fetchedGasPrices is the result of a call to `ClarityMonitor.getFee()`.
|
|
449
|
+
async updateGasPrice(fetchedGasPrices) {
|
|
276
450
|
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 })
|
|
451
|
+
await executeEthLikeFeeMonitorUpdate({
|
|
452
|
+
assetClientInterface: this.aci,
|
|
453
|
+
feeAsset: this.asset,
|
|
454
|
+
fetchedGasPrices,
|
|
455
|
+
})
|
|
286
456
|
} catch (e) {
|
|
287
457
|
this.logger.warn('error updating gasPrice', e)
|
|
288
458
|
}
|
|
289
459
|
}
|
|
290
460
|
|
|
291
461
|
async onTransaction({ walletAccount }) {
|
|
292
|
-
return this.
|
|
462
|
+
return this.tickWalletAccounts({ walletAccount })
|
|
293
463
|
}
|
|
294
464
|
|
|
295
465
|
async onFeeUpdated(fee) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ClarityMonitor } from './clarity-monitor.js'
|
|
2
|
+
import { extractBalanceFromClarityAccountInfo } from './monitor-utils/index.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Clarity v2 + HTTP `getAllTransactions` with `truncated: true` so the backend can attach
|
|
6
|
+
* `accountInfo` (pre-cursor balance/nonce/token snapshots). Merges that data in
|
|
7
|
+
* `resolveAccountState` before optional RPC follow-up; does not use `rpcBalanceAssetNames`
|
|
8
|
+
* (forced empty).
|
|
9
|
+
*/
|
|
10
|
+
export class ClarityTruncatedHistoryMonitor extends ClarityMonitor {
|
|
11
|
+
constructor({ rpcBalanceAssetNames, ...args } = {}) {
|
|
12
|
+
super({ ...args, rpcBalanceAssetNames: [] })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async getStateUpdate(_args) {
|
|
16
|
+
return { accountState: Object.create(null) }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async resolveAccountState({ derivedData, tokens, tokensByAddress, historyResponse, refresh }) {
|
|
20
|
+
let currentAccountState = derivedData.currentAccountState
|
|
21
|
+
let resetBalances
|
|
22
|
+
|
|
23
|
+
// When refreshing, we need to clear out old asset
|
|
24
|
+
// balances in `accountState` since these can surface
|
|
25
|
+
// values populated from alternative monitors i.e.
|
|
26
|
+
// `no-history`, which can be written at arbitrary
|
|
27
|
+
// points in time.
|
|
28
|
+
//
|
|
29
|
+
// Since the `truncated-history-monitor` does not
|
|
30
|
+
// guarantee an `accountState` write for an asset,
|
|
31
|
+
// (these are only provided when the history is
|
|
32
|
+
// truncated), we must ensure older checkpoints cannot
|
|
33
|
+
// be inadvertently relied upon.
|
|
34
|
+
//
|
|
35
|
+
if (refresh) {
|
|
36
|
+
const defaultAccountState = this.asset.api.createAccountState().create()
|
|
37
|
+
resetBalances = {
|
|
38
|
+
balance: defaultAccountState.balance,
|
|
39
|
+
tokenBalances: defaultAccountState.tokenBalances,
|
|
40
|
+
}
|
|
41
|
+
currentAccountState = (currentAccountState ?? defaultAccountState).merge(resetBalances)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const balanceFromAccountInfo = extractBalanceFromClarityAccountInfo({
|
|
45
|
+
accountInfo: historyResponse?.accountInfo,
|
|
46
|
+
asset: this.asset,
|
|
47
|
+
tokensByAddress,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const { accountState } = await this.getStateUpdate({
|
|
51
|
+
derivedData,
|
|
52
|
+
tokens,
|
|
53
|
+
currentTokenBalances: {
|
|
54
|
+
...currentAccountState?.tokenBalances,
|
|
55
|
+
...balanceFromAccountInfo.tokenBalances,
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Clarity `accountInfo`, then RPC `accountState` (empty here) overlays.
|
|
60
|
+
const prevTokenBalances = currentAccountState?.tokenBalances
|
|
61
|
+
const prevTruncatedAccountState = currentAccountState?.truncatedAccountState
|
|
62
|
+
const newData = {
|
|
63
|
+
...resetBalances,
|
|
64
|
+
...balanceFromAccountInfo,
|
|
65
|
+
...accountState,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (prevTokenBalances || balanceFromAccountInfo.tokenBalances || accountState.tokenBalances) {
|
|
69
|
+
newData.tokenBalances = {
|
|
70
|
+
...prevTokenBalances,
|
|
71
|
+
...balanceFromAccountInfo.tokenBalances,
|
|
72
|
+
...accountState.tokenBalances,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
prevTruncatedAccountState ||
|
|
78
|
+
balanceFromAccountInfo.truncatedAccountState ||
|
|
79
|
+
accountState.truncatedAccountState
|
|
80
|
+
) {
|
|
81
|
+
newData.truncatedAccountState = {
|
|
82
|
+
...prevTruncatedAccountState,
|
|
83
|
+
...balanceFromAccountInfo.truncatedAccountState,
|
|
84
|
+
...accountState.truncatedAccountState,
|
|
85
|
+
tokenBalances: {
|
|
86
|
+
...prevTruncatedAccountState?.tokenBalances,
|
|
87
|
+
...balanceFromAccountInfo.truncatedAccountState?.tokenBalances,
|
|
88
|
+
...accountState.truncatedAccountState?.tokenBalances,
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return newData
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getHistoryFromServer({ walletAccount, derivedData, refresh }) {
|
|
97
|
+
const address = derivedData.ourWalletAddress
|
|
98
|
+
const currentCursor = derivedData.currentAccountState?.clarityCursor
|
|
99
|
+
const cursor = currentCursor && !refresh ? currentCursor : null
|
|
100
|
+
return this.server.getAllTransactions({
|
|
101
|
+
walletAccount,
|
|
102
|
+
address,
|
|
103
|
+
cursor,
|
|
104
|
+
refresh,
|
|
105
|
+
withInput: true,
|
|
106
|
+
truncated: true,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|