@exodus/solana-api 3.31.1 → 3.32.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 +10 -0
- package/package.json +2 -2
- package/src/tx-log/clarity-monitor.js +34 -10
- package/src/tx-log/solana-monitor.js +38 -14
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
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
|
+
## [3.32.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.31.1...@exodus/solana-api@3.32.0) (2026-05-01)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* feat: Faster Security Hydration Across Monitor (#7824)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
6
16
|
## [3.31.1](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.31.0...@exodus/solana-api@3.31.1) (2026-04-29)
|
|
7
17
|
|
|
8
18
|
**Note:** Version bump only for package @exodus/solana-api
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-api",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.32.0",
|
|
4
4
|
"description": "Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Solana",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@exodus/assets-testing": "^1.0.0",
|
|
50
50
|
"@exodus/solana-web3.js": "^1.63.1-exodus.9-rc3"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "d17ee07f3f8bf4cc67518f8a8395522578e8dae1",
|
|
53
53
|
"bugs": {
|
|
54
54
|
"url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
|
|
55
55
|
},
|
|
@@ -89,6 +89,21 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
89
89
|
)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
async persistSecurityState({ walletAccount, accountState, ownerChanged }) {
|
|
93
|
+
const securityStatePatch =
|
|
94
|
+
accountState.ownerChanged === ownerChanged ? Object.create(null) : { ownerChanged }
|
|
95
|
+
|
|
96
|
+
if (lodash.isEmpty(securityStatePatch)) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await this.updateAccountState({
|
|
101
|
+
walletAccount,
|
|
102
|
+
accountState,
|
|
103
|
+
newData: securityStatePatch,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
#normalizeAccountInfoPayload(accountInfo) {
|
|
93
108
|
if (!accountInfo) return accountInfo
|
|
94
109
|
const inner = accountInfo.value
|
|
@@ -150,6 +165,7 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
150
165
|
accountState,
|
|
151
166
|
walletAccount,
|
|
152
167
|
})
|
|
168
|
+
|
|
153
169
|
const balanceChanged = this.#balanceChanged({ account: accountState, newAccount: account })
|
|
154
170
|
|
|
155
171
|
const isHistoryUpdateTick =
|
|
@@ -431,8 +447,7 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
431
447
|
|
|
432
448
|
async getAccountsAndBalances({ refresh, address, accountState, walletAccount }) {
|
|
433
449
|
const delegatedAccounts = accountState.tokenDelegationInfo?.delegatedAccounts || []
|
|
434
|
-
const
|
|
435
|
-
this.#fetchAccountInfoWithRetry(address),
|
|
450
|
+
const tokenAccountDataPromise = Promise.all([
|
|
436
451
|
this.clarityApi.getTokensBalancesAndAccounts({ address }).catch((error) => {
|
|
437
452
|
console.warn('SolanaClarityMonitor getTokensBalancesAndAccounts failed', { address, error })
|
|
438
453
|
return null
|
|
@@ -442,6 +457,7 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
442
457
|
return accountState.tokenDelegationInfo?.delegatedBalances ?? Object.create(null)
|
|
443
458
|
}),
|
|
444
459
|
])
|
|
460
|
+
const accountInfoRaw = await this.#fetchAccountInfoWithRetry(address)
|
|
445
461
|
|
|
446
462
|
const accountInfo = this.#normalizeAccountInfoPayload(accountInfoRaw)
|
|
447
463
|
const hasAccountInfo = accountInfo != null
|
|
@@ -450,6 +466,21 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
450
466
|
? this.asset.currency.baseUnit(accountInfo.lamports ?? 0)
|
|
451
467
|
: accountState.balance ?? this.asset.currency.ZERO
|
|
452
468
|
|
|
469
|
+
let ownerChanged = accountState.ownerChanged ?? false
|
|
470
|
+
if (hasAccountInfo) {
|
|
471
|
+
try {
|
|
472
|
+
ownerChanged = await this.clarityApi.ownerChanged(address, accountInfo)
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.warn('SolanaClarityMonitor ownerChanged failed', { address, error })
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
await this.persistSecurityState({
|
|
479
|
+
walletAccount,
|
|
480
|
+
accountState,
|
|
481
|
+
ownerChanged,
|
|
482
|
+
})
|
|
483
|
+
|
|
453
484
|
let rentExemptAmount = accountState.rentExemptAmount ?? this.asset.currency.ZERO
|
|
454
485
|
if (hasAccountInfo) {
|
|
455
486
|
try {
|
|
@@ -465,14 +496,7 @@ export class SolanaClarityMonitor extends BaseMonitor {
|
|
|
465
496
|
}
|
|
466
497
|
}
|
|
467
498
|
|
|
468
|
-
|
|
469
|
-
if (hasAccountInfo) {
|
|
470
|
-
try {
|
|
471
|
-
ownerChanged = await this.clarityApi.ownerChanged(address, accountInfo)
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.warn('SolanaClarityMonitor ownerChanged failed', { address, error })
|
|
474
|
-
}
|
|
475
|
-
}
|
|
499
|
+
const [tokensPayload, delegatedBalances] = await tokenAccountDataPromise
|
|
476
500
|
|
|
477
501
|
const splBalances =
|
|
478
502
|
tokensPayload?.balances && typeof tokensPayload.balances === 'object'
|
|
@@ -83,6 +83,21 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
83
83
|
)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
async persistSecurityState({ walletAccount, accountState, ownerChanged }) {
|
|
87
|
+
const securityStatePatch =
|
|
88
|
+
accountState.ownerChanged === ownerChanged ? Object.create(null) : { ownerChanged }
|
|
89
|
+
|
|
90
|
+
if (_.isEmpty(securityStatePatch)) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await this.updateAccountState({
|
|
95
|
+
walletAccount,
|
|
96
|
+
accountState,
|
|
97
|
+
newData: securityStatePatch,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
86
101
|
async markStaleTransactions({ walletAccount, logItemsByAsset = Object.create(null) }) {
|
|
87
102
|
// mark stale txs as dropped in logItemsByAsset
|
|
88
103
|
const clearedLogItems = logItemsByAsset
|
|
@@ -118,6 +133,7 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
118
133
|
accountState,
|
|
119
134
|
walletAccount,
|
|
120
135
|
})
|
|
136
|
+
|
|
121
137
|
const balanceChanged = this.#balanceChanged({ account: accountState, newAccount: account })
|
|
122
138
|
|
|
123
139
|
const isHistoryUpdateTick =
|
|
@@ -256,18 +272,28 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
256
272
|
async getAccountsAndBalances({ refresh, address, accountState, walletAccount }) {
|
|
257
273
|
const delegatedAccounts = accountState.tokenDelegationInfo?.delegatedAccounts || []
|
|
258
274
|
const tokens = Object.keys(this.assets).filter((name) => name !== this.asset.name)
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
275
|
+
const tokenBalancesPromise = this.rpcApi.getTokensBalancesAndAccounts({
|
|
276
|
+
address,
|
|
277
|
+
filterByTokens: tokens,
|
|
278
|
+
})
|
|
279
|
+
const delegatedBalancesPromise = this.rpcApi.fetchDelegatedBalances({
|
|
280
|
+
delegatedAccounts,
|
|
281
|
+
address,
|
|
282
|
+
})
|
|
283
|
+
// Keep background rejections observed while ownerChanged is persisted first.
|
|
284
|
+
void tokenBalancesPromise.catch(() => {})
|
|
285
|
+
void delegatedBalancesPromise.catch(() => {})
|
|
286
|
+
const accountInfo = await this.rpcApi.getAccountInfo(address)
|
|
287
|
+
|
|
288
|
+
const ownerChanged = await this.rpcApi.ownerChanged(address, accountInfo)
|
|
289
|
+
await this.persistSecurityState({
|
|
290
|
+
walletAccount,
|
|
291
|
+
accountState,
|
|
292
|
+
ownerChanged,
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const [{ balances: splBalances, accounts: tokenAccounts }, delegatedBalances] =
|
|
296
|
+
await Promise.all([tokenBalancesPromise, delegatedBalancesPromise])
|
|
271
297
|
|
|
272
298
|
const solBalance = accountInfo?.lamports || 0
|
|
273
299
|
|
|
@@ -277,8 +303,6 @@ export class SolanaMonitor extends BaseMonitor {
|
|
|
277
303
|
await this.rpcApi.getMinimumBalanceForRentExemption(accountSize)
|
|
278
304
|
)
|
|
279
305
|
|
|
280
|
-
const ownerChanged = await this.rpcApi.ownerChanged(address, accountInfo)
|
|
281
|
-
|
|
282
306
|
const tokenBalances = _.mapValues(splBalances, (balance, name) =>
|
|
283
307
|
this.assets[name].currency.baseUnit(balance)
|
|
284
308
|
)
|