@subwallet/extension-base 1.3.79-1 → 1.3.80-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.
Files changed (48) hide show
  1. package/background/KoniTypes.d.ts +13 -3
  2. package/cjs/core/substrate/system-pallet.js +3 -0
  3. package/cjs/koni/api/nft/index.js +0 -14
  4. package/cjs/koni/background/cron.js +0 -17
  5. package/cjs/koni/background/handlers/Extension.js +6 -5
  6. package/cjs/koni/background/handlers/State.js +24 -6
  7. package/cjs/packageInfo.js +1 -1
  8. package/cjs/services/balance-service/helpers/process.js +49 -21
  9. package/cjs/services/balance-service/index.js +2 -2
  10. package/cjs/services/chain-service/constants.js +6 -2
  11. package/cjs/services/earning-service/handlers/special.js +3 -2
  12. package/cjs/services/nft-service/index.js +219 -150
  13. package/cjs/services/nft-service/multi-chain-nft-fetcher.js +145 -0
  14. package/cjs/services/nft-service/nft-handlers/base-nft-handler.js +28 -0
  15. package/cjs/services/nft-service/nft-handlers/evm/evm-nft-handler.js +179 -0
  16. package/cjs/services/nft-service/nft-handlers/registry.js +37 -0
  17. package/cjs/services/nft-service/nft-handlers/unique/unique-nft-handler.js +187 -0
  18. package/cjs/services/storage-service/DatabaseService.js +1 -1
  19. package/core/substrate/system-pallet.js +3 -0
  20. package/koni/api/nft/index.js +1 -15
  21. package/koni/background/cron.d.ts +0 -1
  22. package/koni/background/cron.js +1 -18
  23. package/koni/background/handlers/Extension.d.ts +1 -0
  24. package/koni/background/handlers/Extension.js +6 -5
  25. package/koni/background/handlers/State.d.ts +8 -2
  26. package/koni/background/handlers/State.js +24 -6
  27. package/package.json +31 -6
  28. package/packageInfo.js +1 -1
  29. package/services/balance-service/helpers/process.d.ts +1 -1
  30. package/services/balance-service/helpers/process.js +48 -21
  31. package/services/balance-service/index.js +2 -2
  32. package/services/chain-service/constants.d.ts +3 -0
  33. package/services/chain-service/constants.js +3 -0
  34. package/services/earning-service/handlers/special.js +4 -3
  35. package/services/nft-service/index.d.ts +42 -6
  36. package/services/nft-service/index.js +219 -151
  37. package/services/nft-service/multi-chain-nft-fetcher.d.ts +13 -0
  38. package/services/nft-service/multi-chain-nft-fetcher.js +138 -0
  39. package/services/nft-service/nft-handlers/base-nft-handler.d.ts +13 -0
  40. package/services/nft-service/nft-handlers/base-nft-handler.js +21 -0
  41. package/services/nft-service/nft-handlers/evm/evm-nft-handler.d.ts +9 -0
  42. package/services/nft-service/nft-handlers/evm/evm-nft-handler.js +171 -0
  43. package/services/nft-service/nft-handlers/registry.d.ts +11 -0
  44. package/services/nft-service/nft-handlers/registry.js +29 -0
  45. package/services/nft-service/nft-handlers/unique/unique-nft-handler.d.ts +12 -0
  46. package/services/nft-service/nft-handlers/unique/unique-nft-handler.js +177 -0
  47. package/services/storage-service/DatabaseService.d.ts +1 -1
  48. package/services/storage-service/DatabaseService.js +1 -1
