@exodus/assets-feature 8.1.3 → 8.3.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
+ ## [8.3.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/assets-feature@8.2.0...@exodus/assets-feature@8.3.0) (2025-08-11)
7
+
8
+ ### Features
9
+
10
+ - feat(assets-feature): filter invalid fetched tokens (#13532)
11
+
12
+ - feat(assets-feature): paginate fetch tokens call (#13533)
13
+
14
+ ## [8.2.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/assets-feature@8.1.3...@exodus/assets-feature@8.2.0) (2025-08-07)
15
+
16
+ ### Features
17
+
18
+ - feat(assets-feature): add fetch tokens method (#13362)
19
+
6
20
  ## [8.1.3](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/assets-feature@8.1.2...@exodus/assets-feature@8.1.3) (2025-07-02)
7
21
 
8
22
  ### Bug Fixes
package/api/index.js CHANGED
@@ -12,6 +12,7 @@ const createAssetsApi = ({ assetPreferences, assetsModule }) => {
12
12
  },
13
13
  assets: {
14
14
  fetchToken: (...args) => assetsModule.fetchToken(...args),
15
+ fetchTokens: (...args) => assetsModule.fetchTokens(...args),
15
16
  searchTokens: (...args) => assetsModule.searchTokens(...args),
16
17
  updateTokens: (...args) => assetsModule.updateTokens(...args),
17
18
  addTokens: (...args) => assetsModule.addTokens(...args),
@@ -13,33 +13,20 @@ import {
13
13
  CT_FETCH_CACHE_EXPIRY,
14
14
  CT_TIMESTAMP_KEY,
15
15
  CT_UPDATE_INTERVAL,
16
+ CTR_TOKENS_LIMIT,
16
17
  } from './constants.js'
17
- import { getAssetFromAssetId, getFetchErrorMessage, isDisabledCustomToken } from './utils.js'
18
+ import {
19
+ filterBuiltInProps,
20
+ getAssetFromAssetId,
21
+ getFetchErrorMessage,
22
+ isDisabledCustomToken,
23
+ } from './utils.js'
18
24
  import createFetchival from '@exodus/fetch/create-fetchival'
19
25
  import { validateCustomToken, isValidCustomToken } from '@exodus/asset-schema-validation'
20
26
  import makeConcurrent from 'make-concurrent'
21
27
  import oldToNewStyleTokenNames from '@exodus/asset-legacy-token-name-mapping'
22
28
 
23
- const { get, isEmpty, once, uniq } = lodash
24
-
25
- const FILTERED_FIELDS = [
26
- 'assetId',
27
- 'baseAssetName',
28
- 'displayName',
29
- 'displayTicker',
30
- 'gradientColors',
31
- 'icon', // there is no icon field in built in assets, but there may be for custom tokens (TODO?)
32
- 'isBuiltIn',
33
- 'isCustomToken',
34
- 'lifecycleStatus',
35
- 'name',
36
- 'primaryColor',
37
- 'properName',
38
- 'properTicker',
39
- 'ticker',
40
- ]
41
-
42
- const filterBuiltInProps = (asset) => ({ ...pick(asset, FILTERED_FIELDS), assetName: asset.name })
29
+ const { get, isEmpty, once, uniq, chunk } = lodash
43
30
 
44
31
  const getFetchCacheKey = (baseAssetName, assetId) => `${assetId}-${baseAssetName}`
45
32
 
@@ -250,6 +237,86 @@ export class AssetsModule {
250
237
  return this.#getCache(key)
251
238
  }
252
239
 
240
+ #fetchTokens = async (assetDescriptors) => {
241
+ for (const { baseAssetName } of assetDescriptors) {
242
+ this.#assertSupportsCustomTokens(baseAssetName)
243
+ }
244
+
245
+ const [builtInDescriptors, customTokensDescriptors] = partition(
246
+ assetDescriptors,
247
+ ({ assetId, baseAssetName }) => !!this.#getAssetFromAssetId(assetId, baseAssetName)
248
+ )
249
+
250
+ const getBuiltInDefinitions = (descriptors) =>
251
+ descriptors.map(({ assetId, baseAssetName }) =>
252
+ filterBuiltInProps(this.#getAssetFromAssetId(assetId, baseAssetName))
253
+ )
254
+
255
+ const getCustomTokensDefinitions = async (descriptors) => {
256
+ const _tokens = await this.#fetch(
257
+ `tokens`,
258
+ { assetIds: descriptors, lifecycleStatus: ['c', 'v', 'u'] },
259
+ 'tokens'
260
+ )
261
+
262
+ let validTokens = []
263
+ if (this.#shouldValidateCustomToken) {
264
+ for (const token of _tokens) {
265
+ try {
266
+ validateCustomToken(token)
267
+ validTokens.push(token)
268
+ } catch (e) {
269
+ this.#logger.warn(
270
+ `Token did not pass validation ${token.baseAssetName} ${token.assetId}. Error: ${e.message}`
271
+ )
272
+ }
273
+ }
274
+ } else {
275
+ validTokens = _tokens
276
+ }
277
+
278
+ const tokens = validTokens.map((token) => normalizeToken(token))
279
+
280
+ try {
281
+ await this.#iconsStorage.storeIcons(tokens)
282
+ } catch (err) {
283
+ this.#logger.warn(`An error occurred while decoding icons`, err.message)
284
+ }
285
+
286
+ for (const token of tokens) {
287
+ const key = getFetchCacheKey(token.baseAssetName, token.assetId)
288
+ this.#setCache(key, token)
289
+ }
290
+
291
+ return tokens
292
+ }
293
+
294
+ const builtInTokens = getBuiltInDefinitions(builtInDescriptors)
295
+ const customTokens = await getCustomTokensDefinitions(customTokensDescriptors)
296
+
297
+ return { builtInTokens, customTokens }
298
+ }
299
+
300
+ fetchTokens = async (assetDescriptors) => {
301
+ const pages = chunk(assetDescriptors, CTR_TOKENS_LIMIT)
302
+
303
+ const builtInTokens = []
304
+ const customTokens = []
305
+ for (const page of pages) {
306
+ const tokens = await this.#fetchTokens(page)
307
+ builtInTokens.push(...tokens.builtInTokens)
308
+ customTokens.push(...tokens.customTokens)
309
+ }
310
+
311
+ const builtInAssets = builtInTokens.map(({ name }) => this.getAsset(name))
312
+ const customTokenAssets = customTokens.map((definition) => {
313
+ const baseAsset = this.getAsset(definition.baseAssetName)
314
+ return baseAsset.api.createToken(definition)
315
+ })
316
+
317
+ return [...builtInAssets, ...customTokenAssets]
318
+ }
319
+
253
320
  addToken = async (assetId, baseAssetName) => {
254
321
  this.#assertCustomTokensStorageSupported()
255
322
  this.#assertSupportsCustomTokens(baseAssetName)
@@ -328,8 +395,9 @@ export class AssetsModule {
328
395
  : []
329
396
 
330
397
  const validTokens = fetchedTokens.filter(this.#isValidCustomToken).map(normalizeToken)
331
- if (validTokens.length !== fetchedTokens.length)
398
+ if (validTokens.length !== fetchedTokens.length) {
332
399
  this.#logger.warn('Invalid Custom Token schema')
400
+ }
333
401
 
334
402
  const tokens = tokenNamesToUpdate.map((tokenName) => assets[tokenName])
335
403
 
@@ -471,8 +539,9 @@ export class AssetsModule {
471
539
  `${endpoint}${testDataParam}`
472
540
  ).post(body)
473
541
 
474
- if (res.status !== 'OK')
542
+ if (res.status !== 'OK') {
475
543
  throw new Error(`Custom tokens registry ${endpoint} ${res.status} ${res.message}`)
544
+ }
476
545
 
477
546
  return get(res, resultPath)
478
547
  } catch (error) {
@@ -4,3 +4,4 @@ export const CT_DATA_KEY = 'customTokens'
4
4
  export const CT_TIMESTAMP_KEY = 'customTokensLastUpdate'
5
5
  export const CT_UPDATE_INTERVAL = ms('8h')
6
6
  export const CT_FETCH_CACHE_EXPIRY = ms('1h')
7
+ export const CTR_TOKENS_LIMIT = 50
package/module/utils.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CT_STATUS as STATUS } from '@exodus/assets'
2
+ import { pick } from '@exodus/basic-utils'
2
3
  import lodash from 'lodash'
3
4
  import assert from 'minimalistic-assert'
4
5
 
@@ -44,3 +45,25 @@ export async function getFetchErrorMessage(error) {
44
45
  return defaultExtraMessage
45
46
  }
46
47
  }
48
+
49
+ const FILTERED_FIELDS = [
50
+ 'assetId',
51
+ 'baseAssetName',
52
+ 'displayName',
53
+ 'displayTicker',
54
+ 'gradientColors',
55
+ 'icon', // there is no icon field in built in assets, but there may be for custom tokens (TODO?)
56
+ 'isBuiltIn',
57
+ 'isCustomToken',
58
+ 'lifecycleStatus',
59
+ 'name',
60
+ 'primaryColor',
61
+ 'properName',
62
+ 'properTicker',
63
+ 'ticker',
64
+ ]
65
+
66
+ export const filterBuiltInProps = (asset) => ({
67
+ ...pick(asset, FILTERED_FIELDS),
68
+ assetName: asset.name,
69
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/assets-feature",
3
- "version": "8.1.3",
3
+ "version": "8.3.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",
@@ -58,7 +58,7 @@
58
58
  "@exodus/bitcoin-plugin": "^1.29.1",
59
59
  "@exodus/bitcoinregtest-plugin": "^1.11.0",
60
60
  "@exodus/bitcointestnet-plugin": "^1.13.1",
61
- "@exodus/blockchain-metadata": "^15.12.0",
61
+ "@exodus/blockchain-metadata": "^15.14.0",
62
62
  "@exodus/cardano-lib": "^3.4.1",
63
63
  "@exodus/combined-assets-meta": "^3.0.0",
64
64
  "@exodus/cosmos-plugin": "^1.3.3",
@@ -68,12 +68,12 @@
68
68
  "@exodus/fusion-local": "^2.1.0",
69
69
  "@exodus/keychain": "^7.3.0",
70
70
  "@exodus/logger": "^1.2.3",
71
- "@exodus/models": "^12.14.0",
71
+ "@exodus/models": "^12.15.0",
72
72
  "@exodus/osmosis-plugin": "^1.3.3",
73
73
  "@exodus/public-key-provider": "^4.2.0",
74
74
  "@exodus/redux-dependency-injection": "^4.1.1",
75
- "@exodus/storage-memory": "^2.2.2",
76
- "@exodus/wallet-accounts": "^17.6.1",
75
+ "@exodus/storage-memory": "^2.3.0",
76
+ "@exodus/wallet-accounts": "^17.6.2",
77
77
  "@exodus/wild-emitter": "^1.0.0",
78
78
  "events": "^3.3.0",
79
79
  "msw": "^2.0.0",
@@ -82,5 +82,5 @@
82
82
  "publishConfig": {
83
83
  "access": "public"
84
84
  },
85
- "gitHead": "c557b881c9c696fff090c233121160d689f2bc95"
85
+ "gitHead": "b1676f695267900d5d7e94265f97d6e7ae4a92ae"
86
86
  }