@exodus/balances 13.3.2 → 13.5.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,26 @@
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
+ ## [13.5.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/balances@13.4.0...@exodus/balances@13.5.0) (2024-11-29)
7
+
8
+ ### Features
9
+
10
+ - feat: add various crypto balance props (#10419)
11
+
12
+ ## [13.4.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/balances@13.3.2...@exodus/balances@13.4.0) (2024-11-25)
13
+
14
+ ### Features
15
+
16
+ - feat: use atoms v9 (#9651)
17
+
18
+ ### Bug Fixes
19
+
20
+ - fix: balances tests (#9738)
21
+
22
+ ### License
23
+
24
+ - license: re-license under MIT license (#10580)
25
+
6
26
  ## [13.3.2](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/balances@13.3.1...@exodus/balances@13.3.2) (2024-09-13)
7
27
 
8
28
  ### Bug Fixes
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2024 Exodus Movement, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -10,7 +10,7 @@ yarn add @exodus/balances
10
10
 
11
11
  ## Usage
12
12
 
13
- This feature is designed to be used together with `@exodus/headless`. See [using the sdk](../../docs/using-the-sdk.md).
13
+ This feature is designed to be used together with `@exodus/headless`. See [using the sdk](../../docs/docs-website/docs/development/using-the-sdk.md).
14
14
 
15
15
  ### Play with it
16
16
 
@@ -36,7 +36,7 @@ selectors.balances
36
36
 
37
37
  ### API Side
38
38
 
39
- See [using the sdk](../../docs/using-the-sdk.md#setup-the-api-side) for more details on how features plug into the SDK. Note that the balances feature currently doesn't provide a top level API and is meant to be used purely through selectors.
39
+ See [using the sdk](../../docs/docs-website/docs/development/using-the-sdk.md#setup-the-api-side) for more details on how features plug into the SDK. Note that the balances feature currently doesn't provide a top level API and is meant to be used purely through selectors.
40
40
 
41
41
  If you're building a feature that requires balances, add a dependency on the `balancesAtom` as below. See also the [available balance fields](./default-config.js).
42
42
 
@@ -68,7 +68,7 @@ const myModuleDefinition = {
68
68
 
69
69
  ### UI Side
70
70
 
71
- See [using the sdk](../../docs/using-the-sdk.md#events) for more details on basic UI-side setup.
71
+ See [using the sdk](../../docs/docs-website/docs/development/using-the-sdk.md#events) for more details on basic UI-side setup.
72
72
 
73
73
  ```js
74
74
  import { selectors } from '~/ui/flux'
@@ -87,4 +87,4 @@ const MyComponent = () => {
87
87
 
88
88
  ### WARNING: no asset specifics!
89
89
 
90
- If you introduce any asset specifics like `if (assetName === 'dogicorn') balance = balance.mul(2)`, you will be fired immediately. This is a safety measure to save you from @feri42's wrath. All asset-specifics belong in `asset.api.getBalances` or other APIs inside the asset libraries.
90
+ Do NOT introduce any asset specifics like `if (assetName === 'dogicorn') balance = balance.mul(2)`. All asset-specifics belong in `asset.api.getBalances` or other APIs inside the asset libraries.
@@ -0,0 +1,46 @@
1
+ import { compute, dedupe } from '@exodus/atoms'
2
+
3
+ const mapsAreEqual = (mapA, mapB) => {
4
+ if (mapA.size !== mapB.size) {
5
+ return false
6
+ }
7
+
8
+ for (const item of mapA) {
9
+ if (!mapB.has(item)) {
10
+ return false
11
+ }
12
+ }
13
+
14
+ return true
15
+ }
16
+
17
+ const createAssetTotalWalletAmountsAtom = ({ balancesAtom }) => {
18
+ let previousValue = new Map()
19
+ const selector = ({ balances }) => {
20
+ const assetsWithBalances = new Map()
21
+
22
+ Object.values(balances).forEach((accountBalances) => {
23
+ Object.keys(accountBalances).forEach((assetName) => {
24
+ if (assetsWithBalances.has(assetName)) {
25
+ const currentAssetBalance = assetsWithBalances.get(assetName)
26
+ assetsWithBalances.set(
27
+ assetName,
28
+ accountBalances[assetName]?.balance.add(currentAssetBalance)
29
+ )
30
+ } else {
31
+ assetsWithBalances.set(assetName, accountBalances[assetName]?.balance)
32
+ }
33
+ })
34
+ })
35
+
36
+ if (!mapsAreEqual(assetsWithBalances, previousValue)) {
37
+ previousValue = assetsWithBalances
38
+ }
39
+
40
+ return previousValue
41
+ }
42
+
43
+ return dedupe(compute({ atom: balancesAtom, selector }))
44
+ }
45
+
46
+ export default createAssetTotalWalletAmountsAtom
package/atoms/index.js CHANGED
@@ -3,6 +3,7 @@ import createAssetsWithBalanceCountAtom from './assets-with-balance-count.js'
3
3
  import createBalancesAtom from './balances.js'
4
4
  import createFundedWalletAccountsAtom from './funded-wallet-accounts.js'
5
5
  import createHasBalanceAtom from './has-balance.js'
6
+ import createAssetsTotalWalletAmountsAtom from './asset-total-wallet-amounts.js'
6
7
 
7
8
  export const balancesAtomDefinition = {
8
9
  id: 'balancesAtom',
@@ -43,3 +44,11 @@ export const assetsWithBalanceCountAtomDefinition = {
43
44
  dependencies: ['assetNamesWithBalanceAtom'],
44
45
  public: true,
45
46
  }
47
+
48
+ export const assetsTotalWalletAmountsDefinition = {
49
+ id: 'assetsTotalWalletAmountsAtom',
50
+ type: 'atom',
51
+ factory: createAssetsTotalWalletAmountsAtom,
52
+ dependencies: ['balancesAtom'],
53
+ public: true,
54
+ }
package/default-config.js CHANGED
@@ -19,7 +19,8 @@ const config = {
19
19
  balanceFieldsConfig,
20
20
  balanceFields: balanceFieldsConfig
21
21
  .flatMap((fieldConfig) => [fieldConfig.name, fieldConfig.legacyName])
22
- .filter(Boolean), // wallets can reduce the number of exposed fields
22
+ .filter(Boolean), // wallets can reduce the number of exposed fields,
23
+ assetsToTrackForBalances: [],
23
24
  }
24
25
 
25
26
  export default config
package/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  fundedWalletAccountsAtomDefinition,
6
6
  assetsWithBalanceCountAtomDefinition,
7
7
  assetNamesWithBalanceAtomDefinition,
8
+ assetsTotalWalletAmountsDefinition,
8
9
  } from './atoms/index.js'
9
10
  import balancesReportDefinition from './report/index.js'
10
11
  import {
@@ -18,11 +19,14 @@ const balances = (walletConfig) => {
18
19
  const config = {
19
20
  ...defaultConfig,
20
21
  balanceFields: walletConfig?.balanceFields || defaultConfig.balanceFields,
22
+ assetsToTrackForBalances:
23
+ walletConfig?.assetsToTrackForBalances || defaultConfig.assetsToTrackForBalances,
21
24
  }
22
25
  typeforce(
23
26
  {
24
27
  balanceFields: typeforce.arrayOf('String'),
25
28
  balanceFieldsConfig: typeforce.arrayOf('Object'),
29
+ assetsToTrackForBalances: typeforce.arrayOf('Object'),
26
30
  },
27
31
  config,
28
32
  true
@@ -46,9 +50,11 @@ const balances = (walletConfig) => {
46
50
  { definition: fundedWalletAccountsAtomDefinition },
47
51
  { definition: balancesReportDefinition },
48
52
  { definition: balancesLifecyclePluginDefinition },
53
+ { definition: assetsTotalWalletAmountsDefinition },
49
54
  {
50
55
  if: { registered: ['analytics'] },
51
56
  definition: balancesAnalyticsPluginDefinition,
57
+ config: { assetsToTrackForBalances: config.assetsToTrackForBalances },
52
58
  },
53
59
  ],
54
60
  }
package/module/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { isNumberUnit } from '@exodus/currency'
2
2
  import { TxSet } from '@exodus/models'
3
- import ExodusModule from '@exodus/module' // eslint-disable-line import/no-deprecated
4
3
  import lodash from 'lodash'
5
4
  import { difference, flattenToPaths, pick, set } from '@exodus/basic-utils'
6
5
  import makeConcurrent from 'make-concurrent'
@@ -20,9 +19,8 @@ const zipNonNull = (keys, values) =>
20
19
  values.filter((value) => value != null)
21
20
  )
22
21
 
23
- export const MODULE_ID = 'balances'
24
-
25
- class Balances extends ExodusModule {
22
+ class Balances {
23
+ #logger
26
24
  #assetsModule
27
25
  #assetsAtom
28
26
  #balanceFieldsConfig
@@ -48,8 +46,7 @@ class Balances extends ExodusModule {
48
46
  txLogsAtom,
49
47
  accountStatesAtom,
50
48
  }) {
51
- super({ name: MODULE_ID, logger })
52
-
49
+ this.#logger = logger
53
50
  this.#assetsModule = assetsModule
54
51
  this.#assetsAtom = assetsAtom
55
52
  this.#balanceFieldsConfig = config?.balanceFieldsConfig || defaultConfig.balanceFieldsConfig
@@ -67,16 +64,13 @@ class Balances extends ExodusModule {
67
64
  this.#pushIntoSharedQueue(() => fn.apply(this, args))
68
65
  }
69
66
 
70
- // sanitize args in case someone passes something sensitive, e.g. getBalance({ walletAccount, mnemonic })
71
- // when only `{ walletAccount }` is expected
72
- const reportAndSwallowErrors =
73
- ({ fn, name, sanitizeArgs = () => {} }) =>
67
+ const swallowErrors =
68
+ (fn) =>
74
69
  async (...args) => {
75
70
  try {
76
71
  return await fn(...args)
77
72
  } catch (error) {
78
- this.emit('error', { fn: name, error, args: sanitizeArgs(...args) })
79
- this._logger.error(error)
73
+ this.#logger.error(error)
80
74
  }
81
75
  }
82
76
 
@@ -85,17 +79,8 @@ class Balances extends ExodusModule {
85
79
  this.#handleTxLogUpdate = shareQueue(this.#handleTxLogUpdate)
86
80
  this.#recompute = shareQueue(this.#recompute)
87
81
  this.#onAssetsChanged = shareQueue(this.#onAssetsChanged)
88
- this.#getBalancesForAssetSource = reportAndSwallowErrors({
89
- fn: this.#getBalancesForAssetSource,
90
- name: 'getBalancesForAssetSource',
91
- sanitizeArgs: ({ walletAccount, assetName }) => ({ walletAccount, assetName }),
92
- })
93
-
94
- this.#setWalletAccounts = reportAndSwallowErrors({
95
- fn: this.#setWalletAccounts,
96
- name: 'setWalletAccounts',
97
- sanitizeArgs: (walletAccounts) => walletAccounts,
98
- })
82
+ this.#getBalancesForAssetSource = swallowErrors(this.#getBalancesForAssetSource)
83
+ this.#setWalletAccounts = swallowErrors(this.#setWalletAccounts)
99
84
  }
100
85
 
101
86
  load = () => {
@@ -130,9 +115,7 @@ class Balances extends ExodusModule {
130
115
 
131
116
  #handleAccountStateUpdate = async (opts) => {
132
117
  if (!this.#ready) {
133
- this._logger.log('ignore update from account-state event.', {
134
- isReady: this.#ready,
135
- })
118
+ this.#logger.log('ignore update from account-state event.', { isReady: this.#ready })
136
119
  return
137
120
  }
138
121
 
@@ -141,9 +124,7 @@ class Balances extends ExodusModule {
141
124
 
142
125
  #handleTxLogUpdate = (opts) => {
143
126
  if (!this.#ready) {
144
- this._logger.log('ignore update from tx-log event.', {
145
- isReady: this.#ready,
146
- })
127
+ this.#logger.log('ignore update from tx-log event.', { isReady: this.#ready })
147
128
  return
148
129
  }
149
130
 
@@ -188,7 +169,7 @@ class Balances extends ExodusModule {
188
169
  balances: pick(balances, this.#balanceFields),
189
170
  asset,
190
171
  zero,
191
- logger: this._logger,
172
+ logger: this.#logger,
192
173
  emitter: this,
193
174
  })
194
175
  }
@@ -208,7 +189,7 @@ class Balances extends ExodusModule {
208
189
  }
209
190
 
210
191
  #load = async ({ walletAccount }) => {
211
- this._logger.log(`loading walletAccount ${walletAccount}`)
192
+ this.#logger.log(`loading walletAccount ${walletAccount}`)
212
193
  this.#balances[walletAccount] = await this.#getBalancesForWalletAccount({ walletAccount })
213
194
  }
214
195
 
@@ -220,14 +201,14 @@ class Balances extends ExodusModule {
220
201
  this.#walletAccounts = walletAccounts
221
202
  this.#ready = true
222
203
  if (removed.length > 0) {
223
- this._logger.log('recomputing after removing walletAccounts', removed)
204
+ this.#logger.log('recomputing after removing walletAccounts', removed)
224
205
  removed.forEach((walletAccount) => {
225
206
  delete this.#balances[walletAccount]
226
207
  })
227
208
  }
228
209
 
229
210
  if (added.length > 0) {
230
- this._logger.log('recomputing after adding walletAccounts', added)
211
+ this.#logger.log('recomputing after adding walletAccounts', added)
231
212
  await this.#recompute({ walletAccounts: added })
232
213
  } else {
233
214
  this.#flush()
@@ -243,7 +224,7 @@ class Balances extends ExodusModule {
243
224
  return
244
225
  }
245
226
 
246
- this._logger.log('recomputing based on new assets list')
227
+ this.#logger.log('recomputing based on new assets list')
247
228
  this.#recompute()
248
229
  }
249
230
 
@@ -308,7 +289,7 @@ class Balances extends ExodusModule {
308
289
  if (!this.#isSupportedAsset(assetName)) return
309
290
 
310
291
  if (!this.#walletAccounts.includes(walletAccount)) {
311
- this._logger.warn(`unexpected update for disabled walletAccount ${walletAccount}`)
292
+ this.#logger.warn(`unexpected update for disabled walletAccount ${walletAccount}`)
312
293
  return
313
294
  }
314
295
 
@@ -339,7 +320,7 @@ class Balances extends ExodusModule {
339
320
  if (!newBalance) continue
340
321
  if ((oldBalance || currency.ZERO).equals(newBalance)) continue
341
322
 
342
- this._logger.log(`updating assetSource ${walletAccount}:${assetName}`)
323
+ this.#logger.log(`updating assetSource ${walletAccount}:${assetName}`)
343
324
  set(changes, [walletAccount, subAssetName, field], {
344
325
  from: oldBalance ?? currency.ZERO,
345
326
  to: newBalance,
@@ -363,7 +344,7 @@ class Balances extends ExodusModule {
363
344
 
364
345
  if (Object.keys(balances).length === 0) return // wait till `load` finished
365
346
 
366
- this._logger.log('balances updated')
347
+ this.#logger.log('balances updated')
367
348
  this.#balancesAtom.set({ balances, changes })
368
349
  }
369
350
 
@@ -376,7 +357,7 @@ class Balances extends ExodusModule {
376
357
  const createBalances = (opts) => new Balances(opts)
377
358
 
378
359
  const balancesDefinition = {
379
- id: MODULE_ID,
360
+ id: 'balances',
380
361
  type: 'module',
381
362
  factory: createBalances,
382
363
  dependencies: [
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@exodus/balances",
3
- "version": "13.3.2",
3
+ "version": "13.5.0",
4
4
  "description": "Tracks crypto balances across enabled wallet accounts.",
5
5
  "author": "Exodus Movement, Inc.",
6
- "license": "UNLICENSED",
6
+ "license": "MIT",
7
7
  "scripts": {
8
8
  "test": "run -T exodus-test --jest",
9
9
  "lint": "run -T eslint -c ../../eslint.config.mjs",
@@ -33,12 +33,11 @@
33
33
  "url": "https://github.com/ExodusMovement/exodus-hydra/issues?q=is%3Aissue+is%3Aopen+label%3Abalances"
34
34
  },
35
35
  "dependencies": {
36
- "@exodus/atoms": "^8.1.1",
36
+ "@exodus/atoms": "^9.0.0",
37
37
  "@exodus/basic-utils": "^3.0.1",
38
38
  "@exodus/core-selectors": "^3.0.0",
39
39
  "@exodus/currency": "^5.0.2",
40
40
  "@exodus/models": "^12.0.1",
41
- "@exodus/module": "^1.3.0",
42
41
  "@exodus/multi-account-redux": "^2.0.1",
43
42
  "@exodus/redux-dependency-injection": "^4.0.3",
44
43
  "@exodus/typeforce": "^1.18.0",
@@ -52,17 +51,19 @@
52
51
  "devDependencies": {
53
52
  "@exodus/assets": "^11.0.0",
54
53
  "@exodus/assets-base": "^10.0.0",
55
- "@exodus/assets-feature": "^5.11.3",
56
- "@exodus/available-assets": "^8.5.0",
54
+ "@exodus/assets-feature": "^5.14.1",
55
+ "@exodus/available-assets": "^8.7.0",
57
56
  "@exodus/bitcoin-meta": "^2.0.0",
58
- "@exodus/blockchain-metadata": "^15.5.0",
57
+ "@exodus/blockchain-metadata": "^15.7.0",
59
58
  "@exodus/ethereum-meta": "^2.0.0",
60
- "@exodus/fee-data-monitors": "^4.3.0",
61
- "@exodus/redux-dependency-injection": "workspace:^",
62
- "@exodus/storage-memory": "^2.2.0",
63
- "@exodus/wallet-accounts": "^16.10.2",
59
+ "@exodus/fee-data-monitors": "^5.0.2",
60
+ "@exodus/storage-memory": "^2.2.2",
61
+ "@exodus/wallet-accounts": "^17.1.1",
64
62
  "events": "^3.3.0",
65
63
  "redux": "^4.2.1"
66
64
  },
67
- "gitHead": "de12d93c6e9cea90a07137a932dccd255226d92f"
65
+ "publishConfig": {
66
+ "access": "public"
67
+ },
68
+ "gitHead": "d979dacecad62ad66a5761afc08d6ea362ca05a5"
68
69
  }
@@ -2,9 +2,16 @@ const createBalancesAnalyticsPlugin = ({
2
2
  analytics,
3
3
  hasBalanceAtom,
4
4
  assetsWithBalanceCountAtom,
5
+ assetsTotalWalletAmountsAtom,
6
+ config: { assetsToTrackForBalances },
5
7
  }) => {
6
8
  let subscriptions = []
7
9
 
10
+ const getAssetAmountFromNumberUnit = (unit) => {
11
+ if (!unit) return 0
12
+ return unit.toDefaultNumber()
13
+ }
14
+
8
15
  const onStart = () => {
9
16
  analytics.requireDefaultEventProperties(['hasBalance', 'numberOfAssets'])
10
17
 
@@ -16,7 +23,19 @@ const createBalancesAnalyticsPlugin = ({
16
23
  analytics.setDefaultEventProperties({ numberOfAssets })
17
24
  )
18
25
 
19
- subscriptions = [unsubscribeBalanceAtom, unsubscribeBalanceCountAtom]
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
+ }, {})
31
+ analytics.setDefaultEventProperties(assetBalances)
32
+ })
33
+
34
+ subscriptions = [
35
+ unsubscribeBalanceAtom,
36
+ unsubscribeBalanceCountAtom,
37
+ unsubscribeWalletAmountsAtom,
38
+ ]
20
39
  }
21
40
 
22
41
  const onStop = () => {
package/plugins/index.js CHANGED
@@ -13,6 +13,12 @@ export const balancesAnalyticsPluginDefinition = {
13
13
  id: 'balancesAnalyticsPlugin',
14
14
  type: 'plugin',
15
15
  factory: createBalancesAnalyticsPlugin,
16
- dependencies: ['analytics', 'hasBalanceAtom', 'assetsWithBalanceCountAtom'],
16
+ dependencies: [
17
+ 'analytics',
18
+ 'hasBalanceAtom',
19
+ 'assetsWithBalanceCountAtom',
20
+ 'assetsTotalWalletAmountsAtom',
21
+ 'config?',
22
+ ],
17
23
  public: true,
18
24
  }