@@ -1,171 +1,239 @@
1
1
  // Copyright 2019-2022 @subwallet/extension-base authors & contributors
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { _AssetType } from '@subwallet/chain-list/types';
5
- import { _getEvmChainId } from '@subwallet/extension-base/services/chain-service/utils';
6
- import { baseParseIPFSUrl } from '@subwallet/extension-base/utils';
7
- import { getKeypairTypeByAddress } from '@subwallet/keyring';
8
- import { EthereumKeypairTypes } from '@subwallet/keyring/types';
9
- import subwalletApiSdk from '@subwallet-monorepos/subwallet-services-sdk';
4
+ import { ServiceStatus } from '@subwallet/extension-base/services/base/types';
5
+ import { _isChainSupportEvmNft, _isChainSupportNativeNft, _isChainSupportWasmNft } from '@subwallet/extension-base/services/chain-service/utils';
6
+ import { addLazy, createPromiseHandler, waitTimeout } from '@subwallet/extension-base/utils';
7
+ import keyring from '@subwallet/ui-keyring';
8
+ import { BehaviorSubject } from 'rxjs';
9
+ import { MultiChainNftFetcher } from "./multi-chain-nft-fetcher.js";
10
+ const INITIAL_NFT_STATE = {
11
+ nftData: {
12
+ total: 0,
13
+ nftList: []
14
+ },
15
+ nftCollections: []
16
+ };
10
17
 
11
- /**
12
- * NFT detection service
13
- * Responsible for managing NFT detection jobs per address
14
- */
18
+ // HIGH PRIORITY – no lazy
19
+ const IMMEDIATE_EVENTS = ['account.updateCurrent', 'account.add', 'account.remove'];
15
20
 
