@exodus/solana-api 3.17.1 → 3.17.3

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.17.3](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.17.2...@exodus/solana-api@3.17.3) (2025-04-23)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: SOL skip unparsed token2022 (#5476)
13
+
14
+
15
+
16
+ ## [3.17.2](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.17.1...@exodus/solana-api@3.17.2) (2025-04-21)
17
+
18
+ **Note:** Version bump only for package @exodus/solana-api
19
+
20
+
21
+
22
+
23
+
6
24
  ## [3.17.1](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.17.0...@exodus/solana-api@3.17.1) (2025-04-11)
7
25
 
8
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "3.17.1",
3
+ "version": "3.17.3",
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",
@@ -30,7 +30,7 @@
30
30
  "@exodus/fetch": "^1.7.3",
31
31
  "@exodus/models": "^12.0.1",
32
32
  "@exodus/simple-retry": "^0.0.6",
33
- "@exodus/solana-lib": "^3.10.1",
33
+ "@exodus/solana-lib": "^3.11.1",
34
34
  "@exodus/solana-meta": "^2.0.2",
35
35
  "@exodus/timer": "^1.1.1",
36
36
  "bn.js": "^4.11.0",
@@ -47,7 +47,7 @@
47
47
  "@exodus/assets-testing": "^1.0.0",
48
48
  "@exodus/solana-web3.js": "^1.63.1-exodus.9-rc3"
49
49
  },
50
- "gitHead": "f0b2b4a3a3dab9be7d36f6d4118c50204c25f40e",
50
+ "gitHead": "1838cc73f98e05d63ff8b3f63f3d142386830638",
51
51
  "bugs": {
52
52
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
53
53
  },
