@exodus/solana-api 3.8.3 → 3.9.1

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,29 @@
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.9.1](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.9.0...@exodus/solana-api@3.9.1) (2024-07-15)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **solana:** harden objects against prototype pollution ([#2838](https://github.com/ExodusMovement/assets/issues/2838)) ([3374c58](https://github.com/ExodusMovement/assets/commit/3374c58de898aa38f96d6a4e30bc1fec3b48c691))
12
+
13
+
14
+
15
+ ## [3.9.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.8.3...@exodus/solana-api@3.9.0) (2024-07-08)
16
+
17
+
18
+ ### Features
19
+
20
+ * reuse SOL API results for getAccount and getTokenAccounts ([#2666](https://github.com/ExodusMovement/assets/issues/2666)) ([4e96f4c](https://github.com/ExodusMovement/assets/commit/4e96f4c66d7783b113f0cbdf73d30bb605ae0534))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * update SOL staking info on balance change ([#2672](https://github.com/ExodusMovement/assets/issues/2672)) ([bc2043c](https://github.com/ExodusMovement/assets/commit/bc2043ce226128d3e321937a325e20382f12f874))
26
+
27
+
28
+
6
29
  ## [3.8.3](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.8.2...@exodus/solana-api@3.8.3) (2024-06-27)
7
30
 
8
31
  **Note:** Version bump only for package @exodus/solana-api
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "3.8.3",
3
+ "version": "3.9.1",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -47,7 +47,7 @@
47
47
  "@exodus/assets-testing": "^1.0.0",
48
48
  "@solana/web3.js": "^1.91.8"
49
49
  },
50
- "gitHead": "8a78b9595437fd8fecc5b08916a98584eabde577",
50
+ "gitHead": "054951dbfb7fb897b4b072e35fd5601920393b71",
51
51
  "bugs": {
52
52
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
53
53
  },
@@ -16,7 +16,7 @@ export const createAccountState = ({ assetList }) => {
16
16
  static defaults = {
17
17
  cursor: '',
18
18
  balance: asset.currency.ZERO,
19
- tokenBalances: {},
19
+ tokenBalances: Object.create(null),
20
20
  mem: {
21
21
  loaded: false,
22
22
  staking: {
@@ -30,7 +30,7 @@ export const createAccountState = ({ assetList }) => {
30
30
  pending: asset.currency.defaultUnit(0),
31
31
  activating: asset.currency.defaultUnit(0),
32
32
  earned: asset.currency.defaultUnit(0),
33
- accounts: {}, // stake accounts
33
+ accounts: Object.create(null), // stake accounts
34
34
  },
35
35
  }
36
36
 
@@ -46,7 +46,7 @@ export const createAccountState = ({ assetList }) => {
46
46
  assets[assetName]
47
47
  ? Object.assign(r, { [assetName]: parseBalance(tokenBalance, assets[assetName]) })
48
48
  : r,
49
- {}
49
+ Object.create(null)
50
50
  ),
51
51
  }
52
52
  }
@@ -1,7 +1,8 @@
1
- import { SolanaMonitor } from './solana-monitor'
2
- import wretch from 'wretch'
3
1
  import urljoin from 'url-join'
2
+ import wretch from 'wretch'
3
+
4
4
  import fetchFlagrEvaluation from './me-flagr'
5
+ import { SolanaMonitor } from './solana-monitor'
5
6
 
6
7
  const FLAGR_KEY = 'sol-balance-api'
7
8
  const SOL_NATIVE = '11111111111111111111111111111111'
@@ -53,9 +54,9 @@ export class MeSolanaMonitor extends SolanaMonitor {
53
54
  return !this.useMeMonitor
54
55
  }
55
56
 
56
- async getTokenAccounts({ address }) {
57
+ async getAccountsAndBalances({ refresh, address, accountState, walletAccount }) {
57
58
  if (!this.useMeMonitor) {
58
- return super.getTokenAccounts({ address })
59
+ return super.getAccountsAndBalances({ refresh, address, accountState, walletAccount })
59
60
  }
60
61
 
61
62
  const tokens = this.getTokens()
@@ -68,75 +69,64 @@ export class MeSolanaMonitor extends SolanaMonitor {
68
69
 
69
70
  const { balances } = await this.request('v1/wallet/balances/fungible').post(body).json()
70
71
  const metadata = new Map()
71
- balances.forEach((balance) => {
72
- if (balance.asset?.id) {
73
- metadata.set(balance.asset.id, { imageURL: balance.image })
72
+
73
+ const account = {
74
+ balance: this.asset.currency.ZERO,
75
+ tokenBalances: Object.create(null),
76
+ }
77
+ const tokenAccounts = []
78
+
79
+ balances.forEach((assetBalance) => {
80
+ const { asset: assetData, balance } = assetBalance
81
+ const mintAddress = assetData.mintAddress
82
+
83
+ if (assetBalance.asset?.id) {
84
+ metadata.set(assetBalance.asset.id, { imageURL: assetBalance.image })
74
85
  }
75
- })
76
- this.emit('token-metadata', { source: 'solana', metadata })
77
86
 
78
- return balances
79
- .filter((balance) => balance.asset.mintAddress !== SOL_NATIVE)
80
- .map((balance) => {
81
- const asset = balance.asset
82
- const mintAddress = asset.mintAddress
87
+ if (mintAddress === SOL_NATIVE) {
88
+ // SOL balance
89
+ account.balance = this.asset.currency.baseUnit(balance.rawBalance)
90
+ } else {
91
+ // Fungible token balances
83
92
  const token = tokens.get(mintAddress) || {
84
93
  // name here is the exodus unique identifier not the display name
85
94
  name: 'unknown',
86
- ticker: asset.symbol,
95
+ ticker: assetData.symbol,
87
96
  decimals: balance.decimals,
88
97
  }
89
98
 
90
- return {
91
- tokenAccountAddress: asset.tokenAccount,
92
- owner: balance.owner,
99
+ if (tokens.get(mintAddress)) {
100
+ const tokenKey = token.name
101
+ Object.defineProperty(account.tokenBalances, tokenKey, {
102
+ value: token.currency.baseUnit(balance.rawBalance),
103
+ })
104
+ }
105
+
106
+ const tokenAccount = {
107
+ tokenAccountAddress: assetData.tokenAccount,
108
+ owner: assetBalance.owner,
93
109
  tokenName: token.name,
94
110
  ticker: token.ticker,
95
- balance: balance.balance.rawBalance,
111
+ balance: assetBalance.balance.rawBalance,
96
112
  mintAddress,
97
- tokenProgram: asset.tokenProgram,
113
+ tokenProgram: assetData.tokenProgram,
98
114
  decimals: token.decimals,
99
- feeBasisPoints: asset.feeBasisPoints ?? 0,
100
- maximumFee: asset.maximumFee ?? 0,
115
+ feeBasisPoints: assetData.feeBasisPoints ?? 0,
116
+ maximumFee: assetData.maximumFee ?? 0,
101
117
  }
102
- })
103
- }
104
-
105
- async getAccount({ address, staking, tokenAccounts }) {
106
- if (!this.useMeMonitor) {
107
- return super.getAccount({ address, staking, tokenAccounts })
108
- }
109
-
110
- const tokens = this.getTokens()
111
- const body = [
112
- {
113
- address,
114
- chain: 'solana',
115
- },
116
- ]
117
-
118
- const { balances } = await this.request('v1/wallet/balances/fungible').post(body).json()
119
-
120
- const result = {
121
- balance: this.asset.currency.ZERO,
122
- tokenBalances: {},
123
- }
124
-
125
- balances.forEach((balanceData) => {
126
- const { asset: responseAsset, balance } = balanceData
127
- const mintAddress = responseAsset.mintAddress
128
-
129
- if (mintAddress === SOL_NATIVE) {
130
- // SOL balance
131
- result.balance = this.asset.currency.baseUnit(balance.rawBalance)
132
- } else if (tokens.has(mintAddress)) {
133
- // Fungible token balances
134
- const token = tokens.get(mintAddress)
135
- const tokenKey = token.name
136
- result.tokenBalances[tokenKey] = token.currency.baseUnit(balance.rawBalance)
118
+ tokenAccounts.push(tokenAccount)
137
119
  }
138
120
  })
121
+ this.emit('token-metadata', { source: 'solana', metadata })
122
+
123
+ const fetchStakingInfo =
124
+ refresh || this.tickCount[walletAccount] % this.ticksBetweenStakeFetches === 0
125
+ const staking =
126
+ this.isStakingEnabled() && fetchStakingInfo
127
+ ? await this.getStakingInfo({ address, walletAccount })
128
+ : { ...accountState.mem, staking: this.staking }
139
129
 
140
- return result
130
+ return { account, tokenAccounts, staking }
141
131
  }
142
132
  }
@@ -134,14 +134,23 @@ export class SolanaMonitor extends BaseMonitor {
134
134
  return clearedLogItems
135
135
  }
136
136
 
137
- async getTokenAccounts({ address }) {
138
- return this.api.getTokenAccountsByOwner(address)
139
- }
140
-
141
137
  isStakingEnabled() {
142
138
  return true
143
139
  }
144
140
 
141
+ async getAccountsAndBalances({ refresh, address, accountState, walletAccount }) {
142
+ const tokenAccounts = await this.api.getTokenAccountsByOwner(address)
143
+ const { account, staking } = await this.getAccount({
144
+ refresh,
145
+ address,
146
+ tokenAccounts,
147
+ accountState,
148
+ walletAccount,
149
+ })
150
+
151
+ return { account, tokenAccounts, staking }
152
+ }
153
+
145
154
  async tick({ walletAccount, refresh }) {
146
155
  // Check for new wallet account
147
156
  await this.initWalletAccount({ walletAccount })
@@ -152,17 +161,13 @@ export class SolanaMonitor extends BaseMonitor {
152
161
 
153
162
  const accountState = await this.aci.getAccountState({ assetName, walletAccount })
154
163
  const address = await this.aci.getReceiveAddress({ assetName, walletAccount, useCache: true })
155
- const stakingAddresses = await this.getStakingAddressesFromTxLog({ assetName, walletAccount })
156
-
157
- const fetchStakingInfo = this.tickCount[walletAccount] % this.ticksBetweenStakeFetches === 0
158
- const staking =
159
- this.isStakingEnabled() && fetchStakingInfo
160
- ? await this.getStakingInfo({ address, stakingAddresses })
161
- : { ...accountState.mem, staking: this.staking }
162
-
163
- const tokenAccounts = await this.getTokenAccounts({ address })
164
- const account = await this.getAccount({ address, staking, tokenAccounts })
165
164
 
165
+ const { account, tokenAccounts, staking } = await this.getAccountsAndBalances({
166
+ refresh,
167
+ address,
168
+ accountState,
169
+ walletAccount,
170
+ })
166
171
  const balanceChanged = this.#balanceChanged({ account: accountState, newAccount: account })
167
172
 
168
173
  const isHistoryUpdateTick =
@@ -278,13 +283,34 @@ export class SolanaMonitor extends BaseMonitor {
278
283
  }
279
284
  }
280
285
 
281
- async getAccount({ address, staking, tokenAccounts }) {
286
+ async getAccount({ refresh, address, tokenAccounts, accountState, walletAccount }) {
282
287
  const tokens = Object.keys(this.assets).filter((name) => name !== this.asset.name)
283
288
  const [solBalance, splBalances] = await Promise.all([
284
289
  this.api.getBalance(address),
285
290
  this.api.getTokensBalance({ address, filterByTokens: tokens, tokenAccounts }),
286
291
  ])
287
292
 
293
+ const tokenBalances = _.mapValues(splBalances, (balance, name) =>
294
+ this.assets[name].currency.baseUnit(balance).toDefault()
295
+ )
296
+
297
+ const solBalanceChanged = this.#balanceChanged({
298
+ account: accountState,
299
+ newAccount: {
300
+ balance: this.asset.currency.baseUnit(solBalance), // balance without staking
301
+ tokenBalances,
302
+ },
303
+ })
304
+ const fetchStakingInfo =
305
+ refresh ||
306
+ solBalanceChanged ||
307
+ this.tickCount[walletAccount] % this.ticksBetweenStakeFetches === 0
308
+
309
+ const staking =
310
+ this.isStakingEnabled() && fetchStakingInfo
311
+ ? await this.getStakingInfo({ address, walletAccount })
312
+ : { ...accountState.mem, staking: this.staking }
313
+
288
314
  const stakedBalance = this.asset.currency.baseUnit(staking.locked)
289
315
  const withdrawableBalance = this.asset.currency.baseUnit(staking.withdrawable)
290
316
  const pendingBalance = this.asset.currency.baseUnit(staking.pending)
@@ -295,13 +321,12 @@ export class SolanaMonitor extends BaseMonitor {
295
321
  .add(pendingBalance)
296
322
  .toDefault()
297
323
 
298
- const tokenBalances = _.mapValues(splBalances, (balance, name) =>
299
- this.assets[name].currency.baseUnit(balance).toDefault()
300
- )
301
-
302
324
  return {
303
- balance,
304
- tokenBalances,
325
+ account: {
326
+ balance,
327
+ tokenBalances,
328
+ },
329
+ staking,
305
330
  }
306
331
  }
307
332
 
@@ -311,8 +336,12 @@ export class SolanaMonitor extends BaseMonitor {
311
336
  return this.updateAccountState({ newData, walletAccount })
312
337
  }
313
338
 
314
- async getStakingInfo({ address, stakingAddresses = [] }) {
339
+ async getStakingInfo({ address, walletAccount }) {
315
340
  const stakingInfo = await this.api.getStakeAccountsInfo(address)
341
+ const stakingAddresses = await this.getStakingAddressesFromTxLog({
342
+ assetName: this.asset.name,
343
+ walletAccount,
344
+ })
316
345
  // merge current and old staking addresses
317
346
  const allStakingAddresses = _.uniq([...Object.keys(stakingInfo.accounts), ...stakingAddresses])
318
347
  const rewards = await this.api.getRewards(allStakingAddresses)