@exodus/balances 13.9.0 → 13.10.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,22 @@
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.10.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/balances@13.9.1...@exodus/balances@13.10.0) (2025-07-04)
7
+
8
+ ### Features
9
+
10
+ - feat: provide feeData to asset.api.getBalances (#13104)
11
+
12
+ ### Bug Fixes
13
+
14
+ - fix: provide feeData as undefined to getBalances when disabled (#13113)
15
+
16
+ ## [13.9.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/balances@13.9.0...@exodus/balances@13.9.1) (2025-06-11)
17
+
18
+ ### Performance
19
+
20
+ - perf: clone balances (#12882)
21
+
6
22
  ## [13.9.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/balances@13.8.2...@exodus/balances@13.9.0) (2025-06-03)
7
23
 
8
24
  ### Features
package/module/index.js CHANGED
@@ -7,12 +7,20 @@ import makeConcurrent from 'make-concurrent'
7
7
  import { processAssetBalances, validateBalances } from './balances-utils.js'
8
8
  import defaultConfig from '../default-config.js'
9
9
 
10
- const { cloneDeepWith, isEmpty, merge, zipObject } = lodash
10
+ const { isEmpty, merge, zipObject } = lodash
11
11
 
12
- const cloneBalances = (balances) =>
13
- cloneDeepWith(balances, (val) => {
14
- if (isNumberUnit(val)) return val
15
- })
12
+ export const cloneBalances = (balances) => {
13
+ if (isNumberUnit(balances)) {
14
+ return balances
15
+ }
16
+
17
+ const cloned = Object.create(Object.getPrototypeOf(balances))
18
+ for (const key in balances) {
19
+ cloned[key] = cloneBalances(balances[key])
20
+ }
21
+
22
+ return cloned
23
+ }
16
24
 
17
25
  const zipNonNull = (keys, values) =>
18
26
  zipObject(
@@ -34,6 +42,7 @@ class Balances {
34
42
  #balancesAtom
35
43
  #accountStatesAtom
36
44
  #txLogsAtom
45
+ #feeDataAtom
37
46
  #enabledWalletAccountsAtom
38
47
  #loaded = false
39
48
  #subscriptions = []
@@ -47,6 +56,7 @@ class Balances {
47
56
  balancesAtom,
48
57
  txLogsAtom,
49
58
  accountStatesAtom,
59
+ feeDataAtom,
50
60
  }) {
51
61
  this.#logger = logger
52
62
  this.#assetsModule = assetsModule
@@ -56,6 +66,7 @@ class Balances {
56
66
  this.#balancesAtom = balancesAtom
57
67
  this.#accountStatesAtom = accountStatesAtom
58
68
  this.#txLogsAtom = txLogsAtom
69
+ this.#feeDataAtom = feeDataAtom
59
70
  this.#enabledWalletAccountsAtom = enabledWalletAccountsAtom
60
71
 
61
72
  const shareQueue =
@@ -79,6 +90,7 @@ class Balances {
79
90
  // these share a queue
80
91
  this.#updateAssetSource = shareQueue(this.#updateAssetSource)
81
92
  this.#handleTxLogUpdate = shareQueue(this.#handleTxLogUpdate)
93
+ this.#handleFeeDataUpdate = shareQueue(this.#handleFeeDataUpdate)
82
94
  this.#recompute = shareQueue(this.#recompute)
83
95
  this.#onAssetsChanged = shareQueue(this.#onAssetsChanged)
84
96
  this.#getBalancesForAssetSource = swallowErrors(this.#getBalancesForAssetSource)
@@ -103,6 +115,16 @@ class Balances {
103
115
  this.#handleTxLogUpdate({ walletAccount, assetName, txLog })
104
116
  )
105
117
  ),
118
+ this.#feeDataAtom.observe((changes) => {
119
+ Object.entries(changes).forEach(([assetName, feeData]) => {
120
+ const asset = this.#getAsset(assetName).baseAsset
121
+ if (asset.api?.features?.balancesRequireFeeData)
122
+ this.#walletAccounts.forEach((walletAccount) => {
123
+ return this.#handleFeeDataUpdate({ walletAccount, assetName, feeData })
124
+ })
125
+ })
126
+ }),
127
+
106
128
  getChanges(this.#availableAssetNamesAtom).observe(({ previous = [], current }) => {
107
129
  this.#availableAssetNames = current
108
130
  const added = difference(current, previous)
@@ -135,6 +157,15 @@ class Balances {
135
157
  return this.#updateAssetSource(opts)
136
158
  }
137
159
 
160
+ #handleFeeDataUpdate = (opts) => {
161
+ if (!this.#ready) {
162
+ this.#logger.log('ignore update from fee-data event.', { isReady: this.#ready })
163
+ return
164
+ }
165
+
166
+ return this.#updateAssetSource(opts)
167
+ }
168
+
138
169
  #pushIntoSharedQueue = makeConcurrent((fn) => fn(), { concurrency: 1 })
139
170
 
140
171
  #reset = ({ walletAccounts = this.#walletAccounts } = {}) => {
@@ -154,7 +185,12 @@ class Balances {
154
185
  // It adds both old and new balances to the response.
155
186
  // Ideally, selectors should start using the new ones (total, spendable, etc)
156
187
 
157
- #getBalancesFromBlockchainMetadata = ({ assetNames, accountState, txLogsByAssetName }) => {
188
+ #getBalancesFromBlockchainMetadata = ({
189
+ assetNames,
190
+ accountState,
191
+ feeData,
192
+ txLogsByAssetName,
193
+ }) => {
158
194
  // in the future accountState may have all the balances and we won't need to rely on txLog
159
195
  // for now, accountState-based balance has both the baseAsset and token balances while txLog is per asset/token
160
196
  const getAssetBalances = (assetName) => {
@@ -163,7 +199,7 @@ class Balances {
163
199
 
164
200
  const zero = asset.currency.ZERO
165
201
 
166
- const apiBalances = asset.api.getBalances({ asset, accountState, txLog })
202
+ const apiBalances = asset.api.getBalances({ asset, accountState, txLog, feeData })
167
203
  const balances = processAssetBalances({
168
204
  balanceFieldsConfig: this.#balanceFieldsConfig,
169
205
  balances: apiBalances || Object.create(null),
@@ -251,24 +287,33 @@ class Balances {
251
287
  #loadBlockchainMetadataForBalance = async ({
252
288
  assetName,
253
289
  walletAccount,
290
+ feeData: providedFeeData, // it's optional, undefined when balancesRequireFeeData is disabled
254
291
  accountState: providedAccountState = null,
255
292
  txLogsByAssetName: providedTxLogsByAssetName = {},
256
293
  }) => {
257
294
  const { value: txLogsByAssetSource } = await this.#txLogsAtom.get()
258
295
  let accountState = providedAccountState
296
+
297
+ const baseAsset = this.#getAsset(assetName).baseAsset
298
+
259
299
  if (!accountState) {
260
- const asset = this.#getAsset(assetName).baseAsset
261
- const assetNameToUse = asset.name
300
+ const assetNameToUse = baseAsset.name
262
301
  const { value } = await this.#accountStatesAtom.get()
263
302
  const cached = value[walletAccount]?.[assetNameToUse]
264
303
  if (cached) {
265
304
  accountState = cached
266
305
  } else {
267
- const AccountState = asset.api?.createAccountState?.()
306
+ const AccountState = baseAsset.api?.createAccountState?.()
268
307
  accountState = AccountState?.create()
269
308
  }
270
309
  }
271
310
 
311
+ let feeData = providedFeeData
312
+ if (!feeData && baseAsset.api?.features?.balancesRequireFeeData) {
313
+ const fees = await this.#feeDataAtom.get()
314
+ feeData = fees[baseAsset.name]
315
+ }
316
+
272
317
  const assetNames = this.#getRelatedAssetNames(assetName)
273
318
  const txLogsByAssetName = zipObject(
274
319
  assetNames,
@@ -281,6 +326,7 @@ class Balances {
281
326
  )
282
327
 
283
328
  return {
329
+ feeData,
284
330
  accountState,
285
331
  txLogsByAssetName,
286
332
  }
@@ -291,6 +337,7 @@ class Balances {
291
337
  walletAccount,
292
338
  accountState: providedAccountState,
293
339
  txLog: providedTxLog,
340
+ feeData: providedFeeData,
294
341
  }) => {
295
342
  if (!this.#isSupportedAsset(assetName)) return
296
343
 
@@ -299,20 +346,23 @@ class Balances {
299
346
  return
300
347
  }
301
348
 
302
- const { accountState, txLogsByAssetName } = await this.#loadBlockchainMetadataForBalance({
303
- assetName,
304
- walletAccount,
305
- accountState: providedAccountState,
306
- txLogsByAssetName: {
307
- [assetName]: providedTxLog,
308
- },
309
- })
349
+ const { accountState, txLogsByAssetName, feeData } =
350
+ await this.#loadBlockchainMetadataForBalance({
351
+ assetName,
352
+ walletAccount,
353
+ feeData: providedFeeData,
354
+ accountState: providedAccountState,
355
+ txLogsByAssetName: {
356
+ [assetName]: providedTxLog,
357
+ },
358
+ })
310
359
 
311
360
  const accountBalances = this.#balances[walletAccount]
312
361
  const newBalances = this.#getBalancesFromBlockchainMetadata({
313
362
  assetNames: this.#getRelatedAssetNames(assetName),
314
363
  accountState,
315
364
  txLogsByAssetName,
365
+ feeData,
316
366
  })
317
367
 
318
368
  const changes = Object.create(null)
@@ -375,6 +425,7 @@ const balancesDefinition = {
375
425
  'balancesAtom',
376
426
  'txLogsAtom',
377
427
  'accountStatesAtom',
428
+ 'feeDataAtom',
378
429
  ],
379
430
  public: true,
380
431
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/balances",
3
- "version": "13.9.0",
3
+ "version": "13.10.0",
4
4
  "description": "Tracks crypto balances across enabled wallet accounts.",
5
5
  "author": "Exodus Movement, Inc.",
6
6
  "license": "MIT",
@@ -56,16 +56,16 @@
56
56
  "@exodus/assets-feature": "^8.1.2",
57
57
  "@exodus/available-assets": "^8.7.0",
58
58
  "@exodus/bitcoin-meta": "^2.0.0",
59
- "@exodus/blockchain-metadata": "^15.11.0",
59
+ "@exodus/blockchain-metadata": "^15.12.0",
60
60
  "@exodus/ethereum-meta": "^2.0.0",
61
61
  "@exodus/fee-data-monitors": "^5.0.4",
62
62
  "@exodus/storage-memory": "^2.2.2",
63
- "@exodus/wallet-accounts": "^17.6.0",
63
+ "@exodus/wallet-accounts": "^17.6.1",
64
64
  "events": "^3.3.0",
65
65
  "redux": "^4.2.1"
66
66
  },
67
67
  "publishConfig": {
68
68
  "access": "public"
69
69
  },
70
- "gitHead": "579c847c10b207c049483955ff3f966c58e7b92f"
70
+ "gitHead": "067f9209a784784a11412f878443873efaa8f6dc"
71
71
  }