@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 +1 -1
- package/src/api.js +3 -4
- package/src/connection.js +2 -5
- package/src/tx-log/solana-monitor.js +63 -18
package/package.json
CHANGED
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 (
|
|
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(
|
|
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(
|
|
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({
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
140
|
+
const balanceChanged = this.balanceChanged({ account: accountState, newAccount: account })
|
|
110
141
|
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
}
|