@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 +23 -0
- package/package.json +2 -2
- package/src/account-state.js +3 -3
- package/src/tx-log/me-solana-monitor.js +48 -58
- package/src/tx-log/solana-monitor.js +51 -22
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.
|
|
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": "
|
|
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
|
},
|
package/src/account-state.js
CHANGED
|
@@ -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:
|
|
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
|
|
57
|
+
async getAccountsAndBalances({ refresh, address, accountState, walletAccount }) {
|
|
57
58
|
if (!this.useMeMonitor) {
|
|
58
|
-
return super.
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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:
|
|
95
|
+
ticker: assetData.symbol,
|
|
87
96
|
decimals: balance.decimals,
|
|
88
97
|
}
|
|
89
98
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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:
|
|
111
|
+
balance: assetBalance.balance.rawBalance,
|
|
96
112
|
mintAddress,
|
|
97
|
-
tokenProgram:
|
|
113
|
+
tokenProgram: assetData.tokenProgram,
|
|
98
114
|
decimals: token.decimals,
|
|
99
|
-
feeBasisPoints:
|
|
100
|
-
maximumFee:
|
|
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
|
|
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({
|
|
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
|
-
|
|
304
|
-
|
|
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,
|
|
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)
|