@exodus/solana-api 3.11.7 → 3.11.9

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,36 @@
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.11.9](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.11.8...@exodus/solana-api@3.11.9) (2024-12-31)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: include rentExemptAmount in balance calculation (#4738)
13
+
14
+ * fix: integration tests (#4720)
15
+
16
+
17
+
18
+ ## [3.11.8](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.11.6...@exodus/solana-api@3.11.8) (2024-12-10)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+
24
+ * fix: prevent invalid account owner (#4485)
25
+
26
+ * fix: SOL rename deprecated methods (#4512)
27
+
28
+
29
+ ### License
30
+
31
+
32
+ * license: re-license under MIT license (#4515)
33
+
34
+
35
+
6
36
  ## [3.11.7](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.11.6...@exodus/solana-api@3.11.7) (2024-11-06)
7
37
 
8
38
 
package/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # Solana Api · [![npm version](https://img.shields.io/badge/npm-private-blue.svg?style=flat)](https://www.npmjs.com/package/@exodus/solana-api)
1
+ # @exodus/solana-api
2
+
3
+ Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Solana. See [Asset Packages](../../docs/asset-packages.md) for more detail on this package's role.
4
+
5
+ ## Known Issues
2
6
 
3
7
  - To get all transactions data from an address we gotta call 3 rpcs `getSignaturesForAddress` (get txIds) -> `getTransaction` (get tx details) -> `getBlockTime` (get tx timestamp). Pretty annoying and resource-consuming backend-side. (https://github.com/solana-labs/solana/issues/12411)
4
8
  - calling `getBlockTime` might results in an error if the slot/block requested is too old (https://github.com/solana-labs/solana/issues/12413), looks like some Solana validators can choose to not keep all the ledger blocks (fix in progress by solana team).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "3.11.7",
4
- "description": "Exodus internal Solana asset API wrapper",
3
+ "version": "3.11.9",
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",
7
7
  "files": [
@@ -14,7 +14,7 @@
14
14
  "author": "Exodus Movement, Inc.",
15
15
  "license": "ISC",
16
16
  "publishConfig": {
17
- "access": "restricted"
17
+ "access": "public"
18
18
  },
19
19
  "scripts": {
20
20
  "test": "run -T exodus-test --jest",
@@ -26,11 +26,11 @@
26
26
  "@exodus/asset-lib": "^5.0.0",
27
27
  "@exodus/assets": "^11.0.0",
28
28
  "@exodus/basic-utils": "^3.0.1",
29
- "@exodus/currency": "^5.0.2",
29
+ "@exodus/currency": "^6.0.1",
30
30
  "@exodus/fetch": "^1.2.0",
31
31
  "@exodus/models": "^12.0.1",
32
32
  "@exodus/simple-retry": "^0.0.6",
33
- "@exodus/solana-lib": "^3.6.0",
33
+ "@exodus/solana-lib": "^3.9.3",
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": "451a0342feffa7ab7dbea8ff7f8e224cbd55c31b",
50
+ "gitHead": "44b274a6a6f0228014aa0eaac5af850dcaf76fed",
51
51
  "bugs": {
52
52
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
53
53
  },
@@ -19,6 +19,7 @@ export const createAccountState = ({ assetList }) => {
19
19
  cursor: '',
20
20
  balance: asset.currency.ZERO,
21
21
  tokenBalances: Object.create(null),
22
+ rentExemptAmount: asset.currency.ZERO,
22
23
  stakingInfo: {
23
24
  loaded: false,
24
25
  staking: {
package/src/api.js CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  import BN from 'bn.js'
18
18
  import lodash from 'lodash'
19
19
  import assert from 'minimalistic-assert'
20
+ import ms from 'ms'
20
21
  import urljoin from 'url-join'
21
22
  import wretch from 'wretch'
22
23
 
@@ -46,6 +47,12 @@ export class Api {
46
47
  const result = await this.rpcCall('getTokenSupply', [mintAddress])
47
48
  return result?.value?.amount
48
49
  })
50
+
51
+ this.getMinimumBalanceForRentExemption = memoize(
52
+ (accountSize) => this.rpcCall('getMinimumBalanceForRentExemption', [accountSize]),
53
+ (accountSize) => accountSize,
54
+ ms('15m')
55
+ )
49
56
  }
50
57
 
51
58
  setServer(rpcUrl) {
@@ -137,7 +144,7 @@ export class Api {
137
144
  async getRecentBlockHash(commitment) {
138
145
  const result = await this.rpcCall(
139
146
  'getLatestBlockhash',
140
- [{ commitment: commitment || 'finalized', encoding: 'jsonParsed' }],
147
+ [{ commitment: commitment || 'confirmed', encoding: 'jsonParsed' }],
141
148
  { forceHttp: true }
142
149
  )
143
150
  return lodash.get(result, 'value.blockhash')
@@ -171,7 +178,7 @@ export class Api {
171
178
  return this.rpcCall('getBlockTime', [slot])
172
179
  }
173
180
 
174
- async getConfirmedSignaturesForAddress(address, { until, before, limit } = {}) {
181
+ async getSignaturesForAddress(address, { until, before, limit } = {}) {
175
182
  until = until || undefined
176
183
  return this.rpcCall('getSignaturesForAddress', [address, { until, before, limit }], { address })
177
184
  }
@@ -195,7 +202,7 @@ export class Api {
195
202
 
196
203
  const txsResultsByAccount = await Promise.all(
197
204
  accountsToCheck.map((addr) =>
198
- this.getConfirmedSignaturesForAddress(addr, {
205
+ this.getSignaturesForAddress(addr, {
199
206
  until,
200
207
  before,
201
208
  limit,
@@ -876,10 +883,6 @@ export class Api {
876
883
  }, 0)
877
884
  }
878
885
 
879
- async getMinimumBalanceForRentExemption(size) {
880
- return this.rpcCall('getMinimumBalanceForRentExemption', [size])
881
- }
882
-
883
886
  async getProgramAccounts(programId, config) {
884
887
  return this.rpcCall('getProgramAccounts', [programId, config])
885
888
  }
@@ -1046,7 +1049,7 @@ export class Api {
1046
1049
  ).toString('base64')
1047
1050
  const { accounts, unitsConsumed, err } = await this.simulateTransaction(encodedTransaction, {
1048
1051
  ...config,
1049
- replaceRecentBlockhash: true,
1052
+ replaceRecentBlockhash: false,
1050
1053
  sigVerify: false,
1051
1054
  })
1052
1055
  return {
@@ -33,7 +33,18 @@ export const getBalancesFactory =
33
33
  .clampLowerZero()
34
34
 
35
35
  const total = stakingFeatureAvailable ? balance : balanceWithoutStaking
36
- const spendable = balanceWithoutStaking.sub(asset.accountReserve || zero).clampLowerZero()
36
+
37
+ const networkReserve = accountState.rentExemptAmount || zero
38
+
39
+ const accountReserve = asset.accountReserve || zero
40
+
41
+ // there is no wallet reserve when there are no tokens nor staking actions. Just network reserve for the rent exempt amount.
42
+ const walletReserve =
43
+ hasStakedFunds({ locked, withdrawable, pending }) || hasTokensBalance({ accountState })
44
+ ? accountReserve.sub(networkReserve).clampLowerZero()
45
+ : zero
46
+
47
+ const spendable = balanceWithoutStaking.sub(walletReserve).sub(networkReserve).clampLowerZero()
37
48
 
38
49
  const staked = locked
39
50
  const unstaking = pending
@@ -47,6 +58,8 @@ export const getBalancesFactory =
47
58
  spendable,
48
59
  staked,
49
60
  unstaking,
61
+ networkReserve,
62
+ walletReserve,
50
63
  }
51
64
  }
52
65
 
@@ -93,3 +106,9 @@ const getBalanceFromAccountState = ({ asset, accountState }) => {
93
106
  asset.currency.ZERO
94
107
  )
95
108
  }
109
+
110
+ const hasStakedFunds = ({ locked, withdrawable, pending }) =>
111
+ [locked, withdrawable, pending].some((amount) => amount.isPositive)
112
+
113
+ const hasTokensBalance = ({ accountState }) =>
114
+ Object.values(accountState?.tokenBalances || {}).some((balance) => balance.isPositive)
@@ -294,10 +294,18 @@ export class SolanaMonitor extends BaseMonitor {
294
294
 
295
295
  async getAccount({ refresh, address, tokenAccounts, accountState, walletAccount }) {
296
296
  const tokens = Object.keys(this.assets).filter((name) => name !== this.asset.name)
297
- const [solBalance, splBalances] = await Promise.all([
298
- this.api.getBalance(address),
299
- this.api.getTokensBalance({ address, filterByTokens: tokens, tokenAccounts }),
300
- ])
297
+ const accountInfo = await this.api.getAccountInfo(address).catch(() => {})
298
+ const accountSize = accountInfo?.space || 0
299
+ const solBalance = accountInfo?.lamports || 0
300
+
301
+ const rentExemptValue = await this.api.getMinimumBalanceForRentExemption(accountSize)
302
+ const rentExemptAmount = this.asset.currency.baseUnit(rentExemptValue)
303
+
304
+ const splBalances = await this.api.getTokensBalance({
305
+ address,
306
+ filterByTokens: tokens,
307
+ tokenAccounts,
308
+ })
301
309
 
302
310
  const tokenBalances = _.mapValues(splBalances, (balance, name) =>
303
311
  this.assets[name].currency.baseUnit(balance).toDefault()
@@ -334,14 +342,21 @@ export class SolanaMonitor extends BaseMonitor {
334
342
  account: {
335
343
  balance,
336
344
  tokenBalances,
345
+ rentExemptAmount,
337
346
  },
338
347
  staking,
339
348
  }
340
349
  }
341
350
 
342
351
  async updateState({ account, cursorState, walletAccount, staking }) {
343
- const { balance, tokenBalances } = account
344
- const newData = { balance, tokenBalances, stakingInfo: staking, ...cursorState }
352
+ const { balance, tokenBalances, rentExemptAmount } = account
353
+ const newData = {
354
+ balance,
355
+ rentExemptAmount,
356
+ tokenBalances,
357
+ stakingInfo: staking,
358
+ ...cursorState,
359
+ }
345
360
  return this.updateAccountState({ newData, walletAccount })
346
361
  }
347
362
 
package/src/tx-send.js CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  TOKEN_2022_PROGRAM_ID,
6
6
  TOKEN_PROGRAM_ID,
7
7
  } from '@exodus/solana-lib'
8
- import { transactionToBase58 } from '@exodus/solana-lib/src/tx/common.js'
9
8
  import assert from 'minimalistic-assert'
10
9
 
11
10
  export const createAndBroadcastTXFactory =
@@ -34,6 +33,7 @@ export const createAndBroadcastTXFactory =
34
33
  expectedMintAddress,
35
34
  metadataAddress,
36
35
  creators,
36
+ priorityFee,
37
37
  // </MagicEden>
38
38
  reference,
39
39
  memo,
@@ -129,7 +129,7 @@ export const createAndBroadcastTXFactory =
129
129
  from,
130
130
  to: address,
131
131
  amount,
132
- fee: feeAmount,
132
+ fee: feeData.fee, // feeAmount includes the priortyFee
133
133
  recentBlockhash,
134
134
  feeData,
135
135
  reference,
@@ -139,25 +139,15 @@ export const createAndBroadcastTXFactory =
139
139
  ...magicEdenParams,
140
140
  })
141
141
 
142
- let { priorityFee } = feeData
143
-
144
142
  const transactionForFeeEstimation = prepareForSigning(unsignedTransaction)
145
143
 
146
- if (!priorityFee) {
147
- try {
148
- priorityFee = await api.getPriorityFee(transactionToBase58(transactionForFeeEstimation))
149
- } catch (e) {
150
- console.warn(`Failed to fetch priority fee: ${e.message}`)
151
- priorityFee = feeData.fallbackPriorityFee
152
- }
153
- }
154
-
155
- const { unitsConsumed: computeUnits } = await api.simulateUnsignedTransaction({
144
+ const { unitsConsumed: computeUnits, err } = await api.simulateUnsignedTransaction({
156
145
  message: transactionForFeeEstimation.message,
157
146
  })
147
+ if (err) throw new Error(JSON.stringify(err))
158
148
 
159
- unsignedTransaction.txData.priorityFee = priorityFee
160
- unsignedTransaction.txData.computeUnits = computeUnits
149
+ unsignedTransaction.txData.priorityFee = priorityFee ?? 0
150
+ unsignedTransaction.txData.computeUnits = computeUnits * feeData.computeUnitsMultiplier
161
151
 
162
152
  const { txId, rawTx } = await assetClientInterface.signTransaction({
163
153
  assetName: baseAsset.name,