@exodus/solana-api 3.17.0 → 3.17.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 +18 -0
- package/package.json +3 -3
- package/src/create-unsigned-tx-for-send.js +15 -4
- 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.2](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.17.1...@exodus/solana-api@3.17.2) (2025-04-21)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @exodus/solana-api
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [3.17.1](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.17.0...@exodus/solana-api@3.17.1) (2025-04-11)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
* fix(solana): don't estimate without fee payer if one is provided (#5435)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## [3.17.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.16.0...@exodus/solana-api@3.17.0) (2025-04-09)
|
|
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.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",
|
|
@@ -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": "2e550990ef9db4209f8fe4e6e0485579d1afeedf",
|
|
51
51
|
"bugs": {
|
|
52
52
|
"url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
|
|
53
53
|
},
|
|
@@ -160,11 +160,23 @@ export const createUnsignedTxForSend = async ({
|
|
|
160
160
|
return 150 + CU_FOR_COMPUTE_BUDGET_INSTRUCTIONS
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
const transactionForFeeEstimation =
|
|
163
|
+
const transactionForFeeEstimation = await maybeAddFeePayer({
|
|
164
|
+
unsignedTx,
|
|
165
|
+
feePayerApiUrl,
|
|
166
|
+
assetName: asset.baseAsset.name,
|
|
167
|
+
})
|
|
168
|
+
const message = transactionForFeeEstimation.txMeta.usedFeePayer
|
|
169
|
+
? deserializeTransaction(transactionForFeeEstimation.txData.transactionBuffer).message
|
|
170
|
+
: prepareForSigning(transactionForFeeEstimation).message
|
|
171
|
+
|
|
164
172
|
const { unitsConsumed, err } = await api.simulateUnsignedTransaction({
|
|
165
|
-
message
|
|
173
|
+
message,
|
|
166
174
|
})
|
|
167
|
-
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
|
+
|
|
168
180
|
return unitsConsumed + CU_FOR_COMPUTE_BUDGET_INSTRUCTIONS
|
|
169
181
|
}
|
|
170
182
|
|
|
@@ -216,7 +228,6 @@ export const createUnsignedTxForSend = async ({
|
|
|
216
228
|
unsignedTx,
|
|
217
229
|
feePayerApiUrl,
|
|
218
230
|
assetName: asset.baseAsset.name,
|
|
219
|
-
useFeePayer,
|
|
220
231
|
})
|
|
221
232
|
}
|
|
222
233
|
|
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
|
-
}
|