@exodus/balances 13.16.1 → 13.17.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.
Files changed (133) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/atoms/asset-names-with-balance.d.ts +1 -0
  3. package/atoms/asset-names-with-balance.js +1 -40
  4. package/atoms/asset-total-wallet-amounts.d.ts +1 -0
  5. package/atoms/asset-total-wallet-amounts.js +1 -47
  6. package/atoms/assets-with-balance-count.d.ts +1 -0
  7. package/atoms/assets-with-balance-count.js +1 -8
  8. package/atoms/balances.d.ts +1 -0
  9. package/atoms/balances.js +1 -6
  10. package/atoms/funded-wallet-accounts.d.ts +1 -0
  11. package/atoms/funded-wallet-accounts.js +1 -25
  12. package/atoms/has-balance.d.ts +1 -0
  13. package/atoms/has-balance.js +1 -31
  14. package/atoms/index.d.ts +8 -0
  15. package/atoms/index.js +8 -54
  16. package/default-config.d.ts +1 -0
  17. package/default-config.js +1 -26
  18. package/index.d.ts +2 -35
  19. package/index.js +1 -65
  20. package/lib/atoms/asset-names-with-balance.d.ts +5 -0
  21. package/lib/atoms/asset-names-with-balance.js +31 -0
  22. package/lib/atoms/asset-total-wallet-amounts.d.ts +5 -0
  23. package/lib/atoms/asset-total-wallet-amounts.js +39 -0
  24. package/lib/atoms/assets-with-balance-count.d.ts +5 -0
  25. package/lib/atoms/assets-with-balance-count.js +6 -0
  26. package/lib/atoms/balances.d.ts +3 -0
  27. package/lib/atoms/balances.js +3 -0
  28. package/lib/atoms/funded-wallet-accounts.d.ts +8 -0
  29. package/lib/atoms/funded-wallet-accounts.js +19 -0
  30. package/lib/atoms/has-balance.d.ts +7 -0
  31. package/lib/atoms/has-balance.js +21 -0
  32. package/lib/atoms/index.d.ts +54 -0
  33. package/lib/atoms/index.js +48 -0
  34. package/lib/default-config.d.ts +3 -0
  35. package/lib/default-config.js +24 -0
  36. package/lib/index.d.ts +187 -0
  37. package/lib/index.js +49 -0
  38. package/lib/module/balances-utils.d.ts +13 -0
  39. package/lib/module/balances-utils.js +70 -0
  40. package/lib/module/index.d.ts +11 -0
  41. package/lib/module/index.js +367 -0
  42. package/lib/module/mockable-balances.d.ts +16 -0
  43. package/lib/module/mockable-balances.js +21 -0
  44. package/lib/module/types.d.ts +80 -0
  45. package/lib/module/types.js +1 -0
  46. package/lib/plugins/analytics.d.ts +18 -0
  47. package/lib/plugins/analytics.js +30 -0
  48. package/lib/plugins/index.d.ts +35 -0
  49. package/lib/plugins/index.js +22 -0
  50. package/lib/plugins/lifecycle.d.ts +14 -0
  51. package/lib/plugins/lifecycle.js +32 -0
  52. package/lib/redux/id.d.ts +2 -0
  53. package/lib/redux/id.js +1 -0
  54. package/lib/redux/index.d.ts +588 -0
  55. package/lib/redux/index.js +53 -0
  56. package/lib/redux/initial-state.d.ts +3 -0
  57. package/lib/redux/initial-state.js +6 -0
  58. package/lib/redux/multi-account-helper.d.ts +463 -0
  59. package/lib/redux/multi-account-helper.js +23 -0
  60. package/lib/redux/selectors/by-asset.d.ts +12 -0
  61. package/lib/redux/selectors/by-asset.js +19 -0
  62. package/lib/redux/selectors/create-balance-selectors.d.ts +70 -0
  63. package/lib/redux/selectors/create-balance-selectors.js +32 -0
  64. package/lib/redux/selectors/create-balances.d.ts +11 -0
  65. package/lib/redux/selectors/create-balances.js +13 -0
  66. package/lib/redux/selectors/create-fuel-threshold.d.ts +19 -0
  67. package/lib/redux/selectors/create-fuel-threshold.js +22 -0
  68. package/lib/redux/selectors/create-requires-fuel-threshold.d.ts +19 -0
  69. package/lib/redux/selectors/create-requires-fuel-threshold.js +29 -0
  70. package/lib/redux/selectors/create-unconfirmed-balance.d.ts +15 -0
  71. package/lib/redux/selectors/create-unconfirmed-balance.js +14 -0
  72. package/lib/redux/selectors/get-balance-for-field.d.ts +14 -0
  73. package/lib/redux/selectors/get-balance-for-field.js +19 -0
  74. package/lib/redux/selectors/get-balance-selectors.d.ts +68 -0
  75. package/lib/redux/selectors/get-balance-selectors.js +32 -0
  76. package/lib/redux/selectors/get-balances.d.ts +10 -0
  77. package/lib/redux/selectors/get-balances.js +13 -0
  78. package/lib/redux/selectors/get-unconfirmed-balance.d.ts +19 -0
  79. package/lib/redux/selectors/get-unconfirmed-balance.js +27 -0
  80. package/lib/redux/selectors/index.d.ts +339 -0
  81. package/lib/redux/selectors/index.js +22 -0
  82. package/lib/redux/types.d.ts +45 -0
  83. package/lib/redux/types.js +1 -0
  84. package/lib/report/index.d.ts +34 -0
  85. package/lib/report/index.js +51 -0
  86. package/lib/types.d.ts +24 -0
  87. package/lib/types.js +1 -0
  88. package/module/balances-utils.d.ts +1 -0
  89. package/module/balances-utils.js +1 -86
  90. package/module/index.d.ts +2 -0
  91. package/module/index.js +2 -497
  92. package/module/mockable-balances.d.ts +1 -0
  93. package/module/mockable-balances.js +1 -27
  94. package/package.json +23 -17
  95. package/plugins/analytics.d.ts +1 -0
  96. package/plugins/analytics.js +1 -48
  97. package/plugins/index.d.ts +4 -0
  98. package/plugins/index.js +4 -24
  99. package/plugins/lifecycle.d.ts +1 -0
  100. package/plugins/lifecycle.js +1 -40
  101. package/redux/id.d.ts +1 -0
  102. package/redux/id.js +1 -1
  103. package/redux/index.d.ts +2 -0
  104. package/redux/index.js +1 -61
  105. package/redux/initial-state.d.ts +1 -0
  106. package/redux/initial-state.js +1 -8
  107. package/redux/multi-account-helper.d.ts +1 -0
  108. package/redux/multi-account-helper.js +1 -14
  109. package/redux/selectors/by-asset.d.ts +1 -0
  110. package/redux/selectors/by-asset.js +1 -25
  111. package/redux/selectors/create-balance-selectors.d.ts +1 -0
  112. package/redux/selectors/create-balance-selectors.js +1 -48
  113. package/redux/selectors/create-balances.d.ts +1 -0
  114. package/redux/selectors/create-balances.js +1 -26
  115. package/redux/selectors/create-fuel-threshold.d.ts +1 -0
  116. package/redux/selectors/create-fuel-threshold.js +1 -32
  117. package/redux/selectors/create-requires-fuel-threshold.d.ts +1 -0
  118. package/redux/selectors/create-requires-fuel-threshold.js +1 -59
  119. package/redux/selectors/create-unconfirmed-balance.d.ts +1 -0
  120. package/redux/selectors/create-unconfirmed-balance.js +1 -28
  121. package/redux/selectors/get-balance-for-field.d.ts +1 -0
  122. package/redux/selectors/get-balance-for-field.js +1 -34
  123. package/redux/selectors/get-balance-selectors.d.ts +1 -0
  124. package/redux/selectors/get-balance-selectors.js +1 -42
  125. package/redux/selectors/get-balances.d.ts +1 -0
  126. package/redux/selectors/get-balances.js +1 -24
  127. package/redux/selectors/get-unconfirmed-balance.d.ts +2 -0
  128. package/redux/selectors/get-unconfirmed-balance.js +2 -43
  129. package/redux/selectors/index.d.ts +1 -0
  130. package/redux/selectors/index.js +1 -23
  131. package/report/index.d.ts +1 -0
  132. package/report/index.js +1 -61
  133. package/tsconfig.json +0 -8
