@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 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.31.1",
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": "4bf6290a71108efdd1e20d48d7898dd7b38aff4d",
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 [accountInfoRaw, tokensPayload, delegatedBalances] = await Promise.all([
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
- let ownerChanged = accountState.ownerChanged ?? false
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 [accountInfo, { balances: splBalances, accounts: tokenAccounts }, delegatedBalances] =
260
- await Promise.all([
261
- this.rpcApi.getAccountInfo(address).catch(() => {}),
262
- this.rpcApi.getTokensBalancesAndAccounts({
263
- address,
264
- filterByTokens: tokens,
265
- }),
266
- this.rpcApi.fetchDelegatedBalances({
267
- delegatedAccounts,
268
- address,
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
  )