@exodus/solana-api 3.6.2 → 3.7.0

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,15 @@
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.7.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.6.2...@exodus/solana-api@3.7.0) (2024-06-13)
7
+
8
+
9
+ ### Features
10
+
11
+ * use ME API for SOL fungible assets ([#2490](https://github.com/ExodusMovement/assets/issues/2490)) ([8234222](https://github.com/ExodusMovement/assets/commit/82342222f57949da4d7128f7f92baf6a0bc1781e))
12
+
13
+
14
+
6
15
  ## [3.6.2](https://github.com/ExodusMovement/assets/compare/@exodus/solana-api@3.6.1...@exodus/solana-api@3.6.2) (2024-06-11)
7
16
 
8
17
  **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.6.2",
3
+ "version": "3.7.0",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -46,7 +46,7 @@
46
46
  "@exodus/assets-testing": "^1.0.0",
47
47
  "@solana/web3.js": "^1.91.8"
48
48
  },
49
- "gitHead": "4fbd10547698f90dd605042ab7f0bd2a5a60dd82",
49
+ "gitHead": "0703f508ef7b1a34bc99f910e6220989382abc00",
50
50
  "bugs": {
51
51
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Asolana-api"
52
52
  },
package/src/api.js CHANGED
@@ -2,17 +2,17 @@ import BN from 'bn.js'
2
2
  import createApi from '@exodus/asset-json-rpc'
3
3
  import { retry } from '@exodus/simple-retry'
4
4
  import {
5
- getMetadataAccount,
5
+ buildRawTransaction,
6
+ computeBalance,
6
7
  deserializeMetaplexMetadata,
7
- getTransactionSimulationParams,
8
8
  filterAccountsByOwner,
9
- SYSTEM_PROGRAM_ID,
9
+ getMetadataAccount,
10
+ getTransactionSimulationParams,
11
+ SOL_DECIMAL,
10
12
  STAKE_PROGRAM_ID,
11
- TOKEN_PROGRAM_ID,
13
+ SYSTEM_PROGRAM_ID,
12
14
  TOKEN_2022_PROGRAM_ID,
13
- SOL_DECIMAL,
14
- computeBalance,
15
- buildRawTransaction,
15
+ TOKEN_PROGRAM_ID,
16
16
  } from '@exodus/solana-lib'
17
17
  import assert from 'assert'
18
18
  import lodash from 'lodash'
@@ -0,0 +1,18 @@
1
+ import wretch from 'wretch'
2
+ import urljoin from 'url-join'
3
+
4
+ const FLAGR_URL = 'https://flagr-w.magiceden.io/api/v1/evaluation'
5
+
6
+ const fetchFlagrEvaluation = async (wallet, flagKey) => {
7
+ const url = urljoin(FLAGR_URL)
8
+ const entityContext = { wallet }
9
+
10
+ try {
11
+ return await wretch(url).post({ entityContext, flagKey }).json()
12
+ } catch (error) {
13
+ console.error('Error fetching flagr:', error)
14
+ throw error
15
+ }
16
+ }
17
+
18
+ export default fetchFlagrEvaluation
@@ -0,0 +1,135 @@
1
+ import { SolanaMonitor } from './solana-monitor'
2
+ import wretch from 'wretch'
3
+ import urljoin from 'url-join'
4
+ import fetchFlagrEvaluation from './me-flagr'
5
+
6
+ const FLAGR_KEY = 'sol-balance-api'
7
+ const SOL_NATIVE = '11111111111111111111111111111111'
8
+
9
+ export class MeSolanaMonitor extends SolanaMonitor {
10
+ #extraHeaders
11
+
12
+ constructor(args) {
13
+ super(args)
14
+ this.useMeMonitor = false
15
+ this.#extraHeaders = args.extraHeaders
16
+ }
17
+
18
+ async startListener({ walletAccount }) {
19
+ const address = await this.aci.getReceiveAddress({
20
+ assetName: this.asset.name,
21
+ walletAccount,
22
+ useCache: true,
23
+ })
24
+ await this.initFlagr(address)
25
+ return super.startListener({ walletAccount })
26
+ }
27
+
28
+ request(path, contentType = 'application/json') {
29
+ return wretch(urljoin('https://api-mainnet.magiceden.io', path)).headers({
30
+ 'Content-Type': contentType,
31
+ ...this.#extraHeaders,
32
+ })
33
+ }
34
+
35
+ async initFlagr(address) {
36
+ // If one of the user's SOL addresses is whitelisted, use the ME API
37
+ if (this.useMeMonitor) return
38
+ try {
39
+ const response = await fetchFlagrEvaluation(address, FLAGR_KEY)
40
+ if (response.variantKey === 'on') {
41
+ this.useMeMonitor = true
42
+ }
43
+ } catch (error) {
44
+ console.error('Failed to fetch useMeMonitor config:', error)
45
+ }
46
+ }
47
+
48
+ getTokens() {
49
+ return this.api.tokens
50
+ }
51
+
52
+ isStakingEnabled() {
53
+ return this.staking.enabled
54
+ }
55
+
56
+ async getTokenAccounts({ address }) {
57
+ if (!this.useMeMonitor) {
58
+ return super.getTokenAccounts({ address })
59
+ }
60
+
61
+ const tokens = this.getTokens()
62
+ const body = [
63
+ {
64
+ address,
65
+ chain: 'solana',
66
+ },
67
+ ]
68
+
69
+ const { balances } = await this.request('v1/wallet/balances/fungible').post(body).json()
70
+
71
+ return balances
72
+ .filter((balance) => balance.asset.mintAddress !== SOL_NATIVE)
73
+ .map((balance) => {
74
+ const asset = balance.asset
75
+ const mintAddress = asset.mintAddress
76
+ const token = tokens.get(mintAddress) || {
77
+ // name here is the exodus unique identifier not the display name
78
+ name: 'unknown',
79
+ ticker: asset.symbol,
80
+ decimals: balance.decimals,
81
+ }
82
+
83
+ return {
84
+ tokenAccountAddress: asset.tokenAccount,
85
+ owner: balance.owner,
86
+ tokenName: token.name,
87
+ ticker: token.ticker,
88
+ balance: balance.balance.rawBalance,
89
+ mintAddress,
90
+ tokenProgram: asset.tokenProgram,
91
+ decimals: token.decimals,
92
+ feeBasisPoints: asset.feeBasisPoints ?? 0,
93
+ maximumFee: asset.maximumFee ?? 0,
94
+ }
95
+ })
96
+ }
97
+
98
+ async getAccount({ address, staking, tokenAccounts }) {
99
+ if (!this.useMeMonitor) {
100
+ return super.getAccount({ address })
101
+ }
102
+
103
+ const tokens = this.getTokens()
104
+ const body = [
105
+ {
106
+ address,
107
+ chain: 'solana',
108
+ },
109
+ ]
110
+
111
+ const { balances } = await this.request('v1/wallet/balances/fungible').post(body).json()
112
+
113
+ const result = {
114
+ balance: null,
115
+ tokenBalances: {},
116
+ }
117
+
118
+ balances.forEach((balanceData) => {
119
+ const { asset: responseAsset, balance } = balanceData
120
+ const mintAddress = responseAsset.mintAddress
121
+
122
+ if (mintAddress === SOL_NATIVE) {
123
+ // SOL balance
124
+ result.balance = this.asset.currency.baseUnit(balance.rawBalance)
125
+ } else if (tokens.has(mintAddress)) {
126
+ // Fungible token balances
127
+ const token = tokens.get(mintAddress)
128
+ const tokenKey = token.name
129
+ result.tokenBalances[tokenKey] = token.currency.baseUnit(balance.rawBalance)
130
+ }
131
+ })
132
+
133
+ return result
134
+ }
135
+ }
@@ -54,6 +54,7 @@ export class SolanaMonitor extends BaseMonitor {
54
54
  walletAccount,
55
55
  useCache: true,
56
56
  })
