@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 +18 -0
- package/package.json +3 -3
- package/src/api.js +2 -0
- package/src/create-unsigned-tx-for-send.js +5 -1
- package/src/get-balances.js +20 -21
- package/src/get-fees.js +65 -14
- package/src/index.js +1 -1
- package/src/staking-utils.js +3 -5
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.
|
|
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.
|
|
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": "
|
|
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)
|
|
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
|
|
package/src/get-balances.js
CHANGED
|
@@ -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 }) ||
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 = ({
|
|
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 ({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
9
|
+
export { getSolStakedFee, getStakingInfo } from './staking-utils.js'
|
|
10
10
|
export {
|
|
11
11
|
isSolanaStaking,
|
|
12
12
|
isSolanaUnstaking,
|
package/src/staking-utils.js
CHANGED
|
@@ -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
|
-
}
|