@exodus/assets-feature 7.2.2 → 7.4.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,18 @@
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
+ ## [7.4.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/assets-feature@7.3.0...@exodus/assets-feature@7.4.0) (2025-04-17)
7
+
8
+ ### Features
9
+
10
+ - feat: validate tokens using asset-schema-validation (#12011)
11
+
12
+ ## [7.3.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/assets-feature@7.2.2...@exodus/assets-feature@7.3.0) (2025-04-16)
13
+
14
+ ### Features
15
+
16
+ - feat: support updating combined asset members (#12019)
17
+
6
18
  ## [7.2.2](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/assets-feature@7.2.1...@exodus/assets-feature@7.2.2) (2025-04-14)
7
19
 
8
20
  ### Bug Fixes
@@ -1,10 +1,11 @@
1
+ /* eslint-disable @exodus/mutable/no-param-reassign-prop-only */
1
2
  import {
2
3
  createAssetRegistry,
3
4
  CT_DEFAULT_SERVER,
4
5
  CT_STATUS as STATUS,
5
6
  CT_UPDATEABLE_PROPERTIES,
6
7
  } from '@exodus/assets'
7
- import { keyBy, mapValues, partition, pick, pickBy } from '@exodus/basic-utils'
8
+ import { keyBy, mapValues, partition, pick, pickBy, difference } from '@exodus/basic-utils'
8
9
  import lodash from 'lodash'
9
10
  import assert from 'minimalistic-assert'
10
11
  import { memoizeLruCache } from '@exodus/asset-lib'
@@ -16,8 +17,9 @@ import {
16
17
  } from './constants.js'
17
18
  import { getAssetFromAssetId, getFetchErrorMessage, isDisabledCustomToken } from './utils.js'
18
19
  import createFetchival from '@exodus/fetch/create-fetchival'
20
+ import { validateCustomToken, isValidCustomToken } from '@exodus/asset-schema-validation'
19
21
 
20
- const { get, isEmpty, once } = lodash
22
+ const { get, isEmpty, once, uniq } = lodash
21
23
 
22
24
  const FILTERED_FIELDS = [
23
25
  'assetId',
@@ -77,7 +79,7 @@ export class AssetsModule {
77
79
  #customTokensServerUrl
78
80
  #updateableProps
79
81
  #iconsStorage
80
- #validateCustomToken
82
+ #shouldValidateCustomToken
81
83
  #assetPlugins
82
84
  #combinedAssetsList
83
85
  #storageDataKey
@@ -99,7 +101,6 @@ export class AssetsModule {
99
101
  assetPlugins,
100
102
  assetsAtom,
101
103
  combinedAssetsList = [],
102
- validateCustomToken = () => true,
103
104
  config = {},
104
105
  fetch,
105
106
  logger,
@@ -114,7 +115,7 @@ export class AssetsModule {
114
115
  this.#customTokensServerUrl = config.customTokensServerUrl || CT_DEFAULT_SERVER
115
116
  this.#updateableProps = [...CT_UPDATEABLE_PROPERTIES, 'version']
116
117
  this.#iconsStorage = iconsStorage
117
- this.#validateCustomToken = validateCustomToken
118
+ this.#shouldValidateCustomToken = config.shouldValidateCustomToken ?? true
118
119
  this.#storageDataKey = config.storageDataKey || CT_DATA_KEY
119
120
  this.#storageTimestampKey = config.storageTimestampKey || CT_TIMESTAMP_KEY
120
121
  this.#customTokenUpdateInterval = config.customTokenUpdateInterval || CT_UPDATE_INTERVAL
@@ -222,11 +223,17 @@ export class AssetsModule {
222
223
  }
223
224
 
224
225
  const _token = await fetchTokenAndCacheError()
225
- if (!this.#validateCustomToken(_token)) {
226
- this.#logger.warn('Invalid Custom Token schema')
227
- const err = new Error('Token did not pass validation')
228
- this.#setCache(key, { cachedError: err })
229
- throw err
226
+ if (this.#shouldValidateCustomToken) {
227
+ try {
228
+ validateCustomToken(_token)
229
+ } catch (e) {
230
+ this.#logger.warn(
231
+ `Token did not pass validation ${baseAssetName} ${assetId}. Error: ${e.message}`
232
+ )
233
+ const err = new Error('Token did not pass validation')
234
+ this.#setCache(key, { cachedError: err })
235
+ throw err
236
+ }
230
237
  }
231
238
 
232
239
  const token = normalizeToken(_token)
@@ -247,13 +254,14 @@ export class AssetsModule {
247
254
 
248
255
  try {
249
256
  const token = await this.fetchToken(assetId, baseAssetName)
250
- const { asset, isAdded } = this.#handleFetchedToken(token)
257
+ const { asset, isAdded, updates } = this.#handleFetchedToken(token)
258
+ const updated = updates.map(this.getAsset)
251
259
 
252
260
  if (isAdded) {
253
261
  await this.#storeCustomTokens([token])
254
- this.#flushChanges({ added: [asset] })
262
+ this.#flushChanges({ added: [asset], updated })
255
263
  } else {
256
- this.#flushChanges({ updated: [asset] })
264
+ this.#flushChanges({ updated: [asset, ...updated] })
257
265
  }
258
266
 
259
267
  return asset
@@ -317,7 +325,7 @@ export class AssetsModule {
317
325
  ? await this.#fetch('tokens', { tokenNames: tokenNamesToFetch }, 'tokens')
318
326
  : []
319
327
 
320
- const validTokens = fetchedTokens.filter(this.#validateCustomToken).map(normalizeToken)
328
+ const validTokens = fetchedTokens.filter(this.#isValidCustomToken).map(normalizeToken)
321
329
  if (validTokens.length !== fetchedTokens.length)
322
330
  this.#logger.warn('Invalid Custom Token schema')
323
331
 
@@ -365,6 +373,8 @@ export class AssetsModule {
365
373
  (token) => _isDisabledCustomToken(token) && !isDisabledCustomToken(this.getAsset(token.name))
366
374
  )
367
375
  const updatedAssets = tokens.map(this.#registry.updateCustomToken)
376
+ const parentNames = uniq(tokens.flatMap(this.#handleCombinedParents))
377
+ updatedAssets.push(...parentNames.map(this.getAsset))
368
378
 
369
379
  this.#flushChanges({ updated: updatedAssets, disabled })
370
380
  }
@@ -398,7 +408,7 @@ export class AssetsModule {
398
408
  { baseAssetName: baseAssetNames, lifecycleStatus, query, excludeTags, pageNumber, pageSize },
399
409
  'tokens'
400
410
  )
401
- const validTokens = tokens.filter(this.#validateCustomToken)
411
+ const validTokens = tokens.filter(this.#isValidCustomToken)
402
412
 
403
413
  if (validTokens.length !== tokens.length) this.#logger.warn('Invalid Custom Token schema')
404
414
 
@@ -415,7 +425,7 @@ export class AssetsModule {
415
425
 
416
426
  #fetchUpdates = async (assetVersions) => {
417
427
  const updatedTokens = await this.#fetch('updates', assetVersions, 'tokens')
418
- const validatedTokens = updatedTokens.filter(this.#validateCustomToken).map(normalizeToken)
428
+ const validatedTokens = updatedTokens.filter(this.#isValidCustomToken).map(normalizeToken)
419
429
  if (validatedTokens.length !== updatedTokens.length) {
420
430
  this.#logger.warn('Invalid Custom Token schema')
421
431
  }
@@ -468,33 +478,70 @@ export class AssetsModule {
468
478
  }
469
479
  }
470
480
 
481
+ #handleCombinedParents = ({ name, parents }) => {
482
+ if (!parents) return []
483
+ assert(Array.isArray(parents), 'Array expected')
484
+
485
+ const currentParents = this.#getAssetNamesBy(
486
+ (asset) => asset.isCombined && asset.combinedAssetNames.includes(name)
487
+ )
488
+ const updatedCombinedAssetNames = []
489
+
490
+ const removedParents = difference(currentParents, parents)
491
+ removedParents.forEach((parent) => {
492
+ this.#registry.updateCombinedAsset(parent, { removeMembers: [name] })
493
+ updatedCombinedAssetNames.push(parent)
494
+ })
495
+
496
+ const addedParents = difference(parents, currentParents)
497
+ addedParents.forEach((parent) => {
498
+ const asset = this.getAsset(parent)
499
+ if (!asset || !asset.isCombined) return
500
+
501
+ this.#registry.updateCombinedAsset(parent, { addMembers: [name] })
502
+ updatedCombinedAssetNames.push(parent)
503
+ })
504
+
505
+ return uniq(updatedCombinedAssetNames)
506
+ }
507
+
471
508
  #handleFetchedToken = (token) => {
472
509
  const asset = this.getAsset(token.name)
473
- if (asset) return { asset, isAdded: false }
510
+ if (asset) return { asset, isAdded: false, updates: [] }
474
511
 
475
512
  const { name } = this.#registry.addCustomToken(token) // add to registry
513
+ const updates = this.#handleCombinedParents(token)
514
+
476
515
  this.#logger.log('Custom token added:', name)
477
- return { asset: this.getAsset(name), isAdded: true }
516
+ return { asset: this.getAsset(name), isAdded: true, updates }
478
517
  }
479
518
 
480
519
  #handleFetchedTokens = (tokens) => {
481
520
  const fetchedAndHandledTokens = []
482
521
  const added = []
483
522
  const updated = []
523
+ const parentNames = []
484
524
  for (const token of tokens) {
485
525
  try {
486
- const { asset, isAdded } = this.#handleFetchedToken(token)
526
+ const { asset, isAdded, updates } = this.#handleFetchedToken(token)
527
+
487
528
  if (isAdded) {
488
529
  added.push(asset)
489
530
  fetchedAndHandledTokens.push(token)
490
531
  } else {
491
532
  updated.push(asset)
492
533
  }
534
+
535
+ if (updates.length > 0) {
536
+ parentNames.push(...updates)
537
+ }
493
538
  } catch (err) {
494
539
  this.#logger.warn('Handle fetched custom tokens error:', err.message)
495
540
  }
496
541
  }
497
542
 
543
+ updated.push(...uniq(parentNames).map(this.getAsset))
544
+
498
545
  return { fetchedAndHandledTokens, added, updated }
499
546
  }
500
547
 
@@ -585,6 +632,14 @@ export class AssetsModule {
585
632
  return !lastUpdateDate || Date.now() - lastUpdateDate > this.#customTokenUpdateInterval
586
633
  }
587
634
 
635
+ #isValidCustomToken = (token) => {
636
+ if (!this.#shouldValidateCustomToken) {
637
+ return true
638
+ }
639
+
640
+ return isValidCustomToken(token)
641
+ }
642
+
588
643
  clear = async () =>
589
644
  this.#storage &&
590
645
  Promise.all([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/assets-feature",
3
- "version": "7.2.2",
3
+ "version": "7.4.0",
4
4
  "license": "MIT",
5
5
  "description": "This Exodus SDK feature provides access to instances of all blockchain asset adapters supported by the wallet, and enables you to search for and add custom tokens at runtime.",
6
6
  "type": "module",
@@ -36,7 +36,8 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@exodus/asset-lib": "^5.3.0",
39
- "@exodus/assets": "^11.0.0",
39
+ "@exodus/asset-schema-validation": "^1.0.1",
40
+ "@exodus/assets": "^11.3.0",
40
41
  "@exodus/atoms": "^9.0.0",
41
42
  "@exodus/basic-utils": "^4.0.0",
42
43
  "@exodus/fetch": "^1.3.0",
@@ -60,6 +61,7 @@
60
61
  "@exodus/cosmos-plugin": "^1.3.3",
61
62
  "@exodus/ethereum-lib": "^5.0.0",
62
63
  "@exodus/ethereum-meta": "^2.0.0",
64
+ "@exodus/ethereum-plugin": "^2.7.4",
63
65
  "@exodus/fusion-local": "^2.1.0",
64
66
  "@exodus/keychain": "^7.3.0",
65
67
  "@exodus/logger": "^1.2.3",
@@ -68,7 +70,7 @@
68
70
  "@exodus/public-key-provider": "^4.1.1",
69
71
  "@exodus/redux-dependency-injection": "^4.1.1",
70
72
  "@exodus/storage-memory": "^2.2.2",
71
- "@exodus/wallet-accounts": "^17.5.0",
73
+ "@exodus/wallet-accounts": "^17.5.1",
72
74
  "@exodus/wild-emitter": "^1.0.0",
73
75
  "bip39": "^3.1.0",
74
76
  "events": "^3.3.0",
@@ -78,5 +80,5 @@
78
80
  "publishConfig": {
79
81
  "access": "public"
80
82
  },
81
- "gitHead": "f34be287dba62ba8e3872c90bf6f0973b07ea2d6"
83
+ "gitHead": "05dfc1fd50441f5b6513eef369476167c3b02c57"
82
84
  }