@exodus/solana-api 3.25.1 → 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,14 @@
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
+
6
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)
7
15
 
8
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "3.25.1",
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": "a87798a8db1f3ffceb94684a842f22b35025d549",
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,96 +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
- const delegatedAmountCurrency = this.assets[tokenName].currency.baseUnit(delegatedAmount)
303
- if (!delegatedAmountCurrency.equals(this.assets[tokenName].currency.ZERO)) {
304
- if (!delegatedTokenAmounts[tokenName]) {
305
- delegatedTokenAmounts[tokenName] = {}
306
- }
307
-
308
- delegatedTokenAmounts[tokenName][delegatedAddress] = delegatedAmountCurrency
309
- }
310
- }
311
- } catch (error) {
312
- console.error(`Failed to fetch delegated account ${delegatedAddress}:`, error)
313
- }
314
- }
315
-
316
- const combinedBalances = {}
317
-
318
- const tokenNameToMintAddress = {}
319
- for (const account of ownedTokenAccounts || []) {
320
- if (account.tokenName && account.mintAddress) {
321
- tokenNameToMintAddress[account.tokenName] = account.mintAddress
322
- }
323
- }
324
-
325
- for (const [tokenName, balance] of Object.entries(splBalances)) {
326
- const mintAddress = tokenNameToMintAddress[tokenName]
327
-
328
- if (mintAddress) {
329
- combinedBalances[mintAddress] = balance
330
- }
331
- }
332
-
333
- for (const [mintAddress, delegatedBalanceCurrency] of Object.entries(delegatedBalances)) {
334
- const tokenName = this.api.tokens.get(mintAddress)?.name
335
- if (tokenName && this.assets[tokenName]) {
336
- const ownedBalance = this.assets[tokenName].currency.baseUnit(
337
- combinedBalances[mintAddress] || 0
338
- )
339
- const totalBalance = ownedBalance.add(delegatedBalanceCurrency)
340
- combinedBalances[mintAddress] = Number(totalBalance.toBaseString())
341
- }
342
- }
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
+ ])
343
260
 
344
261
  const solBalance = accountInfo?.lamports || 0
345
262
 
@@ -351,8 +268,8 @@ export class SolanaClarityMonitor extends BaseMonitor {
351
268
 
352
269
  const ownerChanged = await this.api.ownerChanged(address, accountInfo)
353
270
 
354
- // we can have balances for tokens that are not in our asset list
355
- 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) => {
356
273
  const tokenName = this.api.tokens.get(mintAddress)?.name
357
274
  return !this.assets[tokenName]
358
275
  })
@@ -363,9 +280,6 @@ export class SolanaClarityMonitor extends BaseMonitor {
363
280
  })
364
281
  )
365
282
 
366
- // Use owned token accounts for transaction tracking
367
- const tokenAccounts = ownedTokenAccounts
368
-
369
283
  const solBalanceChanged = this.#balanceChanged({
370
284
  account: accountState,
371
285
  newAccount: {
@@ -398,7 +312,6 @@ export class SolanaClarityMonitor extends BaseMonitor {
398
312
  account: {
399
313
  balance,
400
314
  tokenBalances,
401
- delegatedTokenAmounts,
402
315
  rentExemptAmount,
403
316
  accountSize,
404
317
  ownerChanged,
@@ -409,21 +322,13 @@ export class SolanaClarityMonitor extends BaseMonitor {
409
322
  }
410
323
 
411
324
  async updateState({ account, cursorState, walletAccount, staking }) {
412
- const {
413
- balance,
414
- tokenBalances,
415
- delegatedTokenAmounts,
416
- rentExemptAmount,
417
- accountSize,
418
- ownerChanged,
419
- } = account
325
+ const { balance, tokenBalances, rentExemptAmount, accountSize, ownerChanged } = account
420
326
  const newData = {
421
327
  balance,
422
328
  rentExemptAmount,
423
329
  accountSize,
424
330
  ownerChanged,
425
331
  tokenBalances,
426
- delegatedTokenAmounts,
427
332
  stakingInfo: staking,
428
333
  ...cursorState,
429
334
  }