@exodus/balances 13.13.0 → 13.14.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,20 @@
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.14.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/balances@13.13.1...@exodus/balances@13.14.0) (2025-11-27)
7
+
8
+ ### Features
9
+
10
+ - feat: bump atoms for importers of observer factory (#14057)
11
+
12
+ - feat: introduce blockChainMetadataAtom (#14470)
13
+
14
+ - feat: update all consumers to use safe strings for error namespaces (#14429)
15
+
16
+ ## [13.13.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/balances@13.13.0...@exodus/balances@13.13.1) (2025-08-22)
17
+
18
+ **Note:** Version bump only for package @exodus/balances
19
+
6
20
  ## [13.13.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/balances@13.12.0...@exodus/balances@13.13.0) (2025-07-16)
7
21
 
8
22
  ### Features
@@ -1,4 +1,5 @@
1
1
  import { compute, createStorageAtomFactory } from '@exodus/atoms'
2
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
2
3
  import lodash from 'lodash'
3
4
 
4
5
  const { flatMap, map } = lodash
package/index.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  import typeforce from '@exodus/typeforce'
16
16
  import defaultConfig from './default-config.js'
17
17
  import mockableBalancesDefinition from './module/mockable-balances.js'
18
+ import { safeString } from '@exodus/safe-string'
18
19
 
19
20
  const balances = (walletConfig) => {
20
21
  const config = {
@@ -34,7 +35,7 @@ const balances = (walletConfig) => {
34
35
  )
35
36
 
36
37
  return {
37
- id: 'balances',
38
+ id: safeString`balances`,
38
39
  definitions: [
39
40
  {
40
41
  definition: walletConfig?.importReport ? mockableBalancesDefinition : balancesDefinition,
package/module/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { safeString } from '@exodus/safe-string'
2
2
  import { isNumberUnit } from '@exodus/currency'
3
3
  import { TxSet } from '@exodus/models'
4
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
4
5
  import lodash from 'lodash'
5
6
  import { difference as getChanges } from '@exodus/atoms'
6
- import { difference, flattenToPaths, pick, set } from '@exodus/basic-utils'
7
+ import { difference, pick, set, mapValues } from '@exodus/basic-utils'
7
8
  import makeConcurrent from 'make-concurrent'
8
9
  import { processAssetBalances, validateBalances } from './balances-utils.js'
9
10
  import defaultConfig from '../default-config.js'
@@ -41,8 +42,7 @@ class Balances {
41
42
 
42
43
  #balances = Object.create(null)
43
44
  #balancesAtom
44
- #accountStatesAtom
45
- #txLogsAtom
45
+ #blockchainMetadataAtom
46
46
  #feeDataAtom
47
47
  #enabledWalletAccountsAtom
48
48
  #loaded = false
@@ -55,8 +55,7 @@ class Balances {
55
55
  config,
56
56
  logger,
57
57
  balancesAtom,
58
- txLogsAtom,
59
- accountStatesAtom,
58
+ blockchainMetadataAtom,
60
59
  feeDataAtom,
61
60
  errorTracking,
62
61
  }) {
@@ -66,8 +65,7 @@ class Balances {
66
65
  this.#balanceFieldsConfig = config?.balanceFieldsConfig || defaultConfig.balanceFieldsConfig
67
66
  this.#balanceFields = config?.balanceFields || defaultConfig.balanceFields
68
67
  this.#balancesAtom = balancesAtom
69
- this.#accountStatesAtom = accountStatesAtom
70
- this.#txLogsAtom = txLogsAtom
68
+ this.#blockchainMetadataAtom = blockchainMetadataAtom
71
69
  this.#feeDataAtom = feeDataAtom
72
70
  this.#enabledWalletAccountsAtom = enabledWalletAccountsAtom
73
71
  this.#errorTracking = errorTracking
@@ -92,7 +90,7 @@ class Balances {
92
90
 
93
91
  // these share a queue
94
92
  this.#updateAssetSource = shareQueue(this.#updateAssetSource)
95
- this.#handleTxLogUpdate = shareQueue(this.#handleTxLogUpdate)
93
+ this.#handleBlockchainMetadataUpdate = shareQueue(this.#handleBlockchainMetadataUpdate)
96
94
  this.#handleFeeDataUpdate = shareQueue(this.#handleFeeDataUpdate)
97
95
  this.#recompute = shareQueue(this.#recompute)
98
96
  this.#onAssetsChanged = shareQueue(this.#onAssetsChanged)
@@ -111,23 +109,25 @@ class Balances {
111
109
  this.#enabledWalletAccountsAtom.observe((payload) => {
112
110
  this.#setWalletAccounts(Object.keys(payload))
113
111
  }),
114
- this.#accountStatesAtom.observe(({ changes }) =>
115
- flattenToPaths(changes).forEach(([walletAccount, assetName, accountState]) =>
116
- this.#handleAccountStateUpdate({ walletAccount, assetName, accountState })
117
- )
118
- ),
119
- this.#txLogsAtom.observe(({ changes }) =>
120
- flattenToPaths(changes).forEach(([walletAccount, assetName, txLog]) =>
121
- this.#handleTxLogUpdate({ walletAccount, assetName, txLog })
112
+ this.#blockchainMetadataAtom.observe(({ accountStateChanges, txLogChanges }) => {
113
+ this.#flattenToBlockchainMetadataObjects({ accountStateChanges, txLogChanges }).forEach(
114
+ ({ walletAccount, assetName, accountState, txLogsByAssetName }) =>
115
+ this.#handleBlockchainMetadataUpdate({
116
+ walletAccount,
117
+ assetName,
118
+ accountState,
119
+ txLogsByAssetName,
120
+ })
122
121
  )
123
- ),
122
+ }),
124
123
  this.#feeDataAtom.observe((changes) => {
125
124
  Object.entries(changes).forEach(([assetName, feeData]) => {
126
125
  const asset = this.#getAsset(assetName).baseAsset
127
- if (asset.api?.features?.balancesRequireFeeData)
126
+ if (asset.api?.features?.balancesRequireFeeData) {
128
127
  this.#walletAccounts.forEach((walletAccount) => {
129
128
  return this.#handleFeeDataUpdate({ walletAccount, assetName, feeData })
130
129
  })
130
+ }
131
131
  })
132
132
  }),
