@exodus/solana-api 3.25.0 → 3.25.2

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.25.2](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.25.1...@exodus/solana-api@3.25.2) (2025-11-04)
7
+
8
+ **Note:** Version bump only for package @exodus/solana-api
9
+
10
+
11
+
12
+
13
+
14
+ ## [3.25.1](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.25.0...@exodus/solana-api@3.25.1) (2025-11-03)
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+
20
+ * fix(SOL): Fix mint address key mapping in clarity monitor balance aggregation (#6839)
21
+
22
+
23
+
6
24
  ## [3.25.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.24.1...@exodus/solana-api@3.25.0) (2025-10-30)
7
25
 
8
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "3.25.0",
3
+ "version": "3.25.2",
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": "^12.0.1",
35
35
  "@exodus/simple-retry": "^0.0.6",
36
- "@exodus/solana-lib": "^3.15.0",
36
+ "@exodus/solana-lib": "^3.15.2",
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": "747fdeb31c49ca85b21904d406a589c06a3bc101",
52
+ "gitHead": "c327da082bf6b55c270d4ab754c2abdc73e0a8de",
53
53
  "bugs": {
54
54
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
55
55
  },
@@ -19,11 +19,9 @@ export const createAccountState = ({ assetList }) => {
19
19
  cursor: '',
20
20
  balance: asset.currency.ZERO,
21
21
  tokenBalances: Object.create(null),
22
- delegatedTokenAmounts: Object.create(null),
23
22
  rentExemptAmount: asset.currency.ZERO,
24
23
  accountSize: 0,
25
24
  ownerChanged: false,
26
- delegatedTokenAccounts: [],
27
25
  stakingInfo: {
28
26
  loaded: false,
29
27
  staking: {
package/src/api.js CHANGED
@@ -962,69 +962,6 @@ export class Api {
962
962
  : tokenAccounts
963
963
  }
964
964
 
965
- /**
966
- * Get token account states for both owned and delegated accounts
967
- * @param {string} address - The wallet address (potential delegate)
968
- * @param {Array} delegatedAccounts - Array of delegated account objects from wallet-accounts
969
- * @returns {Promise<Array>} Combined list of owned and delegated token accounts
970
- */
971
- async getTokenAccountsIncludingDelegated(address, delegatedAccounts = []) {
972
- // Get owned accounts (existing functionality)
973
- const ownedAccounts = await this.getTokenAccountsByOwner(address)
974
-
975
- // Fetch delegated account states
976
- const delegatedAccountPromises = delegatedAccounts.map(async ({ delegatedAddress }) => {
977
- try {
978
- // Fetch the account info to get current balance and delegate status
979
- const accountInfo = await this.getAccountInfo(delegatedAddress)
980
-
981
- if (!accountInfo?.data?.parsed) return null
982
-
983
- const parsedData = accountInfo.data.parsed
984
- const info = parsedData.info
985
-
986
- // Verify this account is actually delegated to us
987
- if (info.delegate !== address) {
988
- console.warn(`Delegated account ${delegatedAddress} is not delegated to ${address}`)
989
- return null
990
- }
991
-
992
- const mintAddress = info.mint
993
- const token = this.getTokenByAddress(mintAddress)
994
- if (!token) return null
995
-
996
- // Get token fee info if it's a Token2022 token
997
- const { feeBasisPoints = 0, maximumFee = 0 } =
998
- accountInfo.owner === TOKEN_2022_PROGRAM_ID.toBase58()
999
- ? await this.getTokenFeeBasisPoints(mintAddress)
1000
- : {}
1001
-
1002
- return {
1003
- tokenAccountAddress: delegatedAddress,
1004
- owner: info.owner, // External owner from on-chain data
1005
- delegate: address, // You are the delegate
1006
- isDelegated: true,
1007
- delegatedAmount: info.delegatedAmount?.amount || '0',
1008
- tokenName: token.name,
1009
- ticker: token.ticker,
1010
- balance: info.tokenAmount?.amount || '0',
1011
- mintAddress,
1012
- tokenProgram: accountInfo.owner, // TOKEN_PROGRAM_ID or TOKEN_2022_PROGRAM_ID
1013
- decimals: info.tokenAmount?.decimals || token.decimals,
1014
- feeBasisPoints,
1015
- maximumFee,
1016
- }
1017
- } catch (error) {
1018
- console.error(`Failed to fetch delegated account ${delegatedAddress}:`, error)
1019
- return null
1020
- }
1021
- })
1022
-
1023
- const delegatedAccountStates = await Promise.all(delegatedAccountPromises)
1024
-
1025
- return [...ownedAccounts, ...delegatedAccountStates.filter(Boolean)]
1026
- }
1027
-
1028
965
  async getTokensBalancesAndAccounts({ address, filterByTokens = [] }) {
1029
966
  const accounts = await this.getTokenAccountsByOwner(address)
1030
967
 
@@ -117,79 +117,17 @@ export const createTxFactory = ({ assetClientInterface, api, feePayerClient }) =
117
117
  throw err
118
118
  }
119
119
 
120
- const [
121
- destinationAddressType,
122
- isAssociatedTokenAccountActive,
123
- fromTokenAccountAddresses,
124
- accountState,
125
- ] = await Promise.all([
126
- api.getAddressType(toAddress),
127
- api.isAssociatedTokenAccountActive(tokenAddress),
128
- api.getTokenAccountsByOwner(fromAddress),
129
- assetClientInterface.getAccountState({
130
- walletAccount,
131
- assetName: baseAssetName,
132
- }),
133
- ])
134
-
135
- const delegatedAccounts = accountState?.delegatedTokenAccounts || []
136
-
137
- // Fetch on-chain state for delegated accounts to get current balance and delegation info
138
- const delegatedTokenAccountsForMint = await Promise.all(
139
- delegatedAccounts
140
- .filter(({ assetName: delegatedAssetName }) => {
141
- // Get asset from assetClientInterface
142
- const delegatedAsset = asset.name === delegatedAssetName ? asset : null
143
- if (!delegatedAsset) return false
144
- return delegatedAsset.mintAddress === tokenMintAddress
145
- })
146
- .map(async (delegatedAccount) => {
147
- try {
148
- const accountInfo = await api.rpcCall(
149
- 'getAccountInfo',
150
- [delegatedAccount.delegatedAddress, { encoding: 'jsonParsed' }],
151
- { address: delegatedAccount.delegatedAddress }
152
- )
153
-
154
- if (!accountInfo?.value?.data?.parsed) return null
155
-
156
- const info = accountInfo.value.data.parsed.info
157
-
158
- // Verify delegation is still active
159
- if (info.delegate !== fromAddress) {
160
- console.warn(
161
- `Delegated account ${delegatedAccount.delegatedAddress} is no longer delegated to ${fromAddress}`
162
- )
163
- return null
164
- }
165
-
166
- return {
167
- tokenAccountAddress: delegatedAccount.delegatedAddress,
168
- mintAddress: tokenMintAddress,
169
- balance: info.tokenAmount?.amount || '0',
170
- decimals: info.tokenAmount?.decimals || 0,
171
- tokenProgram: accountInfo.value.owner,
172
- isDelegated: true,
173
- delegatedAmount: info.delegatedAmount?.amount || '0',
174
- }
175
- } catch (error) {
176
- console.error(
177
- `Failed to fetch delegated account ${delegatedAccount.delegatedAddress}:`,
178
- error
179
- )
180
- return null
181
- }
182
- })
120
+ const [destinationAddressType, isAssociatedTokenAccountActive, fromTokenAccountAddresses] =
121
+ await Promise.all([
122
+ api.getAddressType(toAddress),
123
+ api.isAssociatedTokenAccountActive(tokenAddress),
124
+ api.getTokenAccountsByOwner(fromAddress),
125
+ ])
126
+
127
+ const fromTokenAddresses = fromTokenAccountAddresses.filter(
128
+ ({ mintAddress }) => mintAddress === tokenMintAddress
183
129
  )
184
130
 
185
- const validDelegatedAccounts = delegatedTokenAccountsForMint.filter(Boolean)
186
-
187
- // Combine owned and delegated accounts
188
- const fromTokenAddresses = [
189
- ...fromTokenAccountAddresses.filter(({ mintAddress }) => mintAddress === tokenMintAddress),
190
- ...validDelegatedAccounts,
191
- ]
192
-
193
131
  tokenParams = {
194
132
  tokenMintAddress,
195
133
  destinationAddressType,
@@ -250,84 +250,13 @@ export class SolanaClarityMonitor extends BaseMonitor {
250
250
 
251
251
  async getAccountsAndBalances({ refresh, address, accountState, walletAccount }) {
252
252
  const tokens = Object.keys(this.assets).filter((name) => name !== this.asset.name)
253
-
254
- const delegatedAccounts = accountState.delegatedTokenAccounts || []
255
-
256
- const [accountInfo, { balances: splBalances, accounts: ownedTokenAccounts }] =
257
- await Promise.all([
258
- this.api.getAccountInfo(address).catch(() => {}),
259
- this.api.getTokensBalancesAndAccounts({
260
- address,
261
- filterByTokens: tokens,
262
- }),
263
- ])
264
-
265
- // Fetch delegated account balances - get full balances first, then delegated amounts
266
- const delegatedBalances = {}
267
- const delegatedTokenAmounts = {}
268
-
269
- for (const { delegatedAddress } of delegatedAccounts) {
270
- try {
271
- const accountInfo = await this.api.rpcCall(
272
- 'getAccountInfo',
273
- [delegatedAddress, { encoding: 'jsonParsed' }],
274
- { address: delegatedAddress }
275
- )
276
-
277
- if (!accountInfo?.value?.data?.parsed) continue
278
-
279
- const info = accountInfo.value.data.parsed.info
280
-
281
- // Verify this account is actually delegated to us
282
- if (info.delegate !== address) {
283
- console.warn(`Delegated account ${delegatedAddress} is not delegated to ${address}`)
284
- continue
285
- }
286
-
287
- const mintAddress = info.mint
288
- const fullBalance = info.tokenAmount?.amount || '0'
289
- const delegatedAmount = info.delegatedAmount?.amount || '0'
290
-
291
- // Store full balance for combining with owned balances
292
- const tokenName = this.api.tokens.get(mintAddress)?.name
293
- if (!tokenName) continue
294
- if (this.assets[tokenName]) {
295
- const fullBalanceCurrency = this.assets[tokenName].currency.baseUnit(fullBalance)
296
- if (delegatedBalances[mintAddress]) {
297
- delegatedBalances[mintAddress] = delegatedBalances[mintAddress].add(fullBalanceCurrency)
298
- } else {
299
- delegatedBalances[mintAddress] = fullBalanceCurrency
300
- }
301
- }
302
-
303
- // Store delegated amounts separately for the account state
304
- if (this.assets[tokenName]) {
305
- const delegatedAmountCurrency = this.assets[tokenName].currency.baseUnit(delegatedAmount)
306
- if (!delegatedAmountCurrency.isZero()) {
307
- if (!delegatedTokenAmounts[tokenName]) {
308
- delegatedTokenAmounts[tokenName] = {}
309
- }
310
-
311
- delegatedTokenAmounts[tokenName][delegatedAddress] = delegatedAmountCurrency
312
- }
313
- }
314
- } catch (error) {
315
- console.error(`Failed to fetch delegated account ${delegatedAddress}:`, error)
316
- }
317
- }
318
-
319
- // Combine owned and delegated balances
320
- const combinedBalances = { ...splBalances }
321
- for (const [mintAddress, delegatedBalanceCurrency] of Object.entries(delegatedBalances)) {
322
- const tokenName = this.api.tokens.get(mintAddress)?.name
323
- if (tokenName && this.assets[tokenName]) {
324
- const ownedBalance = this.assets[tokenName].currency.baseUnit(
325
- combinedBalances[mintAddress] || 0
326
- )
327
- const totalBalance = ownedBalance.add(delegatedBalanceCurrency)
328
- combinedBalances[mintAddress] = Number(totalBalance.toBaseString())
329
- }
330
- }
253
+ const [accountInfo, { balances: splBalances, accounts: tokenAccounts }] = await Promise.all([
254
+ this.api.getAccountInfo(address).catch(() => {}),
255
+ this.api.getTokensBalancesAndAccounts({
256
+ address,
257
+ filterByTokens: tokens,
258
+ }),
259
+ ])
331
260
 
332
261
  const solBalance = accountInfo?.lamports || 0
333
262
 
@@ -339,8 +268,8 @@ export class SolanaClarityMonitor extends BaseMonitor {
339
268
 
340
269
  const ownerChanged = await this.api.ownerChanged(address, accountInfo)
341
270
 
342
- // we can have balances for tokens that are not in our asset list
343
- const clientKnownTokens = omitBy(combinedBalances, (v, mintAddress) => {
271
+ // we can have splBalances for tokens that are not in our asset list
272
+ const clientKnownTokens = omitBy(splBalances, (v, mintAddress) => {
344
273
  const tokenName = this.api.tokens.get(mintAddress)?.name
345
274
  return !this.assets[tokenName]
346
275
  })
@@ -351,9 +280,6 @@ export class SolanaClarityMonitor extends BaseMonitor {
351
280
  })
352
281
  )
353
282
 
354
- // Use owned token accounts for transaction tracking
355
- const tokenAccounts = ownedTokenAccounts
356
-
357
283
  const solBalanceChanged = this.#balanceChanged({
358
284
  account: accountState,
359
285
  newAccount: {
@@ -386,7 +312,6 @@ export class SolanaClarityMonitor extends BaseMonitor {
386
312
  account: {
387
313
  balance,
388
314
  tokenBalances,
389
- delegatedTokenAmounts,
390
315
  rentExemptAmount,
391
316
  accountSize,
392
317
  ownerChanged,
@@ -397,21 +322,13 @@ export class SolanaClarityMonitor extends BaseMonitor {
397
322
  }
398
323
 
399
324
  async updateState({ account, cursorState, walletAccount, staking }) {
400
- const {
401
- balance,
402
- tokenBalances,
403
- delegatedTokenAmounts,
404
- rentExemptAmount,
405
- accountSize,
406
- ownerChanged,
407
- } = account
325
+ const { balance, tokenBalances, rentExemptAmount, accountSize, ownerChanged } = account
408
326
  const newData = {
409
327
  balance,
410
328
  rentExemptAmount,
411
329
  accountSize,
412
330
  ownerChanged,
413
331
  tokenBalances,
414
- delegatedTokenAmounts,
415
332
  stakingInfo: staking,
416
333
  ...cursorState,
417
334
  }