57
+
57
58
  return this.api.watchAddress({
58
59
  address,
59
60
  onMessage: (json) => {
@@ -133,6 +134,14 @@ export class SolanaMonitor extends BaseMonitor {
133
134
  return clearedLogItems
134
135
  }
135
136
 
137
+ async getTokenAccounts({ address }) {
138
+ return this.api.getTokenAccountsByOwner(address)
139
+ }
140
+
141
+ isStakingEnabled() {
142
+ return true
143
+ }
144
+
136
145
  async tick({ walletAccount, refresh }) {
137
146
  // Check for new wallet account
138
147
  await this.initWalletAccount({ walletAccount })
@@ -146,11 +155,12 @@ export class SolanaMonitor extends BaseMonitor {
146
155
  const stakingAddresses = await this.getStakingAddressesFromTxLog({ assetName, walletAccount })
147
156
 
148
157
  const fetchStakingInfo = this.tickCount[walletAccount] % this.ticksBetweenStakeFetches === 0
149
- const staking = fetchStakingInfo
150
- ? await this.getStakingInfo({ address, stakingAddresses })
151
- : { ...accountState.mem, staking: this.staking }
158
+ const staking =
159
+ this.isStakingEnabled() && fetchStakingInfo
160
+ ? await this.getStakingInfo({ address, stakingAddresses })
161
+ : { ...accountState.mem, staking: this.staking }
152
162
 
153
- const tokenAccounts = await this.api.getTokenAccountsByOwner(address)
163
+ const tokenAccounts = await this.getTokenAccounts({ address })
154
164
  const account = await this.getAccount({ address, staking, tokenAccounts })
155
165
 
156
166
  const balanceChanged = this.#balanceChanged({ account: accountState, newAccount: account })