@exodus/solana-api 2.5.31-alpha.1 → 2.5.31-alpha.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-api",
3
- "version": "2.5.31-alpha.1",
3
+ "version": "2.5.31-alpha.3",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
package/src/api.js CHANGED
@@ -25,6 +25,7 @@ import { Connection } from './connection'
25
25
  const RPC_URL = 'https://solana.a.exodus.io' // https://vip-api.mainnet-beta.solana.com/, https://api.mainnet-beta.solana.com
26
26
  const WS_ENDPOINT = 'wss://solana.a.exodus.io/ws' // not standard across all node providers (we're compatible only with Quicknode)
27
27
  const FORCE_HTTP = true // use https over ws
28
+ const TXS_LIMIT = 100
28
29
 
29
30
  // Tokens + SOL api support
30
31
  export class Api {
@@ -59,18 +60,16 @@ export class Api {
59
60
  async watchAddress({
60
61
  address,
61
62
  tokensAddresses = [],
62
- onMessage,
63
63
  handleAccounts,
64
64
  handleTransfers,
65
65
  handleReconnect,
66
66
  reconnectDelay,
67
67
  }) {
68
- if (this.connections[address]) return // already subscribed
68
+ if (FORCE_HTTP) return false
69
69
  const conn = new Connection({
70
70
  endpoint: this.wsUrl,
71
71
  address,
72
72
  tokensAddresses,
73
- onMsg: (json) => onMessage(json),
74
73
  callback: (updates) =>
75
74
  this.handleUpdates({ updates, address, handleAccounts, handleTransfers }),
76
75
  reconnectCallback: handleReconnect,
@@ -187,7 +186,7 @@ export class Api {
187
186
  })
188
187
  )
189
188
  )
190
- let txsId = txsResultsByAccount.reduce((arr, row) => arr.concat(row), []) // merge arrays
189
+ let txsId = txsResultsByAccount.reduce((arr, row) => arr.concat(row), []).slice(0, TXS_LIMIT) // merge arrays
191
190
  txsId = lodash.uniqBy(txsId, 'signature')
192
191
 
193
192
  // get txs details in parallel
package/src/connection.js CHANGED
@@ -20,7 +20,6 @@ export class Connection {
20
20
  address,
21
21
  tokensAddresses = [],
22
22
  callback,
23
- onMsg,
24
23
  reconnectCallback = () => {},
25
24
  reconnectDelay = DEFAULT_RECONNECT_DELAY,
26
25
  }) {
@@ -28,7 +27,6 @@ export class Connection {
28
27
  this.tokensAddresses = tokensAddresses
29
28
  this.endpoint = endpoint
30
29
  this.callback = callback
31
- this.onMsg = onMsg
32
30
  this.reconnectCallback = reconnectCallback
33
31
  this.reconnectDelay = reconnectDelay
34
32
 
@@ -138,7 +136,7 @@ export class Connection {
138
136
  debug('pushing msg to queue', msg)
139
137
  this.messageQueue.push(msg) // sub results
140
138
  }
141
- this.processMessages(json)
139
+ this.processMessages()
142
140
  } else {
143
141
  if (lodash.get(this.rpcQueue, json.id)) {
144
142
  this.rpcQueue[json.id].reject(new Error(json.error.message))
@@ -197,8 +195,7 @@ export class Connection {
197
195
  }
198
196
  }
199
197
 
200
- async processMessages(json) {
201
- if (this.onMsg) await this.onMsg(json)
198
+ async processMessages() {
202
199
  if (this.inProcessMessages) return null
203
200
  this.inProcessMessages = true
204
201
  try {
@@ -10,14 +10,25 @@ const DEFAULT_REMOTE_CONFIG = {
10
10
  staking: { enabled: true, pool: DEFAULT_POOL_ADDRESS },
11
11
  }
12
12
 
13
+ const TICKS_BETWEEN_HISTORY_FETCHES = 10
14
+ const TICKS_BETWEEN_STAKE_FETCHES = 5
15
+
13
16
  export class SolanaMonitor extends BaseMonitor {
14
- constructor({ api, includeUnparsed = false, ...args }) {
17
+ constructor({
18
+ api,
19
+ includeUnparsed = false,
20
+ ticksBetweenHistoryFetches = TICKS_BETWEEN_HISTORY_FETCHES,
21
+ ticksBetweenStakeFetches = TICKS_BETWEEN_STAKE_FETCHES,
22
+ ...args
23
+ }) {
15
24
  super(args)
16
25
  assert(api, 'api is required')
17
26
  this.api = api
18
27
  this.cursors = {}
19
28
  this.assets = {}
20
29
  this.staking = DEFAULT_REMOTE_CONFIG.staking
30
+ this.ticksBetweenStakeFetches = ticksBetweenStakeFetches
31
+ this.ticksBetweenHistoryFetches = ticksBetweenHistoryFetches
21
32
  this.includeUnparsed = includeUnparsed
22
33
  this.addHook('before-stop', (...args) => this.beforeStop(...args))
23
34
  }
@@ -41,10 +52,15 @@ export class SolanaMonitor extends BaseMonitor {
41
52
  })
42
53
  return this.api.watchAddress({
43
54
  address,
44
- onMessage: (json) => {
45
- // new SOL tx event, tick monitor with 15 sec delay (to avoid hitting delayed nodes)
46
- setTimeout(() => this.tick({ walletAccount }), 15_000)
55
+ /*
56
+ // OPTIONAL. Relying on polling through ws
57
+ tokensAddresses: [], // needed for ASA subs
58
+ handleAccounts: (updates) => this.accountsCallback({ updates, walletAccount }),
59
+ handleTransfers: (txs) => {
60
+ // new SOL tx, ticking monitor
61
+ this.tick({ walletAccount }) // it will cause refresh for both sender/receiver. Without necessarily fetching the tx if it's not finalized in the node.
47
62
  },
63
+ */
48
64
  })
49
65
  }
50
66
 
@@ -87,6 +103,20 @@ export class SolanaMonitor extends BaseMonitor {
87
103
  return _.uniq(_.flatten(stakingAddresses))
88
104
  }
89
105
 
106
+ balanceChanged({ account, newAccount }) {
107
+ const solBalanceChanged = !account.balance || !account.balance.equals(newAccount.balance)
108
+ if (solBalanceChanged) return true
109
+
110
+ const tokenBalanceChanged =
111
+ !account.tokenBalances ||
112
+ Object.entries(newAccount.tokenBalances).some(
113
+ ([token, balance]) =>
114
+ !account.tokenBalances[token] || !account.tokenBalances[token].equals(balance)
115
+ )
116
+
117
+ return tokenBalanceChanged
118
+ }
119
+
90
120
  async tick({ walletAccount, refresh }) {
91
121
  // Check for new wallet account
92
122
  await this.initWalletAccount({ walletAccount })
@@ -99,28 +129,43 @@ export class SolanaMonitor extends BaseMonitor {
99
129
  const address = await this.aci.getReceiveAddress({ assetName, walletAccount, useCache: true })
100
130
  const stakingAddresses = await this.getStakingAddressesFromTxLog({ assetName, walletAccount })
101
131
 
102
- const { logItemsByAsset, hasNewTxs, cursorState } = await this.getHistory({
103
- address,
104
- accountState,
105
- walletAccount,
106
- refresh,
107
- })
132
+ const fetchStakingInfo = this.tickCount[walletAccount] % this.ticksBetweenStakeFetches === 0
133
+ const staking = fetchStakingInfo
134
+ ? await this.getStakingInfo({ address, stakingAddresses })
135
+ : accountState.mem
136
+
137
+ const tokenAccounts = await this.api.getTokenAccountsByOwner(address)
138
+ const account = await this.getAccount({ address, staking, tokenAccounts })
108
139
 
109
- const cursorChanged = this.hasNewCursor({ walletAccount, cursorState })
140
+ const balanceChanged = this.balanceChanged({ account: accountState, newAccount: account })
110
141
 
111
- if (refresh || hasNewTxs || cursorChanged) {
112
- const staking =
113
- refresh || cursorChanged
114
- ? await this.getStakingInfo({ address, stakingAddresses })
115
- : accountState.mem
142
+ const isHistoryUpdateTick =
143
+ this.tickCount[walletAccount] % this.ticksBetweenHistoryFetches === 0
116
144
 
117
- const tokenAccounts = await this.api.getTokenAccountsByOwner(address)
118
- const account = await this.getAccount({ address, staking, tokenAccounts })
145
+ const shouldUpdateHistory = refresh || isHistoryUpdateTick || balanceChanged
146
+ const shouldUpdateOnlyBalance = balanceChanged && !shouldUpdateHistory
147
+ const shouldUpdateBalanceBeforeHistory = true
119
148
 
149
+ // getHistory is more likely to fail/be rate limited, so we want to update users balance only on a lot of ticks
150
+ if (shouldUpdateBalanceBeforeHistory || shouldUpdateOnlyBalance) {
120
151
  // update all state at once
152
+ await this.updateState({ account, walletAccount, staking })
121
153
  await this.emitUnknownTokensEvent({ tokenAccounts })
154
+ }
155
+ if (shouldUpdateHistory) {
156
+ const { logItemsByAsset, cursorState } = await this.getHistory({
157
+ address,
158
+ accountState,
159
+ walletAccount,
160
+ refresh,
161
+ })
162
+
163
+ const cursorChanged = this.hasNewCursor({ walletAccount, cursorState })
164
+
165
+ // update all state at once
122
166
  await this.updateTxLogByAsset({ walletAccount, logItemsByAsset, refresh })
123
167
  await this.updateState({ account, cursorState, walletAccount, staking })
168
+ await this.emitUnknownTokensEvent({ tokenAccounts })
124
169
  if (refresh || cursorChanged) {
125
170
  this.cursors[walletAccount] = cursorState.cursor
126
171
  }