@exodus/assets-feature 9.2.6 → 9.3.1

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
+ ## [9.3.1](https://github.com/ExodusMovement/exodus/compare/@exodus/assets-feature@9.3.0...@exodus/assets-feature@9.3.1) (2026-08-26)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **assets:** accumulate changes in load() before single flush ([#44228](https://github.com/ExodusMovement/exodus/issues/44228)) ([401680e](https://github.com/ExodusMovement/exodus/commit/401680ef721d736a349d54e89b044ec54a469b0c))
11
+
12
+ ## [9.3.0](https://github.com/ExodusMovement/exodus/compare/@exodus/assets-feature@9.2.6...@exodus/assets-feature@9.3.0) (2026-08-25)
13
+
14
+ ### Features
15
+
16
+ - **assets:** add dynamic combined assets generation ([#44197](https://github.com/ExodusMovement/exodus/issues/44197)) ([f107abe](https://github.com/ExodusMovement/exodus/commit/f107abece571af407df3b7154518aa689b8c1a04))
17
+
6
18
  ## [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
19
 
8
20
  ### 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 },
@@ -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, ...this.#combinedAssetsList],
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()
@@ -186,8 +204,12 @@ export class AssetsModule {
186
204
  getBaseAssetNames = () => this.#getAssetNamesBy((asset) => asset.baseAsset.name === asset.name)
187
205
 
188
206
  load = once(async () => {
207
+ const allAdded = []
208
+ const allUpdated = []
209
+
189
210
  try {
190
211
  const storedTokens = await this.#readCustomTokens()
212
+
191
213
  const tokens = pickBy(storedTokens, ({ baseAssetName }) => {
192
214
  const baseAsset = this.getAsset(baseAssetName)
193
215
  return baseAsset?.api?.features?.customTokens
@@ -199,12 +221,48 @@ export class AssetsModule {
199
221
  // 3. both of the above from a network that has feature flag customTokens === false
200
222
  // It is OK to add all of these to the assets registry
201
223
  const { added, updated } = this.#handleFetchedTokens(Object.values(tokens))
202
- this.#flushChanges({ added, updated })
224
+ allAdded.push(...added)
225
+ allUpdated.push(...updated)
203
226
  } catch (e) {
204
227
  this.#logger.error('error reading custom tokens from storage: ' + e.message, e)
205
228
  }
229
+
230
+ if (this.#useDynamicCombinedAssets) {
231
+ try {
232
+ const { added, updated } = this.#createDynamicCombinedAssets()
233
+ allAdded.push(...added)
234
+ allUpdated.push(...updated)
235
+ } catch (e) {
236
+ this.#logger.error('error creating dynamic combined assets: ' + e.message, e)
237
+ }
238
+ }
239
+
240
+ this.#flushChanges({ added: allAdded, updated: allUpdated })
206
241
  })
207
242
 
243
+ #createDynamicCombinedAssets = () => {
244
+ const allAssets = this.getAssets()
245
+ const definitions = createDynamicCombinedAssetDefinitions(allAssets)
246
+
247
+ const newlyCreatedNames = []
248
+ for (const definition of definitions) {
249
+ const existingCombined = this.getAsset(definition.name)
250
+ if (existingCombined) continue
251
+
252
+ this.#registry.addCombinedAsset(definition)
253
+ newlyCreatedNames.push(definition.name)
254
+ }
255
+
256
+ if (newlyCreatedNames.length > 0) {
257
+ const newCombinedAssets = newlyCreatedNames.map((name) => this.getAsset(name)).filter(Boolean)
258
+ const childAssetNames = new Set(newCombinedAssets.flatMap((c) => c.combinedAssetNames))
259
+ const childAssets = [...childAssetNames].map((name) => this.getAsset(name)).filter(Boolean)
260
+ return { added: newCombinedAssets, updated: childAssets }
261
+ }
262
+
263
+ return { added: [], updated: [] }
264
+ }
265
+
208
266
  // Custom tokens API
209
267
 
210
268
  fetchToken = async (assetId, baseAssetName) => {
@@ -316,16 +374,16 @@ export class AssetsModule {
316
374
 
317
375
  try {
318
376
  const token = await this.fetchToken(assetId, baseAssetName)
319
- const { asset, isAdded, updates } = this.#handleFetchedToken(token)
320
- const updated = updates.map(this.getAsset)
377
+ const { asset, isAdded, combinedUpdates, combinedCreated } = this.#handleFetchedToken(token)
378
+ const updated = combinedUpdates.map(this.getAsset)
379
+ const added = isAdded ? [asset, ...combinedCreated] : combinedCreated
321
380
 
322
381
  if (isAdded) {
323
382
  await this.#storeCustomTokens([token])
324
- this.#flushChanges({ added: [asset], updated })
325
- } else {
326
- this.#flushChanges({ updated: [asset, ...updated] })
327
383
  }
328
384
 
385
+ this.#flushChanges({ added, updated: isAdded ? updated : [asset, ...updated] })
386
+
329
387
  return asset
330
388
  } catch (err) {
331
389
  this.#logger.warn(`Add custom token ${assetId} error on ${baseAssetName}`, err.message)
@@ -577,6 +635,10 @@ export class AssetsModule {
577
635
  }
578
636
 
579
637
  #handleCombinedParents = ({ name, parents }) => {
638
+ if (this.#useDynamicCombinedAssets) {
639
+ return []
640
+ }
641
+
580
642
  if (!parents) return []
581
643
  assert(Array.isArray(parents), 'Array expected')
582
644
 
@@ -603,59 +665,121 @@ export class AssetsModule {
603
665
  return uniq(updatedCombinedAssetNames)
604
666
  }
605
667
 
668
+ #handleDynamicCombinedAssetUpdate = (token) => {
669
+ if (!this.#useDynamicCombinedAssets) return { updated: [], created: [], members: [] }
670
+ if (!shouldTokenJoinCombinedAsset(token)) return { updated: [], created: [], members: [] }
671
+
672
+ const allAssets = this.getAssets()
673
+ const combinedAssets = Object.values(allAssets).filter((a) => a.isCombined)
674
+ const matchingCombined = findDynamicCombinedAssetForToken(token, combinedAssets)
675
+
676
+ if (matchingCombined) {
677
+ if (!matchingCombined.combinedAssetNames.includes(token.name)) {
678
+ this.#registry.updateCombinedAsset(matchingCombined.name, { addMembers: [token.name] })
679
+ return { updated: [matchingCombined.name], created: [], members: [] }
680
+ }
681
+ } else {
682
+ const matchingAssets = Object.values(allAssets).filter(
683
+ (a) =>
684
+ !a.isCombined &&
685
+ a.displayTicker?.toLowerCase() === token.displayTicker?.toLowerCase() &&
686
+ a.displayName?.toLowerCase() === token.displayName?.toLowerCase() &&
687
+ shouldTokenJoinCombinedAsset(a)
688
+ )
689
+
690
+ if (matchingAssets.length >= 2) {
691
+ const definitions = createDynamicCombinedAssetDefinitions(matchingAssets)
692
+ if (definitions.length > 0) {
693
+ const newCombined = this.#registry.addCombinedAsset(definitions[0])
694
+ const memberNames = matchingAssets.map((a) => a.name).filter((n) => n !== token.name)
695
+ return { updated: [], created: [newCombined], members: memberNames }
696
+ }
697
+ }
698
+ }
699
+
700
+ return { updated: [], created: [], members: [] }
701
+ }
702
+
606
703
  #handleFetchedToken = (token) => {
607
704
  const asset = this.getAsset(token.name)
608
- if (asset) return { asset, isAdded: false, updates: [] }
705
+ if (asset) {
706
+ const dynamicResult = this.#handleDynamicCombinedAssetUpdate(asset)
707
+ return {
708
+ asset,
709
+ isAdded: false,
710
+ combinedUpdates: dynamicResult.updated,
711
+ combinedCreated: dynamicResult.created,
712
+ combinedMembers: dynamicResult.members,
713
+ }
714
+ }
609
715
 
610
- const { name } = this.#registry.addCustomToken(token) // add to registry
611
- const updates = this.#handleCombinedParents(token)
716
+ const { name } = this.#registry.addCustomToken(token)
717
+ const staticUpdates = this.#handleCombinedParents(token)
718
+ const dynamicResult = this.#handleDynamicCombinedAssetUpdate(this.getAsset(name))
719
+ const combinedUpdates = [...staticUpdates, ...dynamicResult.updated]
612
720
 
613
721
  this.#logger.log('Custom token added:', name)
614
- return { asset: this.getAsset(name), isAdded: true, updates }
722
+ return {
723
+ asset: this.getAsset(name),
724
+ isAdded: true,
725
+ combinedUpdates,
726
+ combinedCreated: dynamicResult.created,
727
+ combinedMembers: dynamicResult.members,
728
+ }
615
729
  }
616
730
 
617
731
  #handleFetchedTokens = (tokens) => {
618
732
  const fetchedAndHandledTokens = []
619
733
  const added = []
620
734
  const updated = []
621
- const parentNames = []
735
+ const combinedUpdateNames = []
736
+ const combinedCreated = []
737
+ const combinedMemberNames = []
738
+
622
739
  for (const token of tokens) {
623
740
  try {
624
- const { asset, isAdded, updates } = this.#handleFetchedToken(token)
741
+ const result = this.#handleFetchedToken(token)
625
742
 
626
- if (isAdded) {
627
- added.push(asset)
743
+ if (result.isAdded) {
744
+ added.push(result.asset)
628
745
  fetchedAndHandledTokens.push(token)
629
746
  } else {
630
- updated.push(asset)
747
+ updated.push(result.asset)
748
+ }
749
+
750
+ if (result.combinedUpdates.length > 0) {
751
+ combinedUpdateNames.push(...result.combinedUpdates)
631
752
  }
632
753
 
633
- if (updates.length > 0) {
634
- parentNames.push(...updates)
754
+ if (result.combinedCreated.length > 0) {
755
+ combinedCreated.push(...result.combinedCreated)
756
+ }
757
+
758
+ if (result.combinedMembers.length > 0) {
759
+ combinedMemberNames.push(...result.combinedMembers)
635
760
  }
636
761
  } catch (err) {
637
762
  this.#logger.error('Handle fetched custom tokens error: ' + err.message, err)
638
763
  }
639
764
  }
640
765
 
641
- updated.push(...uniq(parentNames).map(this.getAsset))
766
+ const createdNames = new Set(combinedCreated.map((a) => a.name))
767
+ const dedupedUpdateNames = uniq(combinedUpdateNames).filter((name) => !createdNames.has(name))
768
+ const dedupedMemberNames = uniq(combinedMemberNames).filter((name) => !createdNames.has(name))
769
+ updated.push(...dedupedUpdateNames.map(this.getAsset).filter(Boolean))
770
+ updated.push(...dedupedMemberNames.map(this.getAsset).filter(Boolean))
771
+ added.push(...combinedCreated.map((a) => this.getAsset(a.name)).filter(Boolean))
642
772
 
643
773
  return { fetchedAndHandledTokens, added, updated }
644
774
  }
645
775
 
646
- #fetchIcon = memoizeLruCache(
776
+ #fetchIconToken = memoizeLruCache(
647
777
  async (assetName) => {
648
- const tokenNames = [assetName]
649
-
650
- const tokens = await this.#fetch(
651
- 'tokens',
652
- { tokenNames, lifecycleStatus: ['c', 'v', 'u'] },
653
- 'tokens'
654
- )
655
- const token = tokens[0]
656
- if (token?.icon) {
657
- return this.#iconsStorage.unzipIcon(token.icon)
658
- }
778
+ const tokens = await this.#fetchTokensByNames({
779
+ tokenNames: [assetName],
780
+ allowedStatusList: ['c', 'v', 'u'],
781
+ })
782
+ return tokens[0]
659
783
  },
660
784
  (assetName) => assetName,
661
785
  { max: 100 }
@@ -666,11 +790,21 @@ export class AssetsModule {
666
790
 
667
791
  const asset = await this.getAsset(assetName)
668
792
  if (asset) {
669
- return this.#iconsStorage.getIcon(assetName)
793
+ try {
794
+ const storedIcon = await this.#iconsStorage.getIcon(assetName)
795
+ if (storedIcon) return storedIcon
796
+ } catch (e) {
797
+ this.#logger.warn(`Failed to read stored icon for ${assetName}. ${e.message}`, e)
798
+ }
799
+
800
+ if (asset.isBuiltIn) return
670
801
  }
671
802
 
672
803
  try {
673
- return await this.#fetchIcon(assetName)
804
+ const token = await this.#fetchIconToken(assetName)
805
+ if (!token?.icon) return
806
+ if (asset) await this.#storeIcons([token])
807
+ return this.#iconsStorage.unzipIcon(token.icon)
674
808
  } catch (e) {
675
809
  this.#logger.error(`Cannot load icon for ${assetName} from CTR. ${e.message}`, e)
676
810
  }
@@ -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.2.6",
3
+ "version": "9.3.1",
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.3",
44
- "@exodus/basic-utils": "^5.0.0",
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.1.2",
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.0",
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.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.10",
79
- "@exodus/wild-emitter": "^1.0.0",
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": "07897763e7cbfa9cf251bd7c436537e059c9fbab"
87
+ "gitHead": "69a95445c733ef5519ac3fde6632fa9afc399b76"
89
88
  }