@exodus/assets-feature 3.7.0 → 3.8.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,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
+ ## [3.8.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/assets-feature@3.7.0...@exodus/assets-feature@3.8.0) (2023-11-21)
7
+
8
+ ### Features
9
+
10
+ - assets atom ([#4109](https://github.com/ExodusMovement/exodus-hydra/issues/4109)) ([d08880d](https://github.com/ExodusMovement/exodus-hydra/commit/d08880d8fe6f2f596cb01b955787de07f2a0747e))
11
+
6
12
  ## [3.7.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/assets-feature@3.6.0...@exodus/assets-feature@3.7.0) (2023-10-25)
7
13
 
8
14
  ### Features
@@ -0,0 +1,10 @@
1
+ import { createInMemoryAtom } from '@exodus/atoms'
2
+
3
+ const assetsAtomDefinition = {
4
+ id: 'assetsAtom',
5
+ type: 'atom',
6
+ factory: createInMemoryAtom,
7
+ dependencies: [],
8
+ }
9
+
10
+ export default assetsAtomDefinition
package/index.js CHANGED
@@ -6,6 +6,7 @@ import assetsApiDefinition from './api'
6
6
  import assetModuleDefinition from './module'
7
7
  import customTokensMonitorDefinition from './monitor'
8
8
  import assetPreferencesDefinition from './module/asset-preferences'
9
+ import assetsAtomDefinition from './atoms/assets'
9
10
 
10
11
  const assets = ({ config = {} } = {}) => {
11
12
  return {
@@ -18,6 +19,9 @@ const assets = ({ config = {} } = {}) => {
18
19
  storage: { namespace: 'assetPreferences' },
19
20
  config: config.multiAddressMode || {},
20
21
  },
22
+ {
23
+ definition: assetsAtomDefinition,
24
+ },
21
25
  {
22
26
  definition: disabledPurposesAtomDefinition,
23
27
  storage: { namespace: 'assetPreferences' },
@@ -25,10 +29,11 @@ const assets = ({ config = {} } = {}) => {
25
29
  },
26
30
  { definition: assetsApiDefinition },
27
31
  // TODO: add storage namespace once we remove customTokensStorage
28
- // eslint-disable-next-line hydra/missing-storage-namespace
32
+ // eslint-disable-next-line @exodus/hydra/missing-storage-namespace
29
33
  {
30
34
  definition: assetModuleDefinition,
31
35
  // storage: { namespace: 'customTokens' },
36
+ writesAtoms: ['assetsAtom'],
32
37
  aliases: [{ implementationId: 'customTokensStorage', interfaceId: 'storage' }],
33
38
  },
34
39
  {
@@ -6,8 +6,6 @@ import {
6
6
  CT_UPDATEABLE_PROPERTIES,
7
7
  } from '@exodus/assets'
8
8
  import { mapValues, pick, pickBy } from '@exodus/basic-utils'
9
- // eslint-disable-next-line no-restricted-imports
10
- import { fetchival } from '@exodus/fetch'
11
9
  import ExodusModule from '@exodus/module'
12
10
  // eslint-disable-next-line @exodus/restricted-imports/prefer-basic-utils
13
11
  import { get, isEmpty, keyBy, once } from 'lodash'
@@ -20,6 +18,7 @@ import {
20
18
  CT_UPDATE_INTERVAL,
21
19
  } from './constants'
22
20
  import { getAssetFromAssetId, getFetchErrorMessage, isDisabledCustomToken } from './utils'
21
+ import createFetchival from '@exodus/fetch/create-fetchival'
23
22
 
24
23
  const FILTERED_FIELDS = [
25
24
  'assetId',
@@ -86,24 +85,27 @@ export class AssetsModule extends ExodusModule {
86
85
  #storageTimestampKey
87
86
  #customTokenUpdateInterval
88
87
  #fetchCacheExpiry
89
- #globalAssetRegistry // temporary
90
88
  #isExternalRegistry
89
+ #assetsAtom
90
+ #fetchival
91
91
 
92
92
  constructor({
93
93
  storage,
94
94
  iconsStorage,
95
95
  assetRegistry, // temporary
96
96
  assetPlugins,
97
+ assetsAtom,
97
98
  combinedAssetsList = [],
98
- globalAssetRegistry, // temporary
99
99
  validateCustomToken = () => true,
100
100
  config = {},
101
+ fetch,
101
102
  }) {
102
103
  super({ name: 'AssetsModule' })
103
104
 
105
+ this.#assetsAtom = assetsAtom
104
106
  this.#storage = storage
105
107
  this.#isExternalRegistry = !!assetRegistry
106
- this.#registry = this.#isExternalRegistry ? assetRegistry : initialAssetRegistry
108
+ this.#setRegistry(assetRegistry ?? initialAssetRegistry)
107
109
  this.#assetPlugins = assetPlugins
108
110
  this.#combinedAssetsList = combinedAssetsList
109
111
  this.#fetchCache = {}
@@ -116,10 +118,24 @@ export class AssetsModule extends ExodusModule {
116
118
  this.#storageTimestampKey = config.storageTimestampKey || CT_TIMESTAMP_KEY
117
119
  this.#customTokenUpdateInterval = config.customTokenUpdateInterval || CT_UPDATE_INTERVAL
118
120
  this.#fetchCacheExpiry = config.fetchCacheExpiry ?? CT_FETCH_CACHE_EXPIRY
119
- this.#globalAssetRegistry = globalAssetRegistry // temporary
121
+ this.#fetchival = createFetchival({ fetch })
120
122
  }
121
123
 
122
- // Assets Registry API
124
+ #setRegistry = (registry) => {
125
+ this.#registry = registry
126
+
127
+ if (registry === initialAssetRegistry) {
128
+ return
129
+ }
130
+
131
+ // we want this to happen asap before load and even before start
132
+ this.#assetsAtom.set({
133
+ value: { ...this.#registry.getAssets() },
134
+ added: [],
135
+ updated: [],
136
+ disabled: [],
137
+ })
138
+ }
123
139
 
124
140
  initialize = ({ assetClientInterface }) => {
125
141
  assert(
@@ -135,11 +151,11 @@ export class AssetsModule extends ExodusModule {
135
151
  console.warn(`Incorrectly referenced supported asset ${name}. Expected ${asset.name}.`)
136
152
  })
137
153
  .filter(Boolean)
138
- this.#registry = createAssetRegistry({
139
- supportedAssetsList: [...assetsList, ...this.#combinedAssetsList],
140
- globalAssetRegistry: this.#globalAssetRegistry, // temporarily supply the deprecated globalAssetRegistry until @exodus/models v10. Afterwards it can simply be removed.
141
- })
142
- // this.emit('assets-initialized')
154
+ this.#setRegistry(
155
+ createAssetRegistry({
156
+ supportedAssetsList: [...assetsList, ...this.#combinedAssetsList],
157
+ })
158
+ )
143
159
  }
144
160
 
145
161
  getAssets = () => this.#registry.getAssets()
@@ -175,7 +191,7 @@ export class AssetsModule extends ExodusModule {
175
191
  // 3. both of the above from a network that has feature flag customTokens === false
176
192
  // It is OK to add all of these to the assets registry
177
193
  const { added, updated } = this.#handleFetchedTokens(Object.values(tokens))
178
- this.#announceChanges({ added, updated })
194
+ this.#flushChanges({ added, updated })
179
195
  } catch (e) {
180
196
  this._logger.log('error reading custom tokens from storage:', e)
181
197
  }
@@ -223,9 +239,9 @@ export class AssetsModule extends ExodusModule {
223
239
 
224
240
  if (isAdded) {
225
241
  await this.#storeCustomTokens([token])
226
- this.#announceChanges({ added: [asset] })
242
+ this.#flushChanges({ added: [asset] })
227
243
  } else {
228
- this.#announceChanges({ updated: [asset] })
244
+ this.#flushChanges({ updated: [asset] })
229
245
  }
230
246
 
231
247
  return asset
@@ -269,7 +285,7 @@ export class AssetsModule extends ExodusModule {
269
285
  await this.#storeCustomTokens(fetchedAndHandledTokens)
270
286
  }
271
287
 
272
- this.#announceChanges({ added, updated })
288
+ this.#flushChanges({ added, updated })
273
289
 
274
290
  return added.concat(updated)
275
291
  }
@@ -299,7 +315,7 @@ export class AssetsModule extends ExodusModule {
299
315
  await this.#iconsStorage.storeIcons(fetchedAndHandledTokens)
300
316
  }
301
317
 
302
- this.#announceChanges({ added, updated })
318
+ this.#flushChanges({ added, updated })
303
319
  }
304
320
 
305
321
  updateTokens = async () => {
@@ -324,10 +340,8 @@ export class AssetsModule extends ExodusModule {
324
340
  (token) => _isDisabledCustomToken(token) && !isDisabledCustomToken(this.getAsset(token.name))
325
341
  )
326
342
  const updatedAssets = tokens.map(this.#registry.updateCustomToken)
327
- this.emit('assets-update', updatedAssets)
328
- if (disabled.length > 0) {
329
- this.emit('assets-disable', disabled)
330
- }
343
+
344
+ this.#flushChanges({ updated: updatedAssets, disabled })
331
345
  }
332
346
 
333
347
  searchTokens = async ({ baseAssetName, lifecycleStatus, query, excludeTags = ['offensive'] }) => {
@@ -393,17 +407,30 @@ export class AssetsModule extends ExodusModule {
393
407
  return item?.value && Date.now() < item.expiry ? item.value : null
394
408
  }
395
409
 
396
- #announceChanges = ({ added = [], updated = [] }) => {
410
+ #flushChanges = ({ added = [], updated = [], disabled = [] } = {}) => {
411
+ // TODO: remove these once all consumers are refactored
397
412
  if (added.length > 0) this.emit('assets-add', added)
398
413
  if (updated.length > 0) this.emit('assets-update', updated)
414
+ if (disabled.length > 0) this.emit('assets-disable', disabled)
415
+
416
+ if (added.length + updated.length + disabled.length === 0) {
417
+ return
418
+ }
419
+
420
+ this.#assetsAtom.set({
421
+ value: { ...this.#registry.getAssets() },
422
+ added,
423
+ updated,
424
+ disabled,
425
+ })
399
426
  }
400
427
 
401
428
  #fetch = async (endpoint, body, resultPath) => {
402
429
  try {
403
430
  const testDataParam = this.#config.fetchTestValues ? '?test=true' : ''
404
- const res = await fetchival(this.#customTokensServerUrl)(`${endpoint}${testDataParam}`).post(
405
- body
406
- )
431
+ const res = await this.#fetchival(this.#customTokensServerUrl)(
432
+ `${endpoint}${testDataParam}`
433
+ ).post(body)
407
434
 
408
435
  if (res.status !== 'OK')
409
436
  throw new Error(`Custom tokens registry ${endpoint} ${res.status} ${res.message}`)
package/module/index.js CHANGED
@@ -1,20 +1,20 @@
1
- // eslint-disable-next-line @exodus/restricted-imports/no-exodus-assets
2
- import globalAssetRegistry from '@exodus/assets' // this is deprecated, do not use for any other purpose. Remove when we have @exodus/models v10.
3
1
  import AssetsModule from './assets-module'
4
2
 
5
- const createAssetsModule = (props) => new AssetsModule({ ...props, globalAssetRegistry })
3
+ const createAssetsModule = (props) => new AssetsModule(props)
6
4
 
7
5
  const assetsModuleDefinition = {
8
6
  id: 'assetsModule',
9
7
  type: 'module',
10
8
  factory: createAssetsModule,
11
9
  dependencies: [
10
+ 'assetsAtom',
12
11
  'storage',
13
12
  'iconsStorage',
14
13
  'assetPlugins',
15
14
  'combinedAssetsList?',
16
15
  'validateCustomToken?',
17
16
  'config',
17
+ 'fetch',
18
18
  ],
19
19
  }
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/assets-feature",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "license": "UNLICENSED",
5
5
  "description": "Assets module, clients and apis",
6
6
  "main": "index.js",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@exodus/basic-utils": "^2.1.0",
35
- "@exodus/fetch": "^1.2.1",
35
+ "@exodus/fetch": "^1.3.0",
36
36
  "@exodus/timer": "^1.0.0",
37
37
  "lodash": "^4.17.21",
38
38
  "minimalistic-assert": "^1.0.1",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "devDependencies": {
43
43
  "@exodus/assets": "^8.0.95",
44
- "@exodus/atoms": "^6.0.0",
44
+ "@exodus/atoms": "^6.0.1",
45
45
  "@exodus/available-assets": "^7.0.0",
46
46
  "@exodus/bitcoin-meta": "^1.0.1",
47
47
  "@exodus/bitcoin-plugin": "^1.0.3",
@@ -49,9 +49,9 @@
49
49
  "@exodus/bitcointestnet-plugin": "^1.0.3",
50
50
  "@exodus/combined-assets-meta": "^1.2.5",
51
51
  "@exodus/cosmos-plugin": "^1.0.0",
52
- "@exodus/ethereum-meta": "^1.0.30",
52
+ "@exodus/ethereum-meta": "^1.1.0",
53
53
  "@exodus/models": "^9.1.1",
54
- "@exodus/module": "^1.2.0",
54
+ "@exodus/module": "^1.2.2",
55
55
  "@exodus/osmosis-plugin": "^1.0.0",
56
56
  "@exodus/redux-dependency-injection": "^3.0.0",
57
57
  "@exodus/storage-memory": "^2.1.1",
@@ -60,7 +60,8 @@
60
60
  "eslint": "^8.44.0",
61
61
  "events": "^3.3.0",
62
62
  "jest": "^29.1.2",
63
+ "msw": "^2.0.0",
63
64
  "redux": "^4.0.0"
64
65
  },
65
- "gitHead": "c083b9655607e20206217c44bd55bd1b68e3c9b4"
66
+ "gitHead": "4480f8501a837520fd19403ea1cffb1e3d7ac9d8"
66
67
  }
package/plugin/index.js CHANGED
@@ -4,47 +4,60 @@ import { createAtomObserver } from '@exodus/atoms'
4
4
  const createAssetsPlugin = ({
5
5
  port,
6
6
  assetsModule,
7
+ assetsAtom,
7
8
  customTokensMonitor,
8
9
  disabledPurposesAtom,
9
10
  multiAddressModeAtom,
10
11
  }) => {
11
- const emitAssetsLoad = () => {
12
- const assets = assetsModule.getAssets()
13
- port.emit('assets-load', {
12
+ const emitAssets = async () => {
13
+ const { value: assets } = await assetsAtom.get()
14
+ const payload = {
14
15
  assets,
15
16
  defaultAccountStates: mapValues(
16
17
  pickBy(assets, (asset) => asset.api?.hasFeature?.('accountState')),
17
18
  (asset) => asset.api.createAccountState().create()
18
19
  ),
19
- })
20
- }
21
-
22
- const listeners = [
23
- ['assets-add', (data) => port.emit('assets-add', data)],
24
- ['assets-update', (data) => port.emit('assets-update', data)],
25
- ]
20
+ }
26
21
 
27
- listeners.forEach(([event, callback]) => {
28
- assetsModule.on(event, callback)
29
- })
22
+ port.emit('assets', payload)
23
+ port.emit('assets-load', payload) // legacy to be removed
24
+ }
30
25
 
31
26
  const observers = [
32
27
  createAtomObserver({ atom: disabledPurposesAtom, port, event: 'disabledPurposes' }),
33
28
  createAtomObserver({ atom: multiAddressModeAtom, port, event: 'multiAddressMode' }),
34
29
  ]
35
30
 
31
+ const subscribers = []
32
+
36
33
  const onStart = async () => {
34
+ subscribers.push(
35
+ assetsAtom.observe(({ added, updated, disabled }) => {
36
+ if (added.length > 0) {
37
+ port.emit('assets-add', added)
38
+ }
39
+
40
+ if (updated.length > 0) {
41
+ port.emit('assets-update', updated)
42
+ }
43
+
44
+ if (disabled.length > 0) {
45
+ port.emit('assets-disable', disabled)
46
+ }
47
+ })
48
+ )
49
+
37
50
  observers.forEach((observer) => observer.register())
38
51
 
39
52
  await assetsModule.load()
40
- emitAssetsLoad()
53
+ await emitAssets()
41
54
  }
42
55
 
43
56
  const onLoad = async () => {
44
57
  observers.forEach((observer) => observer.start())
45
58
 
46
59
  await assetsModule.load()
47
- emitAssetsLoad()
60
+ await emitAssets()
48
61
  }
49
62
 
50
63
  const onUnlock = () => {
@@ -56,11 +69,8 @@ const createAssetsPlugin = ({
56
69
  }
57
70
 
58
71
  const onStop = () => {
59
- listeners.forEach(([event, callback]) => {
60
- assetsModule.removeListener(event, callback)
61
- })
62
-
63
72
  observers.forEach((observer) => observer.unregister())
73
+ subscribers.forEach((unsubscribe) => unsubscribe())
64
74
  }
65
75
 
66
76
  const onClear = async () => {
@@ -77,6 +87,7 @@ const assetsPluginDefinition = {
77
87
  dependencies: [
78
88
  'port',
79
89
  'assetsModule',
90
+ 'assetsAtom',
80
91
  'customTokensMonitor',
81
92
  'disabledPurposesAtom',
82
93
  'multiAddressModeAtom',
package/redux/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { keyBy } from '@exodus/basic-utils'
2
1
  import id from './id'
3
2
  import initialState from './initial-state'
4
3
  import selectorDefinitions from './selectors'
4
+ import { keyBy } from '@exodus/basic-utils'
5
5
 
6
6
  const mergeAssets = (state, payload) => ({
7
7
  ...state,
@@ -16,14 +16,11 @@ const assetsReduxDefinition = {
16
16
  type: 'redux-module',
17
17
  initialState,
18
18
  eventReducers: {
19
- 'assets-load': (state, { assets }) => ({
20
- ...state,
21
- error: null,
22
- loaded: true,
23
- data: assets,
24
- }),
25
19
  'assets-add': mergeAssets,
26
20
  'assets-update': mergeAssets,
21
+ assets: (state, { assets }) => {
22
+ return { ...state, error: null, loaded: true, data: assets }
23
+ },
27
24
  multiAddressMode: (state, payload) => ({
28
25
  ...state,
29
26
  multiAddressMode: payload,