package/src/api.js CHANGED
@@ -830,6 +830,8 @@ export class Api {
830
830
  const { pubkey, account } = entry
831
831
 
832
832
  const mint = lodash.get(account, 'data.parsed.info.mint')
833
+ if (!mint) continue // not a token account (or cannot be parsed)
834
+
833
835
  const token = this.getTokenByAddress(mint) || {
834
836
  name: 'unknown',
835
837
  ticker: 'UNKNOWN',
@@ -172,7 +172,11 @@ export const createUnsignedTxForSend = async ({
172
172
  const { unitsConsumed, err } = await api.simulateUnsignedTransaction({
173
173
  message,
174
174
  })
175
- if (err) throw new Error(JSON.stringify(err))
175
+ if (err) {
176
+ // we don't throw error here, because we can use this method to estimate fee
177
+ console.log('SOL estimate unitsConsumed err:', JSON.stringify(err))
178
+ }
179
+
176
180
  return unitsConsumed + CU_FOR_COMPUTE_BUDGET_INSTRUCTIONS
177
181
  }
178
182
 
@@ -3,19 +3,17 @@ import { TxSet } from '@exodus/models'
3
3
  // staking may be a feature that may not be available for a given wallet.
4
4
  // In this case, The wallet should exclude the staking balance from the general balance
5
5
 
6
- const DEFAULT_STAKING_RESERVE = 0.01 * 1_000_000_000
7
-
8
6
  export const getBalancesFactory =
9
7
  ({ stakingFeatureAvailable, allowSendingAll }) =>
10
8
  ({ asset, accountState, txLog }) => {
11
9
  const zero = asset.currency.ZERO
12
- const defaultStakingReserve = asset.currency.baseUnit(DEFAULT_STAKING_RESERVE)
13
10
 
14
- const { balance, locked, withdrawable, pending } = fixBalances({
11
+ const { balance, locked, activating, withdrawable, pending } = fixBalances({
15
12
  txLog,
16
13
  balance: getBalanceFromAccountState({ asset, accountState }),
17
14
  locked: accountState.stakingInfo?.locked || zero,
18
15
  withdrawable: accountState.stakingInfo?.withdrawable || zero,
16
+ activating: accountState.stakingInfo?.activating || zero,
19
17
  pending: accountState.stakingInfo?.pending || zero,
20
18
  asset,
21
19
  })
@@ -40,29 +38,21 @@ export const getBalancesFactory =
40
38
 
41
39
  // there is no wallet reserve when there are no tokens nor staking actions. Just network reserve for the rent exempt amount.
42
40
  const needsReserve =
43
- hasStakedFunds({ locked, withdrawable, pending }) || hasTokensBalance({ accountState })
41
+ hasStakedFunds({ locked, activating, withdrawable, pending }) ||
42
+ hasTokensBalance({ accountState })
44
43
 
45
44
  const rentExemptAmountConditional =
46
45
  (accountState.accountSize > 0 ? accountState.rentExemptAmount : zero) || zero
47
46
  const networkReserve = allowSendingAll && !needsReserve ? zero : rentExemptAmountConditional
48
47
 
49
- const accountReserve = asset.accountReserve || zero
50
-
51
- const walletReserve = needsReserve ? accountReserve.sub(networkReserve).clampLowerZero() : zero
52
-
53
- const spendable = balanceWithoutStaking.sub(walletReserve).sub(networkReserve).clampLowerZero()
48
+ const spendable = balanceWithoutStaking.sub(networkReserve).clampLowerZero()
54
49
 
55
- // leave enough sol for staking when the reserve is set to 0
56
- // FIXME: should be able to get total stakeable balance dynamically
57
- // instead of hardcoding a reserve value.
58
- const stakeable = walletReserve.isZero
59
- ? spendable.sub(defaultStakingReserve).clampLowerZero()
60
- : spendable
50
+ const stakeable = spendable
61
51
 
62
52
  const staked = locked
63
53
  const unstaking = pending
64
54
 
65
- const staking = accountState.activating || zero
55
+ const staking = activating || zero
66
56
 
67
57
  return {
68
58
  // legacy
@@ -76,11 +66,19 @@ export const getBalancesFactory =
76
66
  staking,
77
67
  unstaking,
78
68
  networkReserve,
79
- walletReserve,
69
+ walletReserve: zero,
80
70
  }
81
71
  }
82
72
 
83
- const fixBalances = ({ txLog = TxSet.EMPTY, balance, locked, withdrawable, pending, asset }) => {
73
+ const fixBalances = ({
74
+ txLog = TxSet.EMPTY,
75
+ balance,
76
+ locked,
77
+ withdrawable,
78
+ activating,
79
+ pending,
80
+ asset,
81
+ }) => {
84
82
  for (const tx of txLog) {
85
83
  if ((tx.sent || tx.data.staking) && tx.pending && !tx.error) {
86
84
  if (tx.coinAmount.unitType.equals(tx.feeAmount.unitType)) {
@@ -112,6 +110,7 @@ const fixBalances = ({ txLog = TxSet.EMPTY, balance, locked, withdrawable, pendi
112
110
  balance: balance.clampLowerZero(),
113
111
  locked: locked.clampLowerZero(),
114
112
  withdrawable: withdrawable.clampLowerZero(),
113
+ activating: activating.clampLowerZero(),
115
114
  pending: pending.clampLowerZero(),
116
115
  }
117
116
  }
@@ -124,8 +123,8 @@ const getBalanceFromAccountState = ({ asset, accountState }) => {
124
123
  )
125
124
  }
126
125
 
127
- const hasStakedFunds = ({ locked, withdrawable, pending }) =>
128
- [locked, withdrawable, pending].some((amount) => amount.isPositive)
126
+ const hasStakedFunds = ({ locked, activating, withdrawable, pending }) =>
127
+ [locked, activating, withdrawable, pending].some((amount) => amount.isPositive)
129
128
 
130
129
  const hasTokensBalance = ({ accountState }) =>
131
130
  Object.values(accountState?.tokenBalances || {}).some((balance) => balance.isPositive)
package/src/get-fees.js CHANGED
@@ -1,23 +1,74 @@
1
+ /* eslint-disable @exodus/mutable/no-param-reassign-prop-only */
1
2
  import assert from 'minimalistic-assert'
2
3
 
3
4
  import { createUnsignedTxForSend } from './create-unsigned-tx-for-send.js'
4
5
 
6
+ const DEFAULT_RESERVE_FEE = '0.01' // SOL
7
+
5
8
  export const getFeeAsyncFactory = ({ api }) => {
6
9
  assert(api, 'api is required')
7
- return async ({ asset, feeData, unsignedTx: providedUnsignedTx, amount, toAddress, ...rest }) => {
8
- const unsignedTx =
9
- providedUnsignedTx ||
10
- (await createUnsignedTxForSend({
11
- asset,
12
- feeData,
13
- api,
14
- amount: amount ?? asset.currency.baseUnit(1),
15
- toAddress: toAddress ?? rest.fromAddress,
16
- useFeePayer: false,
17
- ...rest,
18
- }))
19
-
20
- return { fee: asset.feeAsset.currency.baseUnit(unsignedTx.txMeta.fee), unsignedTx }
10
+ return async ({
11
+ asset,
12
+ method,
13
+ feeData,
14
+ unsignedTx: providedUnsignedTx,
15
+ amount,
16
+ toAddress,
17
+ stakingInfo,
18
+ ...rest
19
+ }) => {
20
+ let fee, unsignedTx
21
+
22
+ if (providedUnsignedTx) {
23
+ unsignedTx = providedUnsignedTx
24
+ fee = asset.feeAsset.currency.baseUnit(unsignedTx.txMeta.fee)
25
+ } else {
26
+ if (['delegate', 'undelegate', 'withdraw'].includes(method)) {
27
+ assert(stakingInfo, 'stakingInfo is required for staking txs')
28
+ assert(rest.fromAddress, 'fromAddress is required for staking txs')
29
+ assert(feeData, 'feeData is required for staking txs')
30
+
31
+ // staking params
32
+ rest.method = method
33
+ rest.seed = `exodus:${Date.now()}` // unique seed
34
+ rest.pool = stakingInfo.staking.pool
35
+
36
+ const stakeAddresses = []
37
+ for (const [addr, info] of Object.entries(stakingInfo.accounts || {})) {
38
+ if (method === 'undelegate' && (info.state === 'active' || info.state === 'activating'))
39
+ stakeAddresses.push(addr)
40
+ if (method === 'withdraw' && info.state === 'inactive') stakeAddresses.push(addr)
41
+ }
42
+
43
+ rest.stakeAddresses = stakeAddresses
44
+ amount =
45
+ method === 'undelegate'
46
+ ? stakingInfo.locked // unstake all
47
+ : method === 'withdraw'
48
+ ? stakingInfo.withdrawable // withdraw all
49
+ : amount
50
+ }
51
+
52
+ try {
53
+ unsignedTx = await createUnsignedTxForSend({
54
+ asset,
55
+ feeData,
56
+ api,
57
+ amount: amount ?? asset.currency.baseUnit(1),
58
+ toAddress: toAddress ?? rest.fromAddress,
59
+ useFeePayer: false,
60
+ ...rest,
61
+ })
62
+
63
+ fee = asset.feeAsset.currency.baseUnit(unsignedTx.txMeta.fee)
64
+ } catch (err) {
65
+ console.log('error computing right SOL fee:', err)
66
+ // simulating a tx will fail if the user has not enough balance
67
+ fee = asset.feeAsset.currency.defaultUnit(DEFAULT_RESERVE_FEE)
68
+ }
69
+ }
70
+
71
+ return { fee, unsignedTx }
21
72
  }
22
73
  }
23
74
 
package/src/index.js CHANGED
@@ -6,7 +6,7 @@ import { Api } from './api.js'
6
6
 
7
7
  export { SolanaMonitor } from './tx-log/index.js'
8
8
  export { createAccountState } from './account-state.js'
9
- export { getSolStakedFee, getStakingInfo, getUnstakingFee } from './staking-utils.js'
9
+ export { getSolStakedFee, getStakingInfo } from './staking-utils.js'
10
10
  export {
11
11
  isSolanaStaking,
12
12
  isSolanaUnstaking,
@@ -8,6 +8,9 @@ export const getSolStakedFee = ({ asset, stakingInfo, fee }) => {
8
8
  const { currency } = asset
9
9
  const { accounts } = stakingInfo
10
10
 
11
+ // TODO: REMOVE this method.. only used in Desktop!
12
+ // (also this is wrong, accounts must be filtered by state to compute the right fee)
13
+
11
14
  const allPending = Object.entries(accounts).length
12
15
  return allPending > 0 ? fee.mul(allPending) : currency.ZERO
13
16
  }
@@ -25,8 +28,3 @@ export const getStakingInfo = (stakingInfo) => {
25
28
  earned: stakingInfo.earned,
26
29
  }
27
30
  }
28
-
29
- export const getUnstakingFee = ({ asset, fee, accountState }) => {
30
- const stakingInfo = getStakingInfo(accountState.stakingInfo ?? {})
31
- return getSolStakedFee({ asset, stakingInfo, fee })
32
- }