@exodus/solana-api 3.30.5 → 3.30.7

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,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
+ ## [3.30.7](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.30.6...@exodus/solana-api@3.30.7) (2026-04-01)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: addressIsActive users tx history (#7708)
13
+
14
+
15
+
16
+ ## [3.30.6](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.30.5...@exodus/solana-api@3.30.6) (2026-03-30)
17
+
18
+ **Note:** Version bump only for package @exodus/solana-api
19
+
20
+
21
+
22
+
23
+
6
24
  ## [3.30.5](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.30.4...@exodus/solana-api@3.30.5) (2026-03-27)
7
25
 
8
26
  **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.30.5",
3
+ "version": "3.30.7",
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",
@@ -33,7 +33,7 @@
33
33
  "@exodus/fetch": "^1.7.3",
34
34
  "@exodus/models": "^13.0.0",
35
35
  "@exodus/simple-retry": "^0.0.6",
36
- "@exodus/solana-lib": "^3.22.2",
36
+ "@exodus/solana-lib": "^3.22.3",
37
37
  "@exodus/solana-meta": "^2.0.2",
38
38
  "@exodus/timer": "^1.1.1",
39
39
  "debug": "^4.1.1",
@@ -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": "07a8157aec179f656835db50568127788d4b91b6",
52
+ "gitHead": "3a8c58c8457a1d096ee5d3ecb2eea9e82b69ecb7",
53
53
  "bugs": {
54
54
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
55
55
  },
package/src/api.js CHANGED
@@ -12,9 +12,10 @@ import {
12
12
  findAssociatedTokenAddress,
13
13
  getMetadataAccount,
14
14
  getTransactionSimulationParams,
15
+ isSystemProgram,
16
+ isTokenProgram,
15
17
  SOL_DECIMAL,
16
18
  STAKE_PROGRAM_ID,
17
- SYSTEM_PROGRAM_ID,
18
19
  TOKEN_2022_PROGRAM_ID,
19
20
  TOKEN_PROGRAM_ID,
20
21
  } from '@exodus/solana-lib'
@@ -455,11 +456,7 @@ export class Api {
455
456
  const value = accountInfo || (await this.getAccountInfo(address))
456
457
  const owner = value?.owner // program owner
457
458
  if (!owner) return false // not initialized account (or purged)
458
- return ![
459
- SYSTEM_PROGRAM_ID.toBase58(),
460
- TOKEN_PROGRAM_ID.toBase58(),
461
- TOKEN_2022_PROGRAM_ID.toBase58(),
462
- ].includes(owner)
459
+ return !(isSystemProgram(owner) || isTokenProgram(owner))
463
460
  }
464
461
 