16
- function mapSdkToNftItem(rawInstance, chain, collectionId, owner) {
17
- var _rawInstance$token_ty, _rawInstance$token_ty2, _rawInstance$id;
18
- const metadata = rawInstance.metadata || {};
19
- const image = metadata.image || rawInstance.image_url || rawInstance.media_url || '';
20
- const attributes = Array.isArray(metadata.attributes) ? metadata.attributes : [];
21
- let rarity;
22
- const properties = {};
23
- for (const attr of attributes) {
24
- try {
25
- var _attr$trait_type;
26
- const key = (_attr$trait_type = attr.trait_type) === null || _attr$trait_type === void 0 ? void 0 : _attr$trait_type.trim();
27
- if (!key) {
28
- continue;
29
- }
30
- let value = attr.value;
31
- if (typeof value === 'string') {
32
- const lower = value.toLowerCase();
33
- if (lower === 'true') {
34
- value = true;
35
- } else if (lower === 'false') {
36
- value = false;
37
- }
38
- }
39
- properties[key] = value;
40
- if (key.toLowerCase() === 'rarity') {
41
- rarity = String(value);
42
- }
43
- } catch {}
44
- }
45
- const hasProperties = Object.keys(properties).length > 0;
46
- const normalizedType = (_rawInstance$token_ty = rawInstance.token_type) === null || _rawInstance$token_ty === void 0 ? void 0 : (_rawInstance$token_ty2 = _rawInstance$token_ty.replace('-', '')) === null || _rawInstance$token_ty2 === void 0 ? void 0 : _rawInstance$token_ty2.toUpperCase();
21
+ // LOW PRIORITY lazy
22
+ const LAZY_EVENTS = ['asset.updateState', 'chain.add'];
23
+ export class NftService {
24
+ NFT_INTERVAL_TIME = 2 * 60 * 60 * 1000; // 2 hours
47
25
 
48
- // Only support ERC721
49
- if (normalizedType !== 'ERC721') {
50
- return null;
51
- }
52
- return {
53
- id: (_rawInstance$id = rawInstance.id) === null || _rawInstance$id === void 0 ? void 0 : _rawInstance$id.toString(),
54
- chain,
55
- collectionId,
56
- owner: rawInstance.owner || owner,
57
- originAsset: undefined,
58
- name: metadata.name || `#${rawInstance.id}`,
59
- image: baseParseIPFSUrl(image),
60
- externalUrl: rawInstance.external_app_url || undefined,
61
- rarity,
62
- description: metadata.description || undefined,
63
- properties: hasProperties ? properties : null,
64
- type: normalizedType === 'ERC721' ? _AssetType.ERC721 : _AssetType.ERC721,
65
- // currently only support ERC721
66
- rmrk_ver: undefined,
67
- onChainOption: undefined,
68
- assetHubType: undefined
69
- };
70
- }
71
- function mapSdkToCollection(raw, chain) {
72
- var _raw$token_instances;
73
- const token = raw.token || {};
74
- return {
75
- // must-have
76
- collectionId: token.address_hash,
77
- chain,
78
- originAsset: undefined,
79
- // optional
80
- collectionName: token.name || token.symbol || 'Unknown Collection',
81
- image: token.icon_url || undefined,
82
- itemCount: Number(raw.amount) || ((_raw$token_instances = raw.token_instances) === null || _raw$token_instances === void 0 ? void 0 : _raw$token_instances.length) || 0,
83
- externalUrl: undefined
84
- };
85
- }
86
- export default class NftService {
87
- inProgress = new Set();
26
+ nftStateSubject = new BehaviorSubject(INITIAL_NFT_STATE);
27
+ nftState$ = this.nftStateSubject.asObservable();
28
+ isReloading = false;
29
+ startPromiseHandler = createPromiseHandler();
30
+ stopPromiseHandler = createPromiseHandler();
31
+ status = ServiceStatus.NOT_INITIALIZED;
32
+ get isStarted() {
33
+ return this.status === ServiceStatus.STARTED;
34
+ }
88
35
  constructor(state) {
89
36
  this.state = state;
37
+ this.multiChainFetcher = new MultiChainNftFetcher(state);
38
+ }
39
+ async init() {
40
+ this.status = ServiceStatus.INITIALIZING;
41
+ await this.state.eventService.waitKeyringReady;
42
+ await this.state.eventService.waitChainReady;
43
+ await this.loadCachedData();
44
+ this.status = ServiceStatus.INITIALIZED;
45
+ this.state.eventService.onLazy(this.handleEvents.bind(this));
46
+ }
47
+ async loadCachedData() {
48
+ const [nftData, collections] = await Promise.all([this.state.getNft(), this.state.getNftCollection()]);
49
+ this.nftStateSubject.next({
50
+ nftData: nftData || {
51
+ total: 0,
52
+ nftList: []
53
+ },
54
+ nftCollections: collections || []
55
+ });
56
+ }
57
+ async start() {
58
+ if (this.status === ServiceStatus.STOPPING) {
59
+ await this.waitForStopped();
60
+ }
61
+ if (this.isStarted || this.status === ServiceStatus.STARTING) {
62
+ return this.waitForStarted();
63
+ }
64
+ this.status = ServiceStatus.STARTING;
65
+ await this.refreshNftData();
66
+ this.status = ServiceStatus.STARTED;
67
+ this.startPromiseHandler.resolve();
68
+ this.startScanNft();
69
+ }
70
+ async stop() {
71
+ if (this.status === ServiceStatus.STARTING) {
72
+ await this.waitForStarted();
73
+ }
74
+ if (this.status === ServiceStatus.STOPPED || this.status === ServiceStatus.STOPPING) {
75
+ return this.waitForStopped();
76
+ }
77
+ this.status = ServiceStatus.STOPPING;
78
+ this.stopScanNft();
79
+ this.stopPromiseHandler.resolve();
80
+ }
81
+ waitForStarted() {
82
+ return this.startPromiseHandler.promise;
83
+ }
84
+ waitForStopped() {
85
+ return this.stopPromiseHandler.promise;
86
+ }
87
+ checkIfNftUpdateNeeded(events, eventTypes) {
88
+ if (!eventTypes.includes('chain.updateState')) {
89
+ return false;
90
+ }
91
+ const updatedChains = this.extractUpdatedChains(events);
92
+ return this.hasNftSupportedChainUpdate(updatedChains);
93
+ }
94
+ extractUpdatedChains(events) {
95
+ return events.filter(event => event.type === 'chain.updateState').map(event => event.data[0]);
90
96
  }
91
- async fetchEvmCollectionsWithPreview(addresses) {
92
- for (const address of addresses) {
93
- const type = getKeypairTypeByAddress(address);
94
- const typeValid = [...EthereumKeypairTypes].includes(type);
95
- if (typeValid) {
96
- if (this.inProgress.has(address)) {
97
- console.log(`[NftService] ${address} already running`);
98
- continue;
99
- }
100
- this.inProgress.add(address);
101
- try {
102
- const nftDetectionApi = subwalletApiSdk.nftDetectionApi;
103
- if (!(nftDetectionApi !== null && nftDetectionApi !== void 0 && nftDetectionApi.getEvmNftCollectionsByAddress)) {
104
- console.warn('[NftService] NftDetectionApi not available');
105
- continue;
106
- }
107
- const rawData = await nftDetectionApi.getEvmNftCollectionsByAddress(address);
108
- const allItems = [];
109
- const allCollections = [];
110
- for (const [chain, collections] of Object.entries(rawData)) {
111
- if (!Array.isArray(collections)) {
112
- continue;
113
- }
114
- for (const col of collections) {
115
- const mappedCollection = mapSdkToCollection(col, chain);
116
- allCollections.push(mappedCollection);
117
- if (Array.isArray(col.token_instances)) {
118
- const items = col.token_instances.map(inst => mapSdkToNftItem(inst, chain, mappedCollection.collectionId, address)).filter(i => Boolean(i));
119
- allItems.push(...items);
120
- }
121
- }
122
- }
123
- await this.state.handleDetectedNftCollections(allCollections);
124
- await this.state.handleDetectedNfts(address, allItems);
125
- } catch (err) {
126
- console.warn(`[NftService] detect error for ${address}`, err);
127
- } finally {
128
- this.inProgress.delete(address);
129
- }
97
+ hasNftSupportedChainUpdate(updatedChains) {
98
+ if (updatedChains.length === 0) {
99
+ return false;
100
+ }
101
+ const chainInfoMap = this.state.getServiceInfo().chainInfoMap;
102
+ return updatedChains.some(chainSlug => {
103
+ const chainInfo = chainInfoMap[chainSlug];
104
+ return this.isChainNftSupported(chainInfo);
105
+ });
106
+ }
107
+ isChainNftSupported(chainInfo) {
108
+ return _isChainSupportNativeNft(chainInfo) || _isChainSupportEvmNft(chainInfo) || _isChainSupportWasmNft(chainInfo);
109
+ }
110
+ handleImmediateRefresh(address) {
111
+ this.state.resetNft(address);
112
+ this.refreshNftData().catch(console.error);
113
+ }
114
+ scheduleLazyRefresh(delay) {
115
+ addLazy('nft.refresh', () => {
116
+ if (!this.isReloading && this.isStarted) {
117
+ this.refreshNftData().catch(console.error);
130
118
  }
119
+ }, delay, undefined, true);
120
+ }
121
+ handleEvents(events, eventTypes) {
122
+ const LAZY_REFRESH_DELAY = 1800;
123
+ const address = this.state.keyringService.context.currentAccount.proxyId;
124
+ const hasImmediateEvent = IMMEDIATE_EVENTS.some(event => eventTypes.includes(event));
125
+ const hasLazyEvent = LAZY_EVENTS.some(event => eventTypes.includes(event));
126
+ const needsNftUpdate = this.checkIfNftUpdateNeeded(events, eventTypes);
127
+ if (hasImmediateEvent || needsNftUpdate) {
128
+ this.handleImmediateRefresh(address);
129
+ return;
130
+ }
131
+ if (hasLazyEvent) {
132
+ this.scheduleLazyRefresh(LAZY_REFRESH_DELAY);
131
133
  }
132
134
  }
133
- async getFullNftInstancesByCollection(request) {
134
- const {
135
- chainInfo,
136
- contractAddress,
137
- owners
138
- } = request;
139
- const chainId = _getEvmChainId(chainInfo);
140
- if (!contractAddress || !owners || !chainId) {
141
- console.warn('[NftService] missing params for getFullNftInstancesByCollection');
135
+ async fetchFullListNftOfACollection(request) {
136
+ if (this.isReloading) {
142
137
  return false;
143
138
  }
144
139
  try {
145
- const nftDetectionApi = subwalletApiSdk.nftDetectionApi;
146
- if (!(nftDetectionApi !== null && nftDetectionApi !== void 0 && nftDetectionApi.getAllNftInstances)) {
147
- console.warn('[NftService] getAllNftInstances not available');
148
- return false;
149
- }
150
- const ownerList = Array.isArray(owners) ? owners : [owners];
151
- for (const eachOwner of ownerList) {
152
- try {
153
- const instances = await nftDetectionApi.getAllNftInstances(contractAddress, eachOwner, chainId.toString());
154
- if (!Array.isArray(instances)) {
155
- continue;
156
- }
157
- console.log('FOR TESTER (before)', instances);
158
- const nftList = instances.map(inst => mapSdkToNftItem(inst, chainInfo.slug, contractAddress, eachOwner)).filter(i => Boolean(i));
159
- console.log('FOR TESTER (after)', nftList);
160
- await this.state.handleDetectedNfts(eachOwner, nftList);
161
- } catch (innerErr) {
162
- console.warn(`[NftService] getAllNftInstances failed for ${eachOwner}`, innerErr);
163
- }
164
- }
140
+ const result = await this.multiChainFetcher.fetchFullListNftOfACollection(request);
141
+
142
+ // Persist DB
143
+ this.persistNftData({
144
+ items: result.items,
145
+ collections: result.collections
146
+ });
165
147
  return true;
166
- } catch (err) {
167
- console.error(`[NftDetectionService] getFullNftInstancesByCollection error for ${contractAddress}`, err);
148
+ } catch (e) {
149
+ console.error('[NftServiceV2] fetchFullListNftOfaCollection failed', e);
168
150
  return false;
169
151
  }
170
152
  }
153
+ async fetchNftDetail(request) {
154
+ if (this.isReloading) {
155
+ return null;
156
+ }
157
+ try {
158
+ const result = await this.multiChainFetcher.fetchNftDetail(request);
159
+ return result.items[0];
160
+ } catch (e) {
161
+ console.error('[NftServiceV2] fetchNftDetail failed', e);
162
+ return null;
163
+ }
164
+ }
165
+ startScanNft() {
166
+ this.stopScanNft();
167
+ const scanNft = () => {
168
+ if (!this.isStarted || this.isReloading) {
169
+ return;
170
+ }
171
+ this.refreshNftData().catch(console.error);
172
+ };
173
+ this._intervalFetchNft = setInterval(scanNft, this.NFT_INTERVAL_TIME);
174
+ }
175
+ stopScanNft() {
176
+ this._intervalFetchNft && clearInterval(this._intervalFetchNft);
177
+ this._intervalFetchNft = undefined;
178
+ }
179
+ persistNftData(result) {
180
+ try {
181
+ for (const item of result.items) {
182
+ const sender = keyring.getPair(item.owner);
183
+ this.state.updateNftData(item.chain, item, sender.address || item.owner);
184
+ }
185
+ for (const col of result.collections) {
186
+ this.state.setNftCollection(col.chain, col);
187
+ }
188
+ } catch (error) {
189
+ console.error('[NftServiceV2] Persist failed:', error);
190
+ }
191
+ }
192
+ async refreshNftData() {
193
+ if (this.isReloading) {
194
+ return;
195
+ }
196
+ this.isReloading = true;
197
+ try {
198
+ const addresses = this.state.keyringService.context.getDecodedAddresses();
199
+ const activeChains = Object.keys(this.state.getActiveChainInfoMap());
200
+ if (addresses.length === 0 || activeChains.length === 0) {
201
+ this.nftStateSubject.next(INITIAL_NFT_STATE);
202
+ return;
203
+ }
204
+ const result = await this.multiChainFetcher.fetch(addresses, activeChains);
205
+ this.persistNftData(result);
206
+ this.nftStateSubject.next({
207
+ nftData: {
208
+ total: result.items.length,
209
+ nftList: result.items
210
+ },
211
+ nftCollections: result.collections
212
+ });
213
+ } catch (error) {
214
+ console.error('[NftService] Refresh failed:', error);
215
+ this.nftStateSubject.next({
216
+ ...this.nftStateSubject.getValue()
217
+ });
218
+ } finally {
219
+ this.isReloading = false;
220
+ }
221
+ }
222
+
223
+ /** Subscribe NFT state */
224
+ subscribeNftItem() {
225
+ return this.nftState$;
226
+ }
227
+ subscribeNftCollection() {
228
+ const getChains = () => this.state.activeChainSlugs;
229
+ return this.state.dbService.stores.nftCollection.subscribeNftCollection(getChains);
230
+ }
231
+
232
+ // TODO: Move NFT reset logic to this function after migration is complete
233
+ async forceReload() {
234
+ this.isReloading = true;
235
+ await waitTimeout(1800);
236
+ this.isReloading = false;
237
+ await this.refreshNftData().catch(console.error);
238
+ }
171
239
  }
@@ -0,0 +1,13 @@
1
+ import { NftDetailRequest, NftFullListRequest } from '@subwallet/extension-base/background/KoniTypes';
2
+ import KoniState from '@subwallet/extension-base/koni/background/handlers/State';
3
+ import { NftHandlerResult } from './nft-handlers/base-nft-handler';
4
+ export declare class MultiChainNftFetcher {
5
+ private readonly state;
6
+ constructor(state: KoniState);
7
+ private handlerCache;
8
+ private getOrCreate;
9
+ private getHandlersForChain;
10
+ fetch(addresses: string[], chainSlugs: string[]): Promise<NftHandlerResult>;
11
+ fetchFullListNftOfACollection(request: NftFullListRequest): Promise<NftHandlerResult>;
12
+ fetchNftDetail(request: NftDetailRequest): Promise<NftHandlerResult>;
13
+ }
@@ -0,0 +1,138 @@
1
+ // Copyright 2019-2022 @subwallet/extension-koni authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { NFT_HANDLER_REGISTRY } from '@subwallet/extension-base/services/nft-service/nft-handlers/registry';
5
+ export class MultiChainNftFetcher {
6
+ constructor(state) {
7
+ this.state = state;
8
+ }
9
+ handlerCache = new Map();
10
+ getOrCreate(chain, desc) {
11
+ const key = `${chain}:${desc.id}`;
12
+ let handler = this.handlerCache.get(key);
13
+ if (!handler) {
14
+ handler = desc.create(chain, this.state);
15
+ this.handlerCache.set(key, handler);
16
+ }
17
+ return handler;
18
+ }
19
+ getHandlersForChain(chainSlug) {
20
+ const chainInfo = this.state.chainService.getChainInfoByKey(chainSlug);
21
+ if (!chainInfo) {
22
+ return [];
23
+ }
24
+ return NFT_HANDLER_REGISTRY.filter(d => d.supports(chainInfo)).map(d => this.getOrCreate(chainSlug, d));
25
+ }
26
+ async fetch(addresses, chainSlugs) {
27
+ const allItems = [];
28
+ const allCollections = [];
29
+ const tasks = [];
30
+ for (const chain of chainSlugs) {
31
+ const handlers = this.getHandlersForChain(chain);
32
+ if (handlers.length === 0) {
33
+ console.warn(`[NftFetcher] No handler for chain: ${chain}`);
34
+ continue;
35
+ }
36
+ for (const handler of handlers) {
37
+ const handlerAddresses = handler.filterAddresses(addresses);
38
+ const task = handler.fetchPreview(handlerAddresses).then(result => {
39
+ allItems.push(...result.items);
40
+ allCollections.push(...result.collections);
41
+ }).catch(err => {
42
+ console.error(`[NftFetcher] Handler failed on ${chain}: handler.id`, err);
43
+ });
44
+ tasks.push(task);
45
+ }
46
+ }
47
+ await Promise.all(tasks);
48
+
49
+ // DEDUPLICATE
50
+ // Todo: Move logic DEDUPLICATE to each handler
51
+ const seenItemIds = new Set();
52
+ const seenCollectionKeys = new Set();
53
+ const uniqueItems = [];
54
+ const uniqueCollections = [];
55
+ for (const item of allItems) {
56
+ if (!seenItemIds.has(item.id)) {
57
+ seenItemIds.add(item.id);
58
+ uniqueItems.push(item);
59
+ }
60
+ }
61
+ for (const col of allCollections) {
62
+ const key = `${col.chain}:${col.collectionId}`;
63
+ if (!seenCollectionKeys.has(key)) {
64
+ seenCollectionKeys.add(key);
65
+ uniqueCollections.push(col);
66
+ }
67
+ }
68
+ return {
69
+ items: uniqueItems,
70
+ collections: uniqueCollections
71
+ };
72
+ }
73
+ async fetchFullListNftOfACollection(request) {
74
+ const {
75
+ chainInfo,
76
+ collectionId,
77
+ owners,
78
+ tokenIds
79
+ } = request;
80
+ const items = [];
81
+ const collections = [];
82
+ const handlers = this.getHandlersForChain(chainInfo.slug);
83
+ for (const handler of handlers) {
84
+ // Todo: Improve the full-list fetch feature
85
+ // if (!handler.supportsFetchFullNftList) {
86
+ // continue;
87
+ // }
88
+
89
+ const handlerOwners = handler.filterAddresses(owners);
90
+ try {
91
+ const result = await handler.fetchFullListNftOfACollection({
92
+ collectionId: collectionId,
93
+ owners: handlerOwners,
94
+ tokenIds: tokenIds,
95
+ chainInfo
96
+ });
97
+ items.push(...result.items);
98
+ collections.push(...result.collections);
99
+ } catch (e) {
100
+ console.error('[NftFetcher] fetchCollection failed', e);
101
+ }
102
+ }
103
+ return {
104
+ items,
105
+ collections
106
+ };
107
+ }
108
+ async fetchNftDetail(request) {
109
+ const {
110
+ chainSlug,
111
+ collectionId,
112
+ tokenId
113
+ } = request;
114
+ const items = [];
115
+ const collections = [];
116
+ const handlers = this.getHandlersForChain(chainSlug);
117
+ for (const handler of handlers) {
118
+ // Todo: Improve the detail nft fetch feature
119
+ // if (!handler.supportsFetchFullNftList) {
120
+ // continue;
121
+ // }
122
+
123
+ try {
124
+ return await handler.fetchNftDetail({
125
+ collectionId: collectionId,
126
+ tokenId: tokenId,
127
+ chainSlug
128
+ });
129
+ } catch (e) {
130
+ console.error('[NftFetcher] fetchCollection failed', e);
131
+ }
132
+ }
133
+ return {
134
+ items,
135
+ collections
136
+ };
137
+ }
138
+ }
@@ -0,0 +1,13 @@
1
+ import { NftCollection, NftDetailRequest, NftFullListRequest, NftItem } from '@subwallet/extension-base/background/KoniTypes';
2
+ export interface NftHandlerResult {
3
+ items: NftItem[];
4
+ collections: NftCollection[];
5
+ }
6
+ export declare abstract class BaseNftHandler {
7
+ protected readonly chain: string;
8
+ constructor(chain: string);
9
+ abstract fetchPreview(addresses: string[]): Promise<NftHandlerResult>;
10
+ abstract filterAddresses(addresses: string[]): string[];
11
+ fetchFullListNftOfACollection(_request: NftFullListRequest): Promise<NftHandlerResult>;
12
+ fetchNftDetail(_request: NftDetailRequest): Promise<NftHandlerResult>;
13
+ }
@@ -0,0 +1,21 @@
1
+ // Copyright 2019-2022 @subwallet/extension-base authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ const EMPTY_NFT_RESULT = {
5
+ items: [],
6
+ collections: []
7
+ };
8
+ export class BaseNftHandler {
9
+ constructor(chain) {
10
+ this.chain = chain;
11
+ }
12
+ // Optional method - subclasses can choose to implement or not
13
+ fetchFullListNftOfACollection(_request) {
14
+ return Promise.resolve(EMPTY_NFT_RESULT);
15
+ }
16
+
17
+ // Optional method - subclasses can choose to implement or not
18
+ fetchNftDetail(_request) {
19
+ return Promise.resolve(EMPTY_NFT_RESULT);
20
+ }
21
+ }
@@ -0,0 +1,9 @@
1
+ import { NftFullListRequest } from '@subwallet/extension-base/background/KoniTypes';
2
+ import { BaseNftHandler, NftHandlerResult } from '../base-nft-handler';
3
+ export declare class EvmNftHandler extends BaseNftHandler {
4
+ filterAddresses(addresses: string[]): string[];
5
+ private mapSdkToNftItem;
6
+ private mapSdkToCollection;
7
+ fetchPreview(addresses: string[]): Promise<NftHandlerResult>;
8
+ fetchFullListNftOfACollection(request: NftFullListRequest): Promise<NftHandlerResult>;
9
+ }