package/module/index.js CHANGED
@@ -1,497 +1,2 @@
1
- import { safeString } from '@exodus/safe-string'
2
- import { TxSet } from '@exodus/models'
3
- import { difference as getChanges } from '@exodus/atoms'
4
- import { difference, pick, set, mapValues } from '@exodus/basic-utils'
5
- import makeConcurrent from 'make-concurrent'
6
- import { processAssetBalances, validateBalances } from './balances-utils.js'
7
- import defaultConfig from '../default-config.js'
8
-
9
- const zipObject = (keys, values) => {
10
- const result = Object.create(null)
11
- for (const [i, key] of keys.entries()) {
12
- result[key] = values[i]
13
- }
14
-
15
- return result
16
- }
17
-
18
- export const cloneBalances = (balances) => {
19
- const result = Object.create(null)
20
- for (const walletAccount of Object.keys(balances)) {
21
- const accountBalances = balances[walletAccount]
22
- const clonedAccount = Object.create(null)
23
- for (const assetName of Object.keys(accountBalances)) {
24
- clonedAccount[assetName] = { ...accountBalances[assetName] }
25
- }
26
-
27
- result[walletAccount] = clonedAccount
28
- }
29
-
30
- return result
31
- }
32
-
33
- const zipNonNull = (keys, values) => {
34
- const result = Object.create(null)
35
- for (const [i, key] of keys.entries()) {
36
- if (values[i] != null) result[key] = values[i]
37
- }
38
-
39
- return result
40
- }
41
-
42
- class Balances {
43
- #logger
44
- #assetsAtom
45
- #assets = Object.create(null)
46
- #availableAssetNamesAtom
47
- #availableAssetNames = []
48
- #balanceFieldsConfig
49
- #balanceFields
50
- #walletAccounts = [] // string[]
51
- #ready = false
52
-
53
- #balances = Object.create(null)
54
- #balancesAtom
55
- #blockchainMetadataAtom
56
- #feeDataAtom
57
- #enabledWalletAccountsAtom
58
- #loaded = false
59
- #subscriptions = []
60
- #errorTracking
61
- constructor({
62
- assetsAtom,
63
- availableAssetNamesAtom,
64
- enabledWalletAccountsAtom,
65
- config,
66
- logger,
67
- balancesAtom,
68
- blockchainMetadataAtom,
69
- feeDataAtom,
70
- errorTracking,
71
- }) {
72
- this.#logger = logger
73
- this.#assetsAtom = assetsAtom
74
- this.#availableAssetNamesAtom = availableAssetNamesAtom
75
- this.#balanceFieldsConfig = config?.balanceFieldsConfig || defaultConfig.balanceFieldsConfig
76
- this.#balanceFields = config?.balanceFields || defaultConfig.balanceFields
77
- this.#balancesAtom = balancesAtom
78
- this.#blockchainMetadataAtom = blockchainMetadataAtom
79
- this.#feeDataAtom = feeDataAtom
80
- this.#enabledWalletAccountsAtom = enabledWalletAccountsAtom
81
- this.#errorTracking = errorTracking
82
-
83
- const shareQueue =
84
- (fn) =>
85
- (...args) => {
86
- if (!this.#ready) return
87
-
88
- this.#pushIntoSharedQueue(() => fn.apply(this, args))
89
- }
90
-
91
- const swallowErrors =
92
- (fn, method) =>
93
- async (...args) => {
94
- try {
95
- return await fn(...args)
96
- } catch (error) {
97
- this.#errorTracking.track({ error: new Error(method, { cause: error }) })
98
- }
99
- }
100
-
101
- // these share a queue
102
- this.#updateAssetSource = shareQueue(this.#updateAssetSource)
103
- this.#handleBlockchainMetadataUpdate = shareQueue(this.#handleBlockchainMetadataUpdate)
104
- this.#handleFeeDataUpdate = shareQueue(this.#handleFeeDataUpdate)
105
- this.#recompute = shareQueue(this.#recompute)
106
- this.#onAssetsChanged = shareQueue(this.#onAssetsChanged)
107
- this.#getBalancesForAssetSource = swallowErrors(
108
- this.#getBalancesForAssetSource,
109
- safeString`getBalancesForAssetSource`
110
- )
111
- this.#setWalletAccounts = swallowErrors(this.#setWalletAccounts, safeString`setWalletAccounts`)
112
- }
113
-
114
- load = async () => {
115
- if (this.#loaded) return
116
- this.#loaded = true
117
-
118
- const { value } = await this.#assetsAtom.get()
119
- this.#assets = value
120
-
121
- this.#subscriptions.push(
122
- this.#assetsAtom.observe(({ value }) => {
123
- this.#assets = value
124
- }),
125
- this.#enabledWalletAccountsAtom.observe((payload) => {
126
- void this.#setWalletAccounts(Object.keys(payload))
127
- }),
128
- this.#blockchainMetadataAtom.observe(({ accountStateChanges, txLogChanges }) => {
129
- this.#flattenToBlockchainMetadataObjects({ accountStateChanges, txLogChanges }).forEach(
130
- ({ walletAccount, assetName, accountState, txLogsByAssetName }) =>
131
- this.#handleBlockchainMetadataUpdate({
132
- walletAccount,
133
- assetName,
134
- accountState,
135
- txLogsByAssetName,
136
- })
137
- )
138
- }),
139
- this.#feeDataAtom.observe((changes) => {
140
- Object.entries(changes).forEach(([assetName, feeData]) => {
141
- const asset = this.#getAsset(assetName).baseAsset
142
- if (asset.api?.features?.balancesRequireFeeData) {
143
- this.#walletAccounts.forEach((walletAccount) => {
144
- return this.#handleFeeDataUpdate({ walletAccount, assetName, feeData })
145
- })
146
- }
147
- })
148
- }),
149
-
150
- getChanges(this.#availableAssetNamesAtom).observe(({ previous = [], current }) => {
151
- this.#availableAssetNames = current
152
- const added = difference(current, previous)
153
- const removed = difference(previous, current)
154
- if (added.length > 0 || removed.length > 0) void this.#onAssetsChanged({ added, removed })
155
- })
156
- )
157
-
158
- this.#availableAssetNames = await this.#availableAssetNamesAtom.get()
159
- }
160
-
161
- #getAsset = (assetName) => this.#assets[assetName]
162
-
163
- #getAssets = () => this.#assets
164
-
165
- #flattenToBlockchainMetadataObjects = ({
166
- accountStateChanges = Object.create(null),
167
- txLogChanges = Object.create(null),
168
- }) => {
169
- const byBaseAssetName = mapValues(accountStateChanges, (accountStates) =>
170
- mapValues(accountStates, (accountState) => ({ accountState }))
171
- )
172
-
173
- Object.entries(txLogChanges).forEach(([walletAccount, txLogs]) => {
174
- Object.entries(txLogs).map(([assetName, txLog]) => {
175
- const { baseAsset } = this.#getAsset(assetName)
176
- set(byBaseAssetName, [walletAccount, baseAsset.name, 'txLogsByAssetName', assetName], txLog)
177
- })
178
- })
179
-
180
- return Object.entries(byBaseAssetName).flatMap(([walletAccount, baseAssets]) =>
181
- Object.entries(baseAssets).map(([baseAssetName, { accountState, txLogsByAssetName }]) => ({
182
- walletAccount,
183
- assetName: baseAssetName,
184
- ...(accountState && { accountState }),
185
- ...(txLogsByAssetName && { txLogsByAssetName }),
186
- }))
187
- )
188
- }
189
-
190
- #handleBlockchainMetadataUpdate = (opts) => {
191
- if (!this.#ready) {
192
- this.#logger.log('ignore update from blockchain-metadata event.', { isReady: this.#ready })
193
- return
194
- }
195
-
196
- return this.#updateAssetSource(opts)
197
- }
198
-
199
- #handleFeeDataUpdate = (opts) => {
200
- if (!this.#ready) {
201
- this.#logger.log('ignore update from fee-data event.', { isReady: this.#ready })
202
- return
203
- }
204
-
205
- return this.#updateAssetSource(opts)
206
- }
207
-
208
- #pushIntoSharedQueue = makeConcurrent((fn) => fn(), { concurrency: 1 })
209
-
210
- #reset = ({ walletAccounts = this.#walletAccounts } = {}) => {
211
- walletAccounts.forEach((walletAccount) => {
212
- this.#balances[walletAccount] = Object.create(null)
213
- })
214
- }
215
-
216
- #getBalancesForAssetSource = async ({ walletAccount, assetName }) => {
217
- const metadata = await this.#loadBlockchainMetadataForBalance({ walletAccount, assetName })
218
- return this.#getBalancesFromBlockchainMetadata({
219
- assetNames: this.#getRelatedAssetNames(assetName),
220
- ...metadata,
221
- })
222
- }
223
-
224
- // It adds both old and new balances to the response.
225
- // Ideally, selectors should start using the new ones (total, spendable, etc)
226
-
227
- #getBalancesFromBlockchainMetadata = ({
228
- assetNames,
229
- accountState,
230
- feeData,
231
- txLogsByAssetName,
232
- }) => {
233
- // in the future accountState may have all the balances and we won't need to rely on txLog
234
- // for now, accountState-based balance has both the baseAsset and token balances while txLog is per asset/token
235
- const getAssetBalances = (assetName) => {
236
- const asset = this.#getAsset(assetName)
237
- const txLog = txLogsByAssetName[assetName]
238
-
239
- const zero = asset.currency.ZERO
240
-
241
- const apiBalances = asset.api.getBalances({ asset, accountState, txLog, feeData })
242
- const balances = processAssetBalances({
243
- balanceFieldsConfig: this.#balanceFieldsConfig,
244
- balances: apiBalances || Object.create(null),
245
- zero,
246
- })
247
- return validateBalances({
248
- balances: pick(balances, this.#balanceFields),
249
- asset,
250
- zero,
251
- logger: this.#logger,
252
- emitter: this,
253
- })
254
- }
255
-
256
- const balances = assetNames.map(getAssetBalances)
257
- return zipNonNull(assetNames, balances)
258
- }
259
-
260
- #getBalancesForWalletAccount = async ({ walletAccount }) => {
261
- const assetNames = this.#getSupportedAssetNames()
262
- const results = await Promise.allSettled(
263
- assetNames.map((assetName) => this.#getBalancesForAssetSource({ assetName, walletAccount }))
264
- )
265
-
266
- const balances = results.map(({ value }) => value).filter(Boolean)
267
- return Object.assign(Object.create(null), ...balances)
268
- }
269
-
270
- #load = async ({ walletAccount }) => {
271
- this.#logger.log(`loading walletAccount ${walletAccount}`)
272
- this.#balances[walletAccount] = await this.#getBalancesForWalletAccount({ walletAccount })
273
- }
274
-
275
- #setWalletAccounts = async (walletAccounts) => {
276
- const added = difference(walletAccounts, this.#walletAccounts)
277
- const removed = difference(this.#walletAccounts, walletAccounts)
278
- if (added.length === 0 && removed.length === 0) return
279
-
280
- this.#walletAccounts = walletAccounts
281
- this.#ready = true
282
- if (removed.length > 0) {
283
- this.#logger.log('recomputing after removing walletAccounts', removed)
284
- removed.forEach((walletAccount) => {
285
- delete this.#balances[walletAccount]
286
- })
287
- }
288
-
289
- if (added.length > 0) {
290
- this.#logger.log('recomputing after adding walletAccounts', added)
291
- await this.#recompute({ walletAccounts: added })
292
- } else {
293
- this.#flush()
294
- }
295
- }
296
-
297
- #onAssetsChanged = async ({ added, removed }) => {
298
- if (removed.length > 0) {
299
- for (const walletAccount of this.#walletAccounts) {
300
- for (const assetName of removed) {
301
- delete this.#balances[walletAccount]?.[assetName]
302
- }
303
- }
304
- }
305
-
306
- const newSupportedAssets = added.filter(this.#isSupportedAsset)
307
- const missingAssets = newSupportedAssets.filter(
308
- (name) =>
309
- !this.#walletAccounts.every((walletAccount) => !!this.#balances?.[walletAccount]?.[name])
310
- )
311
-
312
- if (missingAssets.length === 0) {
313
- if (removed.length > 0) this.#flush()
314
- return
315
- }
316
-
317
- this.#logger.log('computing balances for newly available assets', missingAssets)
318
- await Promise.all(
319
- this.#walletAccounts.map(async (walletAccount) => {
320
- const balances = await Promise.all(
321
- missingAssets.map((assetName) =>
322
- this.#getBalancesForAssetSource({ assetName, walletAccount })
323
- )
324
- )
325
- if (this.#balances[walletAccount]) {
326
- Object.assign(this.#balances[walletAccount], ...balances)
327
- }
328
- })
329
- )
330
-
331
- this.#flush()
332
- }
333
-
334
- #isSupportedAsset = (assetName) => {
335
- if (!this.#availableAssetNames.includes(assetName)) return false
336
- const asset = this.#getAsset(assetName)
337
- return asset && !asset.isCombined
338
- }
339
-
340
- #getSupportedAssetNames = () => Object.keys(this.#getAssets()).filter(this.#isSupportedAsset)
341
-
342
- #getRelatedAssetNames = (assetName) => {
343
- const asset = this.#assets[assetName]
344
- if (asset.baseAsset.name === assetName) {
345
- return Object.keys(this.#assets).filter(
346
- (name) => this.#assets[name].baseAsset.name === assetName && this.#isSupportedAsset(name)
347
- )
348
- }
349
-
350
- return [assetName]
351
- }
352
-
353
- #loadBlockchainMetadataForBalance = async ({
354
- assetName,
355
- walletAccount,
356
- feeData: providedFeeData, // it's optional, undefined when balancesRequireFeeData is disabled
357
- accountState: providedAccountState = null,
358
- txLogsByAssetName: providedTxLogsByAssetName = {},
359
- }) => {
360
- const {
361
- value: { txs: txLogsByAssetSource, accountStates },
362
- } = await this.#blockchainMetadataAtom.get()
363
- let accountState = providedAccountState
364
-
365
- const baseAsset = this.#getAsset(assetName).baseAsset
366
-
367
- if (!accountState) {
368
- const cached = accountStates[walletAccount]?.[baseAsset.name]
369
- if (cached) {
370
- accountState = cached
371
- } else {
372
- const AccountState = baseAsset.api?.createAccountState?.()
373
- accountState = AccountState?.create()
374
- }
375
- }
376
-
377
- let feeData = providedFeeData
378
- if (!feeData && baseAsset.api?.features?.balancesRequireFeeData) {
379
- const fees = await this.#feeDataAtom.get()
380
- feeData = fees[baseAsset.name]
381
- }
382
-
383
- const assetNames = this.#getRelatedAssetNames(assetName)
384
- const txLogsByAssetName = zipObject(
385
- assetNames,
386
- assetNames.map(
387
- (assetName) =>
388
- providedTxLogsByAssetName[assetName] ||
389
- txLogsByAssetSource[walletAccount]?.[assetName] ||
390
- TxSet.EMPTY
391
- )
392
- )
393
-
394
- return {
395
- feeData,
396
- accountState,
397
- txLogsByAssetName,
398
- }
399
- }
400
-
401
- #updateAssetSource = async ({
402
- assetName,
403
- walletAccount,
404
- accountState: providedAccountState,
405
- txLogsByAssetName: providedTxLogsByAssetName,
406
- feeData: providedFeeData,
407
- }) => {
408
- if (!this.#isSupportedAsset(assetName)) return
409
-
410
- if (!this.#walletAccounts.includes(walletAccount)) {
411
- this.#logger.warn(`unexpected update for disabled walletAccount ${walletAccount}`)
412
- return
413
- }
414
-
415
- const { accountState, txLogsByAssetName, feeData } =
416
- await this.#loadBlockchainMetadataForBalance({
417
- assetName,
418
- walletAccount,
419
- feeData: providedFeeData,
420
- accountState: providedAccountState,
421
- txLogsByAssetName: providedTxLogsByAssetName,
422
- })
423
-
424
- const accountBalances = this.#balances[walletAccount]
425
- const newBalances = this.#getBalancesFromBlockchainMetadata({
426
- assetNames: this.#getRelatedAssetNames(assetName),
427
- accountState,
428
- txLogsByAssetName,
429
- feeData,
430
- })
431
-
432
- const changes = Object.create(null)
433
- for (const subAssetName in newBalances) {
434
- const { currency } = this.#getAsset(subAssetName)
435
- const newAssetBalances = newBalances?.[subAssetName]
436
- // e.g. totalBalance or spendableBalance
437
- for (const field of this.#balanceFields) {
438
- const oldBalance = accountBalances?.[subAssetName]?.[field]
439
- const newBalance = newAssetBalances?.[field]
440
- if (!newBalance) continue
441
- if ((oldBalance || currency.ZERO).equals(newBalance)) continue
442
-
443
- this.#logger.log(`updating assetSource ${walletAccount}:${assetName}`)
444
- set(changes, [walletAccount, subAssetName, field], {
445
- from: oldBalance ?? currency.ZERO,
446
- to: newBalance,
447
- })
448
-
449
- set(accountBalances, [subAssetName, field], newBalance)
450
- }
451
- }
452
-
453
- if (Object.keys(changes).length > 0) this.#flush({ changes })
454
- }
455
-
456
- #recompute = async ({ walletAccounts = this.#walletAccounts } = {}) => {
457
- this.#reset({ walletAccounts })
458
- await Promise.all(walletAccounts.map((walletAccount) => this.#load({ walletAccount })))
459
- this.#flush()
460
- }
461
-
462
- #flush = ({ changes } = {}) => {
463
- const balances = cloneBalances(this.#balances)
464
-
465
- if (Object.keys(balances).length === 0) return // wait till `load` finished
466
-
467
- this.#logger.log('balances updated')
468
- this.#balancesAtom.set({ balances, changes })
469
- }
470
-
471
- stop = () => {
472
- this.#subscriptions.forEach((unsubscribe) => unsubscribe())
473
- this.#subscriptions = []
474
- }
475
- }
476
-
477
- const createBalances = (opts) => new Balances(opts)
478
-
479
- const balancesDefinition = {
480
- id: 'balances',
481
- type: 'module',
482
- factory: createBalances,
483
- dependencies: [
484
- 'assetsAtom',
485
- 'availableAssetNamesAtom',
486
- 'enabledWalletAccountsAtom',
487
- 'config',
488
- 'logger',
489
- 'balancesAtom',
490
- 'blockchainMetadataAtom',
491
- 'feeDataAtom',
492
- 'errorTracking',
493
- ],
494
- public: true,
495
- }
496
-
497
- export default balancesDefinition
1
+ export { default } from '../lib/module/index.js'
2
+ export { cloneBalances } from '../lib/module/index.js'
@@ -0,0 +1 @@
1
+ export { default } from '../lib/module/mockable-balances.js'
@@ -1,27 +1 @@
1
- class Balances {
2
- #balancesAtom
3
- #configBalances
4
-
5
- constructor({ config, balancesAtom }) {
6
- this.#balancesAtom = balancesAtom
7
- this.#configBalances = config.importReport
8
- }
9
-
10
- load = async () => {
11
- this.#balancesAtom.set({ balances: this.#configBalances })
12
- }
13
-
14
- stop = () => {}
15
- }
16
-
17
- const createBalances = (opts) => new Balances(opts)
18
-
19
- const mockableBalancesDefinition = {
20
- id: 'balances',
21
- type: 'module',
22
- factory: createBalances,
23
- dependencies: ['config', 'balancesAtom'],
24
- public: true,
25
- }
26
-
27
- export default mockableBalancesDefinition
1
+ export { default } from '../lib/module/mockable-balances.js'
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "@exodus/balances",
3
- "version": "13.16.1",
3
+ "version": "13.17.0",
4
4
  "description": "Tracks crypto balances across enabled wallet accounts.",