465
462
  async fetchValidatedDelegation({ delegatedAddress, expectedDelegate }) {
@@ -500,14 +497,13 @@ export class Api {
500
497
  }
501
498
 
502
499
  /**
503
- * Returns true if the account exists (has not been reclaimed).
504
- * Accounts may be reclaimed by the rent collector after their balance dropped below the
505
- * rent-exempt reserve. getAccountInfo only returns the current account state; if the
506
- * runtime deleted the account, RPC returns null.
500
+ * Returns true if the address has any transaction history.
501
+ * Solana system accounts can be reclaimed after staying below rent exemption, so
502
+ * current account info is not a reliable signal for whether an address was ever used.
507
503
  */
508
504
  async addressIsActive(address) {
509
- const value = await this.getAccountInfo(address)
510
- return !!value?.data
505
+ const signatures = await this.getSignaturesForAddress(address, { limit: 1 })
506
+ return signatures.length > 0
511
507
  }
512
508
 
513
509
  async isSpl(address) {
@@ -552,7 +548,7 @@ export class Api {
552
548
  lamports: value.lamports,
553
549
  }
554
550
 
555
- if (account.owner === SYSTEM_PROGRAM_ID.toBase58()) return 'solana'
551
+ if (isSystemProgram(account.owner)) return 'solana'
556
552
  if (account.owner === TOKEN_PROGRAM_ID.toBase58()) return 'token'
557
553
  if (account.owner === TOKEN_2022_PROGRAM_ID.toBase58()) return 'token-2022'
558
554
  return null
@@ -1,7 +1,7 @@
1
1
  import { memoizeLruCache } from '@exodus/asset-lib'
2
2
  import { isNil, memoize, omitBy } from '@exodus/basic-utils'
3
3
  import wretch from '@exodus/fetch/wretch'
4
- import { SYSTEM_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@exodus/solana-lib'
4
+ import { isSystemProgram, isTokenProgram, TOKEN_PROGRAM_ID } from '@exodus/solana-lib'
5
5
  import ms from 'ms'
6
6
  import urljoin from 'url-join'
7
7
 
@@ -205,11 +205,7 @@ export class ClarityApi extends RpcApi {
205
205
  const value = accountInfo || (await this.getAccountInfo(address))
206
206
  const owner = value?.owner // program owner
207
207
  if (!owner) return false // not initialized account (or purged)
208
- return ![
209
- SYSTEM_PROGRAM_ID.toBase58(),
210
- TOKEN_PROGRAM_ID.toBase58(),
211
- TOKEN_2022_PROGRAM_ID.toBase58(),
212
- ].includes(owner)
208
+ return !(isSystemProgram(owner) || isTokenProgram(owner))
213
209
  }
214
210
 
215
211
  ataOwnershipChangedCached = memoizeLruCache(
package/src/rpc-api.js CHANGED
@@ -10,8 +10,9 @@ import {
10
10
  findAssociatedTokenAddress,
11
11
  getMetadataAccount,
12
12
  getTransactionSimulationParams,
13
+ isSystemProgram,
14
+ isTokenProgram,
13
15
  SOL_DECIMAL,
14
- SYSTEM_PROGRAM_ID as SYSTEM_PROGRAM_ID_KEY,
15
16
  TOKEN_2022_PROGRAM_ID as TOKEN_2022_PROGRAM_ID_KEY,
16
17
  TOKEN_PROGRAM_ID as TOKEN_PROGRAM_ID_KEY,
17
18
  } from '@exodus/solana-lib'
@@ -24,8 +25,7 @@ import {
24
25
  fetchValidatedDelegation as _fetchValidatedDelegation,
25
26
  } from './tx-log/delegation-utils.js'
26
27
 
27
- const [SYSTEM_PROGRAM_ID, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID] = [
28
- SYSTEM_PROGRAM_ID_KEY.toBase58(),
28
+ const [TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID] = [
29
29
  TOKEN_PROGRAM_ID_KEY.toBase58(),
30
30
  TOKEN_2022_PROGRAM_ID_KEY.toBase58(),
31
31
  ]
@@ -190,6 +190,32 @@ export class RpcApi {
190
190
  }
191
191
  }
192
192
 
193
+ async getSignaturesForAddress(address, { until, before, limit } = {}) {
194
+ until = until || undefined
195
+
196
+ const fetchRetry = retry(
197
+ async () => {
198
+ try {
199
+ return await this.rpcCall('getSignaturesForAddress', [address, { until, before, limit }])
200
+ } catch (error) {
201
+ if (
202
+ error.message &&
203
+ !errorMessagesToRetry.some((errorMessage) => error.message.includes(errorMessage))
204
+ ) {
205
+ error.finalError = true
206
+ }
207
+
208
+ console.warn(`Error getting signatures. Retrying...`, error)
209
+
210
+ throw error
211
+ }
212
+ },
213
+ { delayTimesMs: ['8s', '10s', '15s'] }
214
+ )
215
+
216
+ return fetchRetry()
217
+ }
218
+
193
219
  async getWalletTokensList({ tokenAccounts }) {
194
220
  const tokensMint = []
195
221
  for (const account of tokenAccounts) {
@@ -217,7 +243,7 @@ export class RpcApi {
217
243
 
218
244
  async isSpl(address) {
219
245
  const { owner } = await this.getAccountInfo(address)
220
- return [TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID].includes(owner)
246
+ return isTokenProgram(owner)
221
247
  }
222
248
 
223
249
  async getRawAccountInfo({ address, encoding = 'jsonParsed' }) {
@@ -244,14 +270,13 @@ export class RpcApi {
244
270
  }
245
271
 
246
272
  /**
247
- * Returns true if the account exists (has not been reclaimed).
248
- * Accounts may be reclaimed by the rent collector after their balance dropped below the
249
- * rent-exempt reserve. getAccountInfo only returns the current account state; if the
250
- * runtime deleted the account, RPC returns null.
273
+ * Returns true if the address has any transaction history.
274
+ * Solana system accounts can be reclaimed after staying below rent exemption, so
275
+ * current account info is not a reliable signal for whether an address was ever used.
251
276
  */
252
277
  async addressIsActive(address) {
253
- const value = await this.getAccountInfo(address)
254
- return !!value?.data
278
+ const signatures = await this.getSignaturesForAddress(address, { limit: 1 })
279
+ return signatures.length > 0
255
280
  }
256
281
 
257
282
  async getTokenFeeBasisPoints(address) {
@@ -292,7 +317,7 @@ export class RpcApi {
292
317
  lamports: value.lamports,
293
318
  }
294
319
 
295
- if (account.owner === SYSTEM_PROGRAM_ID) return 'solana'
320
+ if (isSystemProgram(account.owner)) return 'solana'
296
321
  if (account.owner === TOKEN_PROGRAM_ID) return 'token'
297
322
  if (account.owner === TOKEN_2022_PROGRAM_ID) return 'token-2022'
298
323
  return null
package/src/ws-api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ isSystemProgram,
2
3
  isTokenProgram,
3
4
  PublicKey,
4
5
  Token,
@@ -392,8 +393,7 @@ export class WsApi {
392
393
  */
393
394
  parseAccountNotification({ address, walletAccount, tokenAccountsByOwner, result }) {
394
395
  const value = result?.value ?? result // support both { context, value } and flat result
395
- const isSolAccount = value.owner === '11111111111111111111111111111111' // System Program
396
- if (isSolAccount) {
396
+ if (isSystemProgram(value.owner)) {
397
397
  // SOL balance changed
398
398
  const amount = value.lamports
399
399
  return { solAddress: address, amount }