@exodus/assets-feature 9.2.6 → 9.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 +6 -0
- package/constants.js +5 -0
- package/index.js +2 -0
- package/module/assets-module.js +152 -32
- package/module/dynamic-combined-assets.js +106 -0
- package/package.json +9 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
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
|
+
## [9.3.0](https://github.com/ExodusMovement/exodus/compare/@exodus/assets-feature@9.2.6...@exodus/assets-feature@9.3.0) (2026-08-25)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **assets:** add dynamic combined assets generation ([#44197](https://github.com/ExodusMovement/exodus/issues/44197)) ([f107abe](https://github.com/ExodusMovement/exodus/commit/f107abece571af407df3b7154518aa689b8c1a04))
|
|
11
|
+
|
|
6
12
|
## [9.2.6](https://github.com/ExodusMovement/exodus/compare/@exodus/assets-feature@9.2.5...@exodus/assets-feature@9.2.6) (2026-07-30)
|
|
7
13
|
|
|
8
14
|
### Bug Fixes
|
package/constants.js
CHANGED
|
@@ -6,7 +6,12 @@ export const disabledPurposesDefaultConfig = {
|
|
|
6
6
|
defaults: Object.create(null),
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export const assetsModuleDefaultConfig = {
|
|
10
|
+
useDynamicCombinedAssets: false,
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
export const defaultConfig = {
|
|
10
14
|
multiAddressMode: multiAddressModeDefaultConfig,
|
|
11
15
|
disabledPurposes: disabledPurposesDefaultConfig,
|
|
16
|
+
assetsModule: assetsModuleDefaultConfig,
|
|
12
17
|
}
|
package/index.js
CHANGED
|
@@ -19,6 +19,7 @@ const assets = (config = Object.create(null)) => {
|
|
|
19
19
|
compatibilityModeGapLimits,
|
|
20
20
|
compatibilityModeMultiAddressMode,
|
|
21
21
|
searchBasedAssetLoaderPlugin,
|
|
22
|
+
assetsModule,
|
|
22
23
|
} = { ...defaultConfig, ...config }
|
|
23
24
|
|
|
24
25
|
return {
|
|
@@ -52,6 +53,7 @@ const assets = (config = Object.create(null)) => {
|
|
|
52
53
|
definition: assetModuleDefinition,
|
|
53
54
|
// storage: { namespace: 'customTokens' },
|
|
54
55
|
aliases: [{ implementationId: 'customTokensStorage', interfaceId: 'storage' }],
|
|
56
|
+
config: assetsModule,
|
|
55
57
|
},
|
|
56
58
|
{ definition: assetPreferencesDefinition },
|
|
57
59
|
{ if: { registered: ['customTokensStorage'] }, definition: customTokensMonitorDefinition },
|
package/module/assets-module.js
CHANGED
|
@@ -26,6 +26,11 @@ import createFetchival from '@exodus/fetch/create-fetchival'
|
|
|
26
26
|
import { validateCustomToken } from '@exodus/asset-schema-validation'
|
|
27
27
|
import makeConcurrent from 'make-concurrent'
|
|
28
28
|
import oldToNewStyleTokenNames from '@exodus/asset-legacy-token-name-mapping'
|
|
29
|
+
import {
|
|
30
|
+
createDynamicCombinedAssetDefinitions,
|
|
31
|
+
shouldTokenJoinCombinedAsset,
|
|
32
|
+
findDynamicCombinedAssetForToken,
|
|
33
|
+
} from './dynamic-combined-assets.js'
|
|
29
34
|
|
|
30
35
|
// oxlint-disable-next-line @exodus/oxlint-plugin-restricted-imports/prefer-basic-utils
|
|
31
36
|
const { get, isEmpty, once, uniq, chunk, pick } = lodash
|
|
@@ -79,6 +84,7 @@ export class AssetsModule {
|
|
|
79
84
|
#fetchival
|
|
80
85
|
#logger
|
|
81
86
|
#invalidTokenNames
|
|
87
|
+
#useDynamicCombinedAssets
|
|
82
88
|
|
|
83
89
|
constructor({
|
|
84
90
|
storage,
|
|
@@ -118,6 +124,7 @@ export class AssetsModule {
|
|
|
118
124
|
),
|
|
119
125
|
...(config.invalidTokenNames || []),
|
|
120
126
|
])
|
|
127
|
+
this.#useDynamicCombinedAssets = config.useDynamicCombinedAssets ?? false
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
#setRegistry = (registry) => {
|
|
@@ -161,11 +168,22 @@ export class AssetsModule {
|
|
|
161
168
|
console.warn(`Incorrectly referenced supported asset ${name}. Expected ${asset.name}.`)
|
|
162
169
|
})
|
|
163
170
|
.filter(Boolean)
|
|
171
|
+
|
|
172
|
+
const combinedAssets = this.#useDynamicCombinedAssets ? [] : this.#combinedAssetsList
|
|
173
|
+
|
|
164
174
|
this.#setRegistry(
|
|
165
175
|
createAssetRegistry({
|
|
166
|
-
supportedAssetsList: [...assetsList, ...
|
|
176
|
+
supportedAssetsList: [...assetsList, ...combinedAssets],
|
|
167
177
|
})
|
|
168
178
|
)
|
|
179
|
+
|
|
180
|
+
// Only flush static combined assets - dynamic ones are flushed in load()
|
|
181
|
+
if (!this.#useDynamicCombinedAssets) {
|
|
182
|
+
const allCombined = Object.values(this.#registry.getAssets()).filter((a) => a.isCombined)
|
|
183
|
+
if (allCombined.length > 0) {
|
|
184
|
+
this.#flushChanges({ added: allCombined })
|
|
185
|
+
}
|
|
186
|
+
}
|
|
169
187
|
})
|
|
170
188
|
|
|
171
189
|
getAssets = () => this.#registry.getAssets()
|
|
@@ -188,6 +206,7 @@ export class AssetsModule {
|
|
|
188
206
|
load = once(async () => {
|
|
189
207
|
try {
|
|
190
208
|
const storedTokens = await this.#readCustomTokens()
|
|
209
|
+
|
|
191
210
|
const tokens = pickBy(storedTokens, ({ baseAssetName }) => {
|
|
192
211
|
const baseAsset = this.getAsset(baseAssetName)
|
|
193
212
|
return baseAsset?.api?.features?.customTokens
|
|
@@ -202,9 +221,34 @@ export class AssetsModule {
|
|
|
202
221
|
this.#flushChanges({ added, updated })
|
|
203
222
|
} catch (e) {
|
|
204
223
|
this.#logger.error('error reading custom tokens from storage: ' + e.message, e)
|
|
224
|
+
} finally {
|
|
225
|
+
if (this.#useDynamicCombinedAssets) {
|
|
226
|
+
this.#createDynamicCombinedAssets()
|
|
227
|
+
}
|
|
205
228
|
}
|
|
206
229
|
})
|
|
207
230
|
|
|
231
|
+
#createDynamicCombinedAssets = () => {
|
|
232
|
+
const allAssets = this.getAssets()
|
|
233
|
+
const definitions = createDynamicCombinedAssetDefinitions(allAssets)
|
|
234
|
+
|
|
235
|
+
const newlyCreatedNames = []
|
|
236
|
+
for (const definition of definitions) {
|
|
237
|
+
const existingCombined = this.getAsset(definition.name)
|
|
238
|
+
if (existingCombined) continue
|
|
239
|
+
|
|
240
|
+
this.#registry.addCombinedAsset(definition)
|
|
241
|
+
newlyCreatedNames.push(definition.name)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (newlyCreatedNames.length > 0) {
|
|
245
|
+
const newCombinedAssets = newlyCreatedNames.map((name) => this.getAsset(name)).filter(Boolean)
|
|
246
|
+
const childAssetNames = new Set(newCombinedAssets.flatMap((c) => c.combinedAssetNames))
|
|
247
|
+
const childAssets = [...childAssetNames].map((name) => this.getAsset(name)).filter(Boolean)
|
|
248
|
+
this.#flushChanges({ added: newCombinedAssets, updated: childAssets })
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
208
252
|
// Custom tokens API
|
|
209
253
|
|
|
210
254
|
fetchToken = async (assetId, baseAssetName) => {
|
|
@@ -316,16 +360,16 @@ export class AssetsModule {
|
|
|
316
360
|
|
|
317
361
|
try {
|
|
318
362
|
const token = await this.fetchToken(assetId, baseAssetName)
|
|
319
|
-
const { asset, isAdded,
|
|
320
|
-
const updated =
|
|
363
|
+
const { asset, isAdded, combinedUpdates, combinedCreated } = this.#handleFetchedToken(token)
|
|
364
|
+
const updated = combinedUpdates.map(this.getAsset)
|
|
365
|
+
const added = isAdded ? [asset, ...combinedCreated] : combinedCreated
|
|
321
366
|
|
|
322
367
|
if (isAdded) {
|
|
323
368
|
await this.#storeCustomTokens([token])
|
|
324
|
-
this.#flushChanges({ added: [asset], updated })
|
|
325
|
-
} else {
|
|
326
|
-
this.#flushChanges({ updated: [asset, ...updated] })
|
|
327
369
|
}
|
|
328
370
|
|
|
371
|
+
this.#flushChanges({ added, updated: isAdded ? updated : [asset, ...updated] })
|
|
372
|
+
|
|
329
373
|
return asset
|
|
330
374
|
} catch (err) {
|
|
331
375
|
this.#logger.warn(`Add custom token ${assetId} error on ${baseAssetName}`, err.message)
|
|
@@ -577,6 +621,10 @@ export class AssetsModule {
|
|
|
577
621
|
}
|
|
578
622
|
|
|
579
623
|
#handleCombinedParents = ({ name, parents }) => {
|
|
624
|
+
if (this.#useDynamicCombinedAssets) {
|
|
625
|
+
return []
|
|
626
|
+
}
|
|
627
|
+
|
|
580
628
|
if (!parents) return []
|
|
581
629
|
assert(Array.isArray(parents), 'Array expected')
|
|
582
630
|
|
|
@@ -603,59 +651,121 @@ export class AssetsModule {
|
|
|
603
651
|
return uniq(updatedCombinedAssetNames)
|
|
604
652
|
}
|
|
605
653
|
|
|
654
|
+
#handleDynamicCombinedAssetUpdate = (token) => {
|
|
655
|
+
if (!this.#useDynamicCombinedAssets) return { updated: [], created: [], members: [] }
|
|
656
|
+
if (!shouldTokenJoinCombinedAsset(token)) return { updated: [], created: [], members: [] }
|
|
657
|
+
|
|
658
|
+
const allAssets = this.getAssets()
|
|
659
|
+
const combinedAssets = Object.values(allAssets).filter((a) => a.isCombined)
|
|
660
|
+
const matchingCombined = findDynamicCombinedAssetForToken(token, combinedAssets)
|
|
661
|
+
|
|
662
|
+
if (matchingCombined) {
|
|
663
|
+
if (!matchingCombined.combinedAssetNames.includes(token.name)) {
|
|
664
|
+
this.#registry.updateCombinedAsset(matchingCombined.name, { addMembers: [token.name] })
|
|
665
|
+
return { updated: [matchingCombined.name], created: [], members: [] }
|
|
666
|
+
}
|
|
667
|
+
} else {
|
|
668
|
+
const matchingAssets = Object.values(allAssets).filter(
|
|
669
|
+
(a) =>
|
|
670
|
+
!a.isCombined &&
|
|
671
|
+
a.displayTicker?.toLowerCase() === token.displayTicker?.toLowerCase() &&
|
|
672
|
+
a.displayName?.toLowerCase() === token.displayName?.toLowerCase() &&
|
|
673
|
+
shouldTokenJoinCombinedAsset(a)
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
if (matchingAssets.length >= 2) {
|
|
677
|
+
const definitions = createDynamicCombinedAssetDefinitions(matchingAssets)
|
|
678
|
+
if (definitions.length > 0) {
|
|
679
|
+
const newCombined = this.#registry.addCombinedAsset(definitions[0])
|
|
680
|
+
const memberNames = matchingAssets.map((a) => a.name).filter((n) => n !== token.name)
|
|
681
|
+
return { updated: [], created: [newCombined], members: memberNames }
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return { updated: [], created: [], members: [] }
|
|
687
|
+
}
|
|
688
|
+
|
|
606
689
|
#handleFetchedToken = (token) => {
|
|
607
690
|
const asset = this.getAsset(token.name)
|
|
608
|
-
if (asset)
|
|
691
|
+
if (asset) {
|
|
692
|
+
const dynamicResult = this.#handleDynamicCombinedAssetUpdate(asset)
|
|
693
|
+
return {
|
|
694
|
+
asset,
|
|
695
|
+
isAdded: false,
|
|
696
|
+
combinedUpdates: dynamicResult.updated,
|
|
697
|
+
combinedCreated: dynamicResult.created,
|
|
698
|
+
combinedMembers: dynamicResult.members,
|
|
699
|
+
}
|
|
700
|
+
}
|
|
609
701
|
|
|
610
|
-
const { name } = this.#registry.addCustomToken(token)
|
|
611
|
-
const
|
|
702
|
+
const { name } = this.#registry.addCustomToken(token)
|
|
703
|
+
const staticUpdates = this.#handleCombinedParents(token)
|
|
704
|
+
const dynamicResult = this.#handleDynamicCombinedAssetUpdate(this.getAsset(name))
|
|
705
|
+
const combinedUpdates = [...staticUpdates, ...dynamicResult.updated]
|
|
612
706
|
|
|
613
707
|
this.#logger.log('Custom token added:', name)
|
|
614
|
-
return {
|
|
708
|
+
return {
|
|
709
|
+
asset: this.getAsset(name),
|
|
710
|
+
isAdded: true,
|
|
711
|
+
combinedUpdates,
|
|
712
|
+
combinedCreated: dynamicResult.created,
|
|
713
|
+
combinedMembers: dynamicResult.members,
|
|
714
|
+
}
|
|
615
715
|
}
|
|
616
716
|
|
|
617
717
|
#handleFetchedTokens = (tokens) => {
|
|
618
718
|
const fetchedAndHandledTokens = []
|
|
619
719
|
const added = []
|
|
620
720
|
const updated = []
|
|
621
|
-
const
|
|
721
|
+
const combinedUpdateNames = []
|
|
722
|
+
const combinedCreated = []
|
|
723
|
+
const combinedMemberNames = []
|
|
724
|
+
|
|
622
725
|
for (const token of tokens) {
|
|
623
726
|
try {
|
|
624
|
-
const
|
|
727
|
+
const result = this.#handleFetchedToken(token)
|
|
625
728
|
|
|
626
|
-
if (isAdded) {
|
|
627
|
-
added.push(asset)
|
|
729
|
+
if (result.isAdded) {
|
|
730
|
+
added.push(result.asset)
|
|
628
731
|
fetchedAndHandledTokens.push(token)
|
|
629
732
|
} else {
|
|
630
|
-
updated.push(asset)
|
|
733
|
+
updated.push(result.asset)
|
|
631
734
|
}
|
|
632
735
|
|
|
633
|
-
if (
|
|
634
|
-
|
|
736
|
+
if (result.combinedUpdates.length > 0) {
|
|
737
|
+
combinedUpdateNames.push(...result.combinedUpdates)
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (result.combinedCreated.length > 0) {
|
|
741
|
+
combinedCreated.push(...result.combinedCreated)
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (result.combinedMembers.length > 0) {
|
|
745
|
+
combinedMemberNames.push(...result.combinedMembers)
|
|
635
746
|
}
|
|
636
747
|
} catch (err) {
|
|
637
748
|
this.#logger.error('Handle fetched custom tokens error: ' + err.message, err)
|
|
638
749
|
}
|
|
639
750
|
}
|
|
640
751
|
|
|
641
|
-
|
|
752
|
+
const createdNames = new Set(combinedCreated.map((a) => a.name))
|
|
753
|
+
const dedupedUpdateNames = uniq(combinedUpdateNames).filter((name) => !createdNames.has(name))
|
|
754
|
+
const dedupedMemberNames = uniq(combinedMemberNames).filter((name) => !createdNames.has(name))
|
|
755
|
+
updated.push(...dedupedUpdateNames.map(this.getAsset).filter(Boolean))
|
|
756
|
+
updated.push(...dedupedMemberNames.map(this.getAsset).filter(Boolean))
|
|
757
|
+
added.push(...combinedCreated.map((a) => this.getAsset(a.name)).filter(Boolean))
|
|
642
758
|
|
|
643
759
|
return { fetchedAndHandledTokens, added, updated }
|
|
644
760
|
}
|
|
645
761
|
|
|
646
|
-
#
|
|
762
|
+
#fetchIconToken = memoizeLruCache(
|
|
647
763
|
async (assetName) => {
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
'tokens'
|
|
654
|
-
)
|
|
655
|
-
const token = tokens[0]
|
|
656
|
-
if (token?.icon) {
|
|
657
|
-
return this.#iconsStorage.unzipIcon(token.icon)
|
|
658
|
-
}
|
|
764
|
+
const tokens = await this.#fetchTokensByNames({
|
|
765
|
+
tokenNames: [assetName],
|
|
766
|
+
allowedStatusList: ['c', 'v', 'u'],
|
|
767
|
+
})
|
|
768
|
+
return tokens[0]
|
|
659
769
|
},
|
|
660
770
|
(assetName) => assetName,
|
|
661
771
|
{ max: 100 }
|
|
@@ -666,11 +776,21 @@ export class AssetsModule {
|
|
|
666
776
|
|
|
667
777
|
const asset = await this.getAsset(assetName)
|
|
668
778
|
if (asset) {
|
|
669
|
-
|
|
779
|
+
try {
|
|
780
|
+
const storedIcon = await this.#iconsStorage.getIcon(assetName)
|
|
781
|
+
if (storedIcon) return storedIcon
|
|
782
|
+
} catch (e) {
|
|
783
|
+
this.#logger.warn(`Failed to read stored icon for ${assetName}. ${e.message}`, e)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (asset.isBuiltIn) return
|
|
670
787
|
}
|
|
671
788
|
|
|
672
789
|
try {
|
|
673
|
-
|
|
790
|
+
const token = await this.#fetchIconToken(assetName)
|
|
791
|
+
if (!token?.icon) return
|
|
792
|
+
if (asset) await this.#storeIcons([token])
|
|
793
|
+
return this.#iconsStorage.unzipIcon(token.icon)
|
|
674
794
|
} catch (e) {
|
|
675
795
|
this.#logger.error(`Cannot load icon for ${assetName} from CTR. ${e.message}`, e)
|
|
676
796
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const TRUSTED_LIFECYCLE = new Set(['c'])
|
|
2
|
+
const MIN_ASSETS_TO_COMBINE = 2
|
|
3
|
+
|
|
4
|
+
const normalizeForKey = (str) => str.toLowerCase().trim()
|
|
5
|
+
|
|
6
|
+
const normalizeForName = (str) => str.toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
7
|
+
|
|
8
|
+
const createGroupKey = (displayTicker, displayName) =>
|
|
9
|
+
`${normalizeForKey(displayTicker)}::${normalizeForKey(displayName)}`
|
|
10
|
+
|
|
11
|
+
const groupAssetsByTickerAndName = (assets) => {
|
|
12
|
+
const groups = Object.create(null)
|
|
13
|
+
|
|
14
|
+
for (const asset of assets) {
|
|
15
|
+
if (asset.isCombined || !asset.displayTicker || !asset.displayName) continue
|
|
16
|
+
|
|
17
|
+
const key = createGroupKey(asset.displayTicker, asset.displayName)
|
|
18
|
+
if (!groups[key]) groups[key] = []
|
|
19
|
+
groups[key].push(asset)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return groups
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const selectPrimaryAsset = (assets) => {
|
|
26
|
+
const sorted = [...assets].sort((a, b) => a.name.localeCompare(b.name))
|
|
27
|
+
return sorted[0]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const resolveDecimals = (assets) => {
|
|
31
|
+
const decimals = assets.map((a) => a.currency?.defaultUnit?.power ?? 8)
|
|
32
|
+
return Math.max(...decimals)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const createDynamicCombinedAssetDefinition = (assetGroup) => {
|
|
36
|
+
const primary = selectPrimaryAsset(assetGroup)
|
|
37
|
+
const sortedAssets = [...assetGroup].sort((a, b) => a.name.localeCompare(b.name))
|
|
38
|
+
|
|
39
|
+
const normalizedTicker = normalizeForName(primary.displayTicker)
|
|
40
|
+
const normalizedDisplayName = normalizeForName(primary.displayName)
|
|
41
|
+
const decimals = resolveDecimals(assetGroup)
|
|
42
|
+
|
|
43
|
+
const name = `_combined_${normalizedTicker}_${normalizedDisplayName}`
|
|
44
|
+
const ticker = name.toUpperCase()
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
ticker,
|
|
49
|
+
baseAssetName: name,
|
|
50
|
+
displayName: primary.displayName,
|
|
51
|
+
displayTicker: primary.displayTicker,
|
|
52
|
+
assetType: 'MULTI_NETWORK_ASSET',
|
|
53
|
+
units: {
|
|
54
|
+
[`${name}_base`]: 0,
|
|
55
|
+
[ticker]: decimals,
|
|
56
|
+
},
|
|
57
|
+
combinedAssetNames: sortedAssets.map((a) => a.name),
|
|
58
|
+
info: primary.info || { description: '' },
|
|
59
|
+
primaryColor: primary.primaryColor || '#888888',
|
|
60
|
+
gradientColors: primary.gradientColors || ['#888888', '#666666'],
|
|
61
|
+
chainBadgeColors: primary.chainBadgeColors || primary.gradientColors || ['#888888', '#666666'],
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const isTrustedAsset = (asset) => {
|
|
66
|
+
// Built-in assets (bundled with app, not fetched from CTR) are trusted
|
|
67
|
+
if (asset.isBuiltIn === true && asset.isCustomToken !== true) return true
|
|
68
|
+
|
|
69
|
+
// Curated custom tokens from CTR are trusted (Exodus controls curation)
|
|
70
|
+
if (TRUSTED_LIFECYCLE.has(asset.lifecycleStatus)) return true
|
|
71
|
+
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const createDynamicCombinedAssetDefinitions = (assets) => {
|
|
76
|
+
const assetList = Array.isArray(assets) ? assets : Object.values(assets)
|
|
77
|
+
const trustedAssets = assetList.filter(isTrustedAsset)
|
|
78
|
+
const groups = groupAssetsByTickerAndName(trustedAssets)
|
|
79
|
+
|
|
80
|
+
const definitions = []
|
|
81
|
+
for (const assetGroup of Object.values(groups)) {
|
|
82
|
+
if (assetGroup.length >= MIN_ASSETS_TO_COMBINE) {
|
|
83
|
+
definitions.push(createDynamicCombinedAssetDefinition(assetGroup))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return definitions
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const findDynamicCombinedAssetForToken = (token, existingCombinedAssets) => {
|
|
91
|
+
if (!token.displayTicker || !token.displayName) return null
|
|
92
|
+
|
|
93
|
+
const tokenKey = createGroupKey(token.displayTicker, token.displayName)
|
|
94
|
+
|
|
95
|
+
for (const combined of existingCombinedAssets) {
|
|
96
|
+
if (!combined.isCombined) continue
|
|
97
|
+
if (!combined.displayTicker || !combined.displayName) continue
|
|
98
|
+
|
|
99
|
+
const combinedKey = createGroupKey(combined.displayTicker, combined.displayName)
|
|
100
|
+
if (combinedKey === tokenKey) return combined
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const shouldTokenJoinCombinedAsset = (token) => isTrustedAsset(token)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/assets-feature",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.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",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"@exodus/asset-schema-validation": "^1.2.0",
|
|
41
41
|
"@exodus/asset-types": "^0.5.0",
|
|
42
42
|
"@exodus/assets": "^11.3.0",
|
|
43
|
-
"@exodus/atoms": "^10.3.
|
|
44
|
-
"@exodus/basic-utils": "^5.
|
|
43
|
+
"@exodus/atoms": "^10.3.4",
|
|
44
|
+
"@exodus/basic-utils": "^5.2.0",
|
|
45
45
|
"@exodus/fetch": "^1.3.0",
|
|
46
46
|
"@exodus/fusion-atoms": "^1.4.0",
|
|
47
|
-
"@exodus/timer": "^1.
|
|
47
|
+
"@exodus/timer": "^1.2.2",
|
|
48
48
|
"lodash": "^4.17.21",
|
|
49
49
|
"make-concurrent": "^5.4.0",
|
|
50
50
|
"minimalistic-assert": "^1.0.1",
|
|
@@ -52,17 +52,16 @@
|
|
|
52
52
|
"reselect": "^3.0.1"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@exodus/available-assets": "^8.11.
|
|
55
|
+
"@exodus/available-assets": "^8.11.1",
|
|
56
56
|
"@exodus/bip32": "^4.3.0",
|
|
57
57
|
"@exodus/bip39": "^1.1.0",
|
|
58
|
-
"@exodus/bip44-constants": "^195.0.
|
|
58
|
+
"@exodus/bip44-constants": "^195.0.2",
|
|
59
59
|
"@exodus/bitcoin-meta": "^2.0.0",
|
|
60
60
|
"@exodus/bitcoin-plugin": "^2.0.0",
|
|
61
61
|
"@exodus/bitcoinregtest-plugin": "^2.0.0",
|
|
62
62
|
"@exodus/bitcointestnet-plugin": "^2.0.0",
|
|
63
63
|
"@exodus/blockchain-metadata": "^17.2.0",
|
|
64
64
|
"@exodus/cardano-lib": "^4.0.0",
|
|
65
|
-
"@exodus/combined-assets-meta": "^3.7.0",
|
|
66
65
|
"@exodus/cosmos-plugin": "^1.3.3",
|
|
67
66
|
"@exodus/ethereum-lib": "^5.0.0",
|
|
68
67
|
"@exodus/ethereum-meta": "^2.0.0",
|
|
@@ -75,8 +74,8 @@
|
|
|
75
74
|
"@exodus/public-key-provider": "^4.3.1",
|
|
76
75
|
"@exodus/redux-dependency-injection": "^5.0.0",
|
|
77
76
|
"@exodus/storage-memory": "^2.4.0",
|
|
78
|
-
"@exodus/wallet-accounts": "^20.4.
|
|
79
|
-
"@exodus/wild-emitter": "^1.
|
|
77
|
+
"@exodus/wallet-accounts": "^20.4.11",
|
|
78
|
+
"@exodus/wild-emitter": "^1.1.2",
|
|
80
79
|
"events": "^3.3.0",
|
|
81
80
|
"msw": "^2.0.0",
|
|
82
81
|
"redux": "^4.0.0"
|
|
@@ -85,5 +84,5 @@
|
|
|
85
84
|
"access": "public",
|
|
86
85
|
"provenance": false
|
|
87
86
|
},
|
|
88
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "d98bbb7e2796db99847671fe1559b916ca23073d"
|
|
89
88
|
}
|