133
133
 
@@ -145,18 +145,34 @@ class Balances {
145
145
 
146
146
  #getAssets = () => this.#assetsModule.getAssets()
147
147
 
148
- #handleAccountStateUpdate = async (opts) => {
149
- if (!this.#ready) {
150
- this.#logger.log('ignore update from account-state event.', { isReady: this.#ready })
151
- return
152
- }
148
+ #flattenToBlockchainMetadataObjects = ({
149
+ accountStateChanges = Object.create(null),
150
+ txLogChanges = Object.create(null),
151
+ }) => {
152
+ const byBaseAssetName = mapValues(accountStateChanges, (accountStates) =>
153
+ mapValues(accountStates, (accountState) => ({ accountState }))
154
+ )
153
155
 
154
- return this.#updateAssetSource(opts)
156
+ Object.entries(txLogChanges).forEach(([walletAccount, txLogs]) => {
157
+ Object.entries(txLogs).map(([assetName, txLog]) => {
158
+ const { baseAsset } = this.#getAsset(assetName)
159
+ set(byBaseAssetName, [walletAccount, baseAsset.name, 'txLogsByAssetName', assetName], txLog)
160
+ })
161
+ })
162
+
163
+ return Object.entries(byBaseAssetName).flatMap(([walletAccount, baseAssets]) =>
164
+ Object.entries(baseAssets).map(([baseAssetName, { accountState, txLogsByAssetName }]) => ({
165
+ walletAccount,
166
+ assetName: baseAssetName,
167
+ ...(accountState && { accountState }),
168
+ ...(txLogsByAssetName && { txLogsByAssetName }),
169
+ }))
170
+ )
155
171
  }
156
172
 
157
- #handleTxLogUpdate = (opts) => {
173
+ #handleBlockchainMetadataUpdate = (opts) => {
158
174
  if (!this.#ready) {
159
- this.#logger.log('ignore update from tx-log event.', { isReady: this.#ready })
175
+ this.#logger.log('ignore update from blockchain-metadata event.', { isReady: this.#ready })
160
176
  return
161
177
  }
162
178
 
