@exodus/solana-api 2.5.31 → 2.5.32

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",
3
+ "version": "2.5.32",
4
4
  "description": "Exodus internal Solana asset API wrapper",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@exodus/asset-json-rpc": "^1.0.0",
24
- "@exodus/asset-lib": "^4.0.0",
24
+ "@exodus/asset-lib": "^4.1.0",
25
25
  "@exodus/assets": "^9.0.1",
26
26
  "@exodus/basic-utils": "^2.1.0",
27
27
  "@exodus/currency": "^2.3.2",
@@ -44,5 +44,5 @@
44
44
  "devDependencies": {
45
45
  "@exodus/assets-testing": "^1.0.0"
46
46
  },
47
- "gitHead": "9ec7084bcdfe14f1c6f11df393351885773046a6"
47
+ "gitHead": "6b37a481bbf82a7ff63a453ca66446fbd45fb4a8"
48
48
  }
package/src/api.js CHANGED
@@ -28,11 +28,12 @@ const FORCE_HTTP = true // use https over ws
28
28
 
29
29
  // Tokens + SOL api support
30
30
  export class Api {
31
- constructor({ rpcUrl, wsUrl, assets }) {
31
+ constructor({ rpcUrl, wsUrl, assets, txsLimit }) {
32
32
  this.setServer(rpcUrl)
33
33
  this.setWsEndpoint(wsUrl)
34
34
  this.setTokens(assets)
35
35
  this.tokensToSkip = {}
36
+ this.txsLimit = txsLimit
36
37
  this.connections = {}
37
38
  }
38
39
 
@@ -167,6 +168,7 @@ export class Api {
167
168
  * Get transactions from an address
168
169
  */
169
170
  async getTransactions(address, { cursor, before, limit, includeUnparsed = false } = {}) {
171
+ limit = limit || this.txsLimit
170
172
  let transactions = []
171
173
  // cursor is a txHash
172
174
 
@@ -1,6 +1,7 @@
1
1
  import { BaseMonitor } from '@exodus/asset-lib'
2
2
  import _ from 'lodash'
3
3
  import assert from 'minimalistic-assert'
4
+ import ms from 'ms'
4
5
 
5
6
  const DEFAULT_POOL_ADDRESS = '9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF' // Everstake
6
7
 
@@ -10,14 +11,26 @@ const DEFAULT_REMOTE_CONFIG = {
10
11
  staking: { enabled: true, pool: DEFAULT_POOL_ADDRESS },
11
12
  }
12
13
 
14
+ const TICKS_BETWEEN_HISTORY_FETCHES = 10
15
+ const TICKS_BETWEEN_STAKE_FETCHES = 5
16
+ const TX_STALE_AFTER = ms('2m') // mark txs as dropped after N minutes
17
+
13
18
  export class SolanaMonitor extends BaseMonitor {
14
- constructor({ api, includeUnparsed = false, ...args }) {
19
+ constructor({
20
+ api,
21
+ includeUnparsed = false,
22
+ ticksBetweenHistoryFetches = TICKS_BETWEEN_HISTORY_FETCHES,
23
+ ticksBetweenStakeFetches = TICKS_BETWEEN_STAKE_FETCHES,
24
+ ...args
25
+ }) {
15
26
  super(args)
16
27
  assert(api, 'api is required')
17
28
  this.api = api
18
29
  this.cursors = {}
19
30
  this.assets = {}
20
31
  this.staking = DEFAULT_REMOTE_CONFIG.staking
32
+ this.ticksBetweenStakeFetches = ticksBetweenStakeFetches
33
+ this.ticksBetweenHistoryFetches = ticksBetweenHistoryFetches
21
34
  this.includeUnparsed = includeUnparsed
22
35
  this.addHook('before-stop', (...args) => this.beforeStop(...args))
23
36
  }
@@ -87,6 +100,37 @@ export class SolanaMonitor extends BaseMonitor {
87
100
  return _.uniq(stakingAddresses.flat())
88
101
  }
89
102
 
103
+ #balanceChanged({ account, newAccount }) {
104
+ const solBalanceChanged = !account.balance || !account.balance.equals(newAccount.balance)
105
+ if (solBalanceChanged) return true
106
+
107
+ // token balance changed
108
+ return (
109
+ !account.tokenBalances ||
110
+ Object.entries(newAccount.tokenBalances).some(
111
+ ([token, balance]) =>
112
+ !account.tokenBalances[token] || !account.tokenBalances[token].equals(balance)
113
+ )
114
+ )
115
+ }
116
+
117
+ async markStaleTransactions({ walletAccount, logItemsByAsset = {} }) {
118
+ // mark stale txs as dropped in logItemsByAsset
119
+ const clearedLogItems = logItemsByAsset
120
+ const tokenNames = [...this.api.tokens.values()].map(({ name }) => name)
121
+ const assets = [this.asset.name, ...tokenNames]
122
+
123
+ for (const assetName of assets) {
124
+ const txSet = await this.aci.getTxLog({ assetName, walletAccount })
125
+ const { stale } = this.getUnconfirmed({ txSet, staleTxAge: TX_STALE_AFTER })
126
+ if (stale.length > 0) {
127
+ clearedLogItems[assetName] = _.unionBy(stale, logItemsByAsset[assetName], 'txId')
128
+ }
129
+ }
130
+
131
+ return clearedLogItems
132
+ }
133
+
90
134
  async tick({ walletAccount, refresh }) {
91
135
  // Check for new wallet account
92
136
  await this.initWalletAccount({ walletAccount })
@@ -99,28 +143,45 @@ export class SolanaMonitor extends BaseMonitor {
99
143
  const address = await this.aci.getReceiveAddress({ assetName, walletAccount, useCache: true })
100
144
  const stakingAddresses = await this.getStakingAddressesFromTxLog({ assetName, walletAccount })
101
145
 
102
- const { logItemsByAsset, hasNewTxs, cursorState } = await this.getHistory({
103
- address,
104
- accountState,
105
- walletAccount,
106
- refresh,
107
- })
146
+ const fetchStakingInfo = this.tickCount[walletAccount] % this.ticksBetweenStakeFetches === 0
147
+ const staking = fetchStakingInfo
148
+ ? await this.getStakingInfo({ address, stakingAddresses })
149
+ : accountState.mem
150
+
151
+ const tokenAccounts = await this.api.getTokenAccountsByOwner(address)
152
+ const account = await this.getAccount({ address, staking, tokenAccounts })
108
153
 
109
- const cursorChanged = this.hasNewCursor({ walletAccount, cursorState })
154
+ const balanceChanged = this.#balanceChanged({ account: accountState, newAccount: account })
110
155
 
111
- if (refresh || hasNewTxs || cursorChanged) {
112
- const staking =
113
- refresh || cursorChanged
114
- ? await this.getStakingInfo({ address, stakingAddresses })
115
- : accountState.mem
156
+ const isHistoryUpdateTick =
157
+ this.tickCount[walletAccount] % this.ticksBetweenHistoryFetches === 0
116
158
 
117
- const tokenAccounts = await this.api.getTokenAccountsByOwner(address)
118
- const account = await this.getAccount({ address, staking, tokenAccounts })
159
+ const shouldUpdateHistory = refresh || isHistoryUpdateTick || balanceChanged
160
+ const shouldUpdateOnlyBalance = balanceChanged && !shouldUpdateHistory
161
+ const shouldUpdateBalanceBeforeHistory = true
119
162
 
163
+ // getHistory is more likely to fail/be rate limited, so we want to update users balance only on a lot of ticks
164
+ if (shouldUpdateBalanceBeforeHistory || shouldUpdateOnlyBalance) {
120
165
  // update all state at once
166
+ await this.updateState({ account, walletAccount, staking })
121
167
  await this.emitUnknownTokensEvent({ tokenAccounts })
122
- await this.updateTxLogByAsset({ walletAccount, logItemsByAsset, refresh })
168
+ }
169
+
170
+ if (shouldUpdateHistory) {
171
+ const { logItemsByAsset, cursorState } = await this.getHistory({
172
+ address,
173
+ accountState,
174
+ walletAccount,
175
+ refresh,
176
+ })
177
+
178
+ const cursorChanged = this.hasNewCursor({ walletAccount, cursorState })
179
+
180
+ // update all state at once
181
+ const clearedLogItems = await this.markStaleTransactions({ walletAccount, logItemsByAsset })
182
+ await this.updateTxLogByAsset({ walletAccount, logItemsByAsset: clearedLogItems, refresh })
123
183
  await this.updateState({ account, cursorState, walletAccount, staking })
184
+ await this.emitUnknownTokensEvent({ tokenAccounts })
124
185
  if (refresh || cursorChanged) {
125
186
  this.cursors[walletAccount] = cursorState.cursor
126
187
  }
package/src/tx-send.js CHANGED
@@ -136,7 +136,9 @@ export const createAndBroadcastTXFactory =
136
136
  ? asset.currency.ZERO
137
137
  : amount.abs().negate()
138
138
 
139
- const data = isStakingTx ? { staking: stakingParams } : Object.create(null)
139
+ const data = isStakingTx
140
+ ? { staking: { ...stakingParams, stake: coinAmount.toBaseNumber() } }
141
+ : Object.create(null)
140
142
  const tx = {
141
143
  txId,
142
144
  confirmations: 0,