5
5
  "author": "Exodus Movement, Inc.",
6
6
  "license": "MIT",
7
7
  "scripts": {
8
- "test": "pnpm exec exodus-test --jest",
9
- "typecheck": "pnpm exec tsc -p tsconfig.json --noEmit",
8
+ "test": "pnpm run build && pnpm exec exodus-test --jest",
9
+ "typecheck": "pnpm run build && pnpm exec tsc -p tsconfig.json --noEmit",
10
10
  "lint": "pnpm exec eslint -c ../../eslint.config.mjs",
11
11
  "lint:fix": "pnpm run lint --fix",
12
12
  "format": "pnpm exec prettier --write \"**/*.{json,md,yaml}\" --log-level warn",
13
- "format:check": "pnpm exec prettier --check \"**/*.{json,md,yaml}\" --log-level warn"
13
+ "format:check": "pnpm exec prettier --check \"**/*.{json,md,yaml}\" --log-level warn",
14
+ "build": "pnpm exec tsc -p tsconfig.build.json",
15
+ "prepublishOnly": "pnpm -w run build --scope @exodus/balances"
14
16
  },
15
17
  "type": "module",
16
18
  "main": "index.js",
@@ -20,13 +22,14 @@
20
22
  "atoms",
21
23
  "default-config.js",