@@ -297,15 +313,15 @@ class Balances {
297
313
  accountState: providedAccountState = null,
298
314
  txLogsByAssetName: providedTxLogsByAssetName = {},
299
315
  }) => {
300
- const { value: txLogsByAssetSource } = await this.#txLogsAtom.get()
316
+ const {
317
+ value: { txs: txLogsByAssetSource, accountStates },
318
+ } = await this.#blockchainMetadataAtom.get()
301
319
  let accountState = providedAccountState
302
320
 
303
321
  const baseAsset = this.#getAsset(assetName).baseAsset
304
322
 
305
323
  if (!accountState) {
306
- const assetNameToUse = baseAsset.name
307
- const { value } = await this.#accountStatesAtom.get()
308
- const cached = value[walletAccount]?.[assetNameToUse]
324
+ const cached = accountStates[walletAccount]?.[baseAsset.name]
309
325
  if (cached) {
310
326
  accountState = cached
311
327
  } else {
@@ -342,7 +358,7 @@ class Balances {
342
358
  assetName,
343
359
  walletAccount,
344
360
  accountState: providedAccountState,
345
- txLog: providedTxLog,
361
+ txLogsByAssetName: providedTxLogsByAssetName,
346
362
  feeData: providedFeeData,
347
363
  }) => {
348
364
  if (!this.#isSupportedAsset(assetName)) return
@@ -358,9 +374,7 @@ class Balances {
358
374
  walletAccount,
359
375
  feeData: providedFeeData,
360
376
  accountState: providedAccountState,
361
- txLogsByAssetName: {
362
- [assetName]: providedTxLog,
363
- },
377
+ txLogsByAssetName: providedTxLogsByAssetName,
364
378
  })
365
379
 
366
380
  const accountBalances = this.#balances[walletAccount]
@@ -429,8 +443,7 @@ const balancesDefinition = {
429
443
  'config',
430
444
  'logger',
431
445
  'balancesAtom',
432
- 'txLogsAtom',
433
- 'accountStatesAtom',
446
+ 'blockchainMetadataAtom',
434
447
  'feeDataAtom',
435
448
  'errorTracking',
436
449
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/balances",
3
- "version": "13.13.0",
3
+ "version": "13.14.0",
4
4
  "description": "Tracks crypto balances across enabled wallet accounts.",
5
5
  "author": "Exodus Movement, Inc.",
6
6
  "license": "MIT",
@@ -33,9 +33,9 @@
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": "^9.0.0",
36
+ "@exodus/atoms": "^10.0.0",
37
37
  "@exodus/basic-utils": "^3.0.1",
38
- "@exodus/core-selectors": "^3.0.0",
38
+ "@exodus/core-selectors": "^4.0.0",
39
39
  "@exodus/currency": "^5.0.2",
40
40
  "@exodus/errors": "^3.0.0",
41
41
  "@exodus/models": "^12.0.1",
@@ -49,24 +49,25 @@
49
49
  "reselect": "^3.0.1"
50
50
  },
51
51
  "peerDependencies": {
52
- "@exodus/blockchain-metadata": "^15.4.0"
52
+ "@exodus/blockchain-metadata": "^17.0.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@exodus/assets": "^11.0.0",
56
56
  "@exodus/assets-base": "^10.0.0",
57
- "@exodus/assets-feature": "^8.1.3",
58
- "@exodus/available-assets": "^8.7.0",
57
+ "@exodus/assets-feature": "^8.7.1",
58
+ "@exodus/available-assets": "^8.8.0",
59
59
  "@exodus/bitcoin-meta": "^2.0.0",
60
- "@exodus/blockchain-metadata": "^15.12.0",
60
+ "@exodus/blockchain-metadata": "^17.1.0",
61
61
  "@exodus/ethereum-meta": "^2.0.0",
62
62
  "@exodus/fee-data-monitors": "^5.0.4",
63
- "@exodus/storage-memory": "^2.2.2",
64
- "@exodus/wallet-accounts": "^17.6.1",
63
+ "@exodus/storage-memory": "^2.3.0",
64
+ "@exodus/wallet-accounts": "^20.2.1",
65
65
  "events": "^3.3.0",
66
66
  "redux": "^4.2.1"
67
67
  },
68
68
  "publishConfig": {
69
- "access": "public"
69
+ "access": "public",
70
+ "provenance": false
70
71
  },
71
- "gitHead": "f2359d974180d0af6e116f209da34dbee4ef8cff"
72
+ "gitHead": "1535d3792c06b4c96e94e0787f75d40fbb5cca1f"
72
73
  }
package/redux/index.js CHANGED
@@ -2,6 +2,7 @@ import id from './id.js'
2
2
  import initialState from './initial-state.js'
3
3
  import selectorDefinitions from './selectors/index.js'
4
4
  import { mapValues } from '@exodus/basic-utils'
5
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
5
6
  import lodash from 'lodash'
6
7
  import helper from './multi-account-helper.js'
7
8
  import { setAccounts } from '@exodus/multi-account-redux/src/common.js'
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
1
2
  import lodash from 'lodash'
2
3
  import { MY_STATE } from '@exodus/redux-dependency-injection'
3
4
  import { createSelector } from 'reselect'
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
1
2
  import lodash from 'lodash'
2
3
  import { createSelector } from 'reselect'
3
4
  import { MY_STATE } from '@exodus/redux-dependency-injection'
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
1
2
  import lodash from 'lodash'
2
3
  import { createSelector } from 'reselect'
3
4
  import { isNumberUnit } from '@exodus/currency'
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
1
2
  import lodash from 'lodash'
2
3
  import { createSelector } from 'reselect'
3
4
  import { getUnconfirmedBalance } from './get-unconfirmed-balance.js'
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
1
2
  import lodash from 'lodash'
2
3
  import defaultConfig from '../../default-config.js'
3
4
 
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
1
2
  import lodash from 'lodash'
2
3
 
3
4
  const { memoize } = lodash // eslint-disable-line @exodus/basic-utils/prefer-basic-utils
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
1
2
  import lodash from 'lodash'
2
3
  import { createSelector } from 'reselect'
3
4
  import { MY_STATE } from '@exodus/redux-dependency-injection'
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line no-restricted-imports -- TODO: Fix this the next time the file is edited.
1
2
  import lodash from 'lodash'
2
3
  import { createSelector } from 'reselect'
3
4