22
24
  "index.d.ts",
23
- "tsconfig.json",
24
25
  "report",
25
26
  "plugins",
26
27
  "redux",
27
28
  "README.md",
28
29
  "CHANGELOG.md",
29
- "!**/__tests__/**"
30
+ "!**/__tests__/**",
31
+ "lib",
32
+ "default-config.d.ts"
30
33
  ],
31
34
  "repository": {
32
35
  "type": "git",
@@ -40,15 +43,17 @@
40
43
  "@exodus/asset-types": "^0.6.0",
41
44
  "@exodus/atoms": "^10.3.5",
42
45
  "@exodus/basic-utils": "^5.2.1",
43
- "@exodus/core-selectors": "^4.0.3",
44
- "@exodus/currency": "^6.4.1",
45
- "@exodus/errors": "^3.8.1",
46
- "@exodus/models": "^13.3.2",
47
- "@exodus/multi-account-redux": "^3.0.0",
46
+ "@exodus/core-selectors": "^4.0.4",
47
+ "@exodus/currency": "^6.4.2",
48
+ "@exodus/errors": "^3.8.2",
49
+ "@exodus/models": "^13.3.3",
50
+ "@exodus/multi-account-redux": "^3.0.2",
48
51
  "@exodus/redux-dependency-injection": "^5.0.0",
49
- "@exodus/safe-string": "^1.5.0",
50
- "@exodus/typeforce": "^1.19.4",
52
+ "@exodus/safe-string": "^1.5.1",
53
+ "@exodus/storage-interface": "^1.0.2",
54
+ "@exodus/typeforce": "^1.19.5",
51
55
  "@exodus/zod": "^3.24.4",
56
+ "@types/lodash": "^4.17.0",
52
57
  "lodash": "^4.17.21",
53
58
  "make-concurrent": "^4.0.0",
54
59
  "reselect": "^3.0.1"
@@ -59,10 +64,11 @@
59
64
  "devDependencies": {
60
65
  "@exodus/assets": "^11.0.0",
61
66
  "@exodus/assets-base": "^12.0.0",
62
- "@exodus/assets-feature": "^9.4.0",
63
- "@exodus/available-assets": "^8.11.2",
67
+ "@exodus/assets-feature": "^9.7.1",
68
+ "@exodus/available-assets": "^8.11.3",
64
69
  "@exodus/bitcoin-meta": "^2.0.0",
65
- "@exodus/blockchain-metadata": "^17.2.1",
70
+ "@exodus/blockchain-metadata": "^17.2.2",
71
+ "@exodus/dependency-types": "^2.1.1",
66
72
  "@exodus/ethereum-meta": "^2.0.0",
67
73
  "@exodus/fee-data-monitors": "^5.0.6",
68
74
  "@exodus/storage-memory": "^2.4.1",
@@ -74,5 +80,5 @@
74
80
  "access": "public",
75
81
  "provenance": false
76
82
  },
77
- "gitHead": "1f4cac80971bb7800acb1493c4b231098a9d39d8"
83
+ "gitHead": "8501081a08c1a353b35a1564bc28a1200087b647"
78
84
  }
@@ -0,0 +1 @@
1
+ export { default } from '../lib/plugins/analytics.js'
@@ -1,48 +1 @@
1
- const createBalancesAnalyticsPlugin = ({
2
- analytics,
3
- hasBalanceAtom,
4
- assetsWithBalanceCountAtom,
5
- assetsTotalWalletAmountsAtom,
6
- config: { assetsToTrackForBalances },
7
- }) => {
8
- let subscriptions = []
9
-
10
- const getAssetAmountFromNumberUnit = (unit) => {
11
- if (!unit) return 0
12
- return unit.toDefaultNumber()
13
- }
14
-
15
- const onStart = () => {
16
- analytics.requireDefaultEventProperties(['hasBalance', 'numberOfAssets'])
17
-
18
- const unsubscribeBalanceAtom = hasBalanceAtom.observe((hasBalance) =>
19
- analytics.setDefaultEventProperties({ hasBalance })
20
- )
21
-
22
- const unsubscribeBalanceCountAtom = assetsWithBalanceCountAtom.observe((numberOfAssets) =>
23
- analytics.setDefaultEventProperties({ numberOfAssets })
24
- )
25
-
26
- const unsubscribeWalletAmountsAtom = assetsTotalWalletAmountsAtom.observe(async (balances) => {
27
- const assetBalances = assetsToTrackForBalances.reduce((acc, obj) => {
28
- acc[obj.analyticsName] = getAssetAmountFromNumberUnit(balances.get(obj.assetName))
29
- return acc
30
- }, Object.create(null))
31
- analytics.setDefaultEventProperties(assetBalances)
32
- })
33
-
34
- subscriptions = [
35
- unsubscribeBalanceAtom,
36
- unsubscribeBalanceCountAtom,
37
- unsubscribeWalletAmountsAtom,
38
- ]
39
- }
40
-
41
- const onStop = () => {
42
- subscriptions.forEach((unsubscribe) => unsubscribe())
43
- }
44
-
45
- return { onStart, onStop }
46
- }
47
-
48
- export default createBalancesAnalyticsPlugin
1
+ export { default } from '../lib/plugins/analytics.js'
@@ -0,0 +1,4 @@
1
+ export {
2
+ balancesLifecyclePluginDefinition,
3
+ balancesAnalyticsPluginDefinition,
4
+ } from '../lib/plugins/index.js'