@subwallet/extension-base 1.3.67-0 → 1.3.68-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.
Files changed (57) hide show
  1. package/background/KoniTypes.d.ts +12 -1
  2. package/cjs/constants/environment.js +1 -3
  3. package/cjs/constants/index.js +4 -1
  4. package/cjs/core/substrate/system-pallet.js +4 -0
  5. package/cjs/koni/api/nft/rari/index.js +1 -1
  6. package/cjs/koni/background/cron.js +16 -0
  7. package/cjs/koni/background/handlers/Extension.js +165 -89
  8. package/cjs/koni/background/handlers/State.js +25 -0
  9. package/cjs/packageInfo.js +1 -1
  10. package/cjs/services/balance-service/helpers/group.js +31 -2
  11. package/cjs/services/balance-service/helpers/subscribe/substrate/index.js +51 -13
  12. package/cjs/services/balance-service/helpers/subscribe/substrate/utils.js +69 -0
  13. package/cjs/services/balance-service/index.js +36 -11
  14. package/cjs/services/balance-service/transfer/smart-contract.js +2 -0
  15. package/cjs/services/chain-service/constants.js +8 -46
  16. package/cjs/services/chain-service/handler/EvmChainHandler.js +6 -3
  17. package/cjs/services/earning-service/handlers/base.js +7 -1
  18. package/cjs/services/nft-service/index.js +173 -0
  19. package/cjs/services/transaction-service/index.js +1 -1
  20. package/cjs/types/balance/index.js +26 -1
  21. package/cjs/utils/index.js +25 -2
  22. package/cjs/utils/setup-api-sdk.js +0 -5
  23. package/constants/environment.d.ts +0 -1
  24. package/constants/environment.js +0 -1
  25. package/constants/index.d.ts +1 -0
  26. package/constants/index.js +1 -0
  27. package/core/substrate/system-pallet.d.ts +1 -0
  28. package/core/substrate/system-pallet.js +3 -0
  29. package/core/substrate/types.d.ts +14 -0
  30. package/koni/api/nft/rari/index.js +1 -1
  31. package/koni/background/cron.d.ts +1 -0
  32. package/koni/background/cron.js +17 -1
  33. package/koni/background/handlers/Extension.d.ts +3 -0
  34. package/koni/background/handlers/Extension.js +87 -13
  35. package/koni/background/handlers/State.d.ts +4 -0
  36. package/koni/background/handlers/State.js +25 -0
  37. package/package.json +16 -6
  38. package/packageInfo.js +1 -1
  39. package/services/balance-service/helpers/group.js +31 -2
  40. package/services/balance-service/helpers/subscribe/substrate/index.js +51 -13
  41. package/services/balance-service/helpers/subscribe/substrate/utils.d.ts +7 -0
  42. package/services/balance-service/helpers/subscribe/substrate/utils.js +58 -0
  43. package/services/balance-service/index.d.ts +4 -2
  44. package/services/balance-service/index.js +26 -6
  45. package/services/balance-service/transfer/smart-contract.js +2 -0
  46. package/services/chain-service/constants.d.ts +5 -24
  47. package/services/chain-service/constants.js +6 -35
  48. package/services/chain-service/handler/EvmChainHandler.js +6 -3
  49. package/services/earning-service/handlers/base.js +7 -1
  50. package/services/nft-service/index.d.ts +9 -0
  51. package/services/nft-service/index.js +165 -0
  52. package/services/transaction-service/index.js +1 -1
  53. package/services/transaction-service/types.d.ts +2 -1
  54. package/types/balance/index.d.ts +14 -0
  55. package/types/balance/index.js +21 -1
  56. package/utils/index.js +25 -2
  57. package/utils/setup-api-sdk.js +1 -6
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _types = require("@subwallet/chain-list/types");
9
+ var _utils = require("@subwallet/extension-base/services/chain-service/utils");
10
+ var _utils2 = require("@subwallet/extension-base/utils");
11
+ var _keyring = require("@subwallet/keyring");
12
+ var _types2 = require("@subwallet/keyring/types");
13
+ var _subwalletServicesSdk = _interopRequireDefault(require("@subwallet-monorepos/subwallet-services-sdk"));
14
+ // Copyright 2019-2022 @subwallet/extension-base authors & contributors
15
+ // SPDX-License-Identifier: Apache-2.0
16
+
17
+ function mapSdkToNftItem(rawInstance, chain, collectionId, owner) {
18
+ var _rawInstance$token_ty, _rawInstance$token_ty2, _rawInstance$id;
19
+ const metadata = rawInstance.metadata || {};
20
+ const image = metadata.image || rawInstance.image_url || rawInstance.media_url || '';
21
+ const attributes = Array.isArray(metadata.attributes) ? metadata.attributes : [];
22
+ let rarity;
23
+ const properties = {};
24
+ for (const attr of attributes) {
25
+ try {
26
+ var _attr$trait_type;
27
+ const key = (_attr$trait_type = attr.trait_type) === null || _attr$trait_type === void 0 ? void 0 : _attr$trait_type.trim();
28
+ if (!key) {
29
+ continue;
30
+ }
31
+ let value = attr.value;
32
+ if (typeof value === 'string') {
33
+ const lower = value.toLowerCase();
34
+ if (lower === 'true') {
35
+ value = true;
36
+ } else if (lower === 'false') {
37
+ value = false;
38
+ }
39
+ }
40
+ properties[key] = value;
41
+ if (key.toLowerCase() === 'rarity') {
42
+ rarity = String(value);
43
+ }
44
+ } catch {}
45
+ }
46
+ const hasProperties = Object.keys(properties).length > 0;
47
+ 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();
48
+
49
+ // Only support ERC721
50
+ if (normalizedType !== 'ERC721') {
51
+ return null;
52
+ }
53
+ return {
54
+ id: (_rawInstance$id = rawInstance.id) === null || _rawInstance$id === void 0 ? void 0 : _rawInstance$id.toString(),
55
+ chain,
56
+ collectionId,
57
+ owner: rawInstance.owner || owner,
58
+ originAsset: undefined,
59
+ name: metadata.name || `#${rawInstance.id}`,
60
+ image: (0, _utils2.baseParseIPFSUrl)(image),
61
+ externalUrl: rawInstance.external_app_url || undefined,
62
+ rarity,
63
+ description: metadata.description || undefined,
64
+ properties: hasProperties ? properties : null,
65
+ type: normalizedType === 'ERC721' ? _types._AssetType.ERC721 : _types._AssetType.ERC721,
66
+ // currently only support ERC721
67
+ rmrk_ver: undefined,
68
+ onChainOption: undefined,
69
+ assetHubType: undefined
70
+ };
71
+ }
72
+ function mapSdkToCollection(raw, chain) {
73
+ var _raw$token_instances;
74
+ const token = raw.token || {};
75
+ return {
76
+ // must-have
77
+ collectionId: token.address_hash,
78
+ chain,
79
+ originAsset: undefined,
80
+ // optional
81
+ collectionName: token.name || token.symbol || 'Unknown Collection',
82
+ image: token.icon_url || undefined,
83
+ itemCount: Number(raw.amount) || ((_raw$token_instances = raw.token_instances) === null || _raw$token_instances === void 0 ? void 0 : _raw$token_instances.length) || 0,
84
+ externalUrl: undefined
85
+ };
86
+ }
87
+ class NftService {
88
+ inProgress = new Set();
89
+ constructor(state) {
90
+ this.state = state;
91
+ }
92
+ async fetchEvmCollectionsWithPreview(addresses) {
93
+ for (const address of addresses) {
94
+ const type = (0, _keyring.getKeypairTypeByAddress)(address);
95
+ const typeValid = [..._types2.EthereumKeypairTypes].includes(type);
96
+ if (typeValid) {
97
+ if (this.inProgress.has(address)) {
98
+ console.log(`[NftService] ${address} already running`);
99
+ continue;
100
+ }
101
+ this.inProgress.add(address);
102
+ try {
103
+ const nftDetectionApi = _subwalletServicesSdk.default.nftDetectionApi;
104
+ if (!(nftDetectionApi !== null && nftDetectionApi !== void 0 && nftDetectionApi.getEvmNftCollectionsByAddress)) {
105
+ console.warn('[NftService] NftDetectionApi not available');
106
+ continue;
107
+ }
108
+ const rawData = await nftDetectionApi.getEvmNftCollectionsByAddress(address);
109
+ const allItems = [];
110
+ const allCollections = [];
111
+ for (const [chain, collections] of Object.entries(rawData)) {
112
+ if (!Array.isArray(collections)) {
113
+ continue;
114
+ }
115
+ for (const col of collections) {
116
+ const mappedCollection = mapSdkToCollection(col, chain);
117
+ allCollections.push(mappedCollection);
118
+ if (Array.isArray(col.token_instances)) {
119
+ const items = col.token_instances.map(inst => mapSdkToNftItem(inst, chain, mappedCollection.collectionId, address)).filter(i => Boolean(i));
120
+ allItems.push(...items);
121
+ }
122
+ }
123
+ }
124
+ await this.state.handleDetectedNftCollections(allCollections);
125
+ await this.state.handleDetectedNfts(address, allItems);
126
+ } catch (err) {
127
+ console.warn(`[NftService] detect error for ${address}`, err);
128
+ } finally {
129
+ this.inProgress.delete(address);
130
+ }
131
+ }
132
+ }
133
+ }
134
+ async getFullNftInstancesByCollection(request) {
135
+ const {
136
+ chainInfo,
137
+ contractAddress,
138
+ owners
139
+ } = request;
140
+ const chainId = (0, _utils._getEvmChainId)(chainInfo);
141
+ if (!contractAddress || !owners || !chainId) {
142
+ console.warn('[NftService] missing params for getFullNftInstancesByCollection');
143
+ return false;
144
+ }
145
+ try {
146
+ const nftDetectionApi = _subwalletServicesSdk.default.nftDetectionApi;
147
+ if (!(nftDetectionApi !== null && nftDetectionApi !== void 0 && nftDetectionApi.getAllNftInstances)) {
148
+ console.warn('[NftService] getAllNftInstances not available');
149
+ return false;
150
+ }
151
+ const ownerList = Array.isArray(owners) ? owners : [owners];
152
+ for (const eachOwner of ownerList) {
153
+ try {
154
+ const instances = await nftDetectionApi.getAllNftInstances(contractAddress, eachOwner, chainId.toString());
155
+ if (!Array.isArray(instances)) {
156
+ continue;
157
+ }
158
+ console.log('FOR TESTER (before)', instances);
159
+ const nftList = instances.map(inst => mapSdkToNftItem(inst, chainInfo.slug, contractAddress, eachOwner)).filter(i => Boolean(i));
160
+ console.log('FOR TESTER (after)', nftList);
161
+ await this.state.handleDetectedNfts(eachOwner, nftList);
162
+ } catch (innerErr) {
163
+ console.warn(`[NftService] getAllNftInstances failed for ${eachOwner}`, innerErr);
164
+ }
165
+ }
166
+ return true;
167
+ } catch (err) {
168
+ console.error(`[NftDetectionService] getFullNftInstancesByCollection error for ${contractAddress}`, err);
169
+ return false;
170
+ }
171
+ }
172
+ }
173
+ exports.default = NftService;
@@ -134,7 +134,7 @@ class TransactionService {
134
134
  // Check account signing transaction
135
135
 
136
136
  (0, _transfer.checkSigningAccountForTransaction)(validationResponse, chainInfoMap);
137
- const nativeTokenAvailable = await this.state.balanceService.getTransferableBalance(address, chain, nativeTokenInfo.slug, extrinsicType);
137
+ const nativeTokenAvailable = await this.state.balanceService.getBalanceByType(address, chain, nativeTokenInfo.slug, transactionInput.balanceType, extrinsicType);
138
138
 
139
139
  // Check available balance against transaction fee
140
140
  (0, _transfer.checkBalanceWithTransactionFee)(validationResponse, transactionInput, nativeTokenInfo, nativeTokenAvailable);
@@ -1 +1,26 @@
1
- "use strict";
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.BalanceType = void 0;
7
+ // Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors
8
+ // SPDX-License-Identifier: Apache-2.0
9
+ let BalanceType;
10
+ /**
11
+ * Balance info of a token on an address
12
+ * @property {string} address - Address
13
+ * @property {string} tokenSlug - Slug of token
14
+ * @property {APIItemState} state - State of information
15
+ * @property {number} [timestamp] - Time to get information
16
+ * @property {string} free - Transferable balance
17
+ * @property {string} locked - Locked balance, cannot be transferred, locked here is only meaningful in the context of token transfer
18
+ * @property {metadata} [metadata] - Could be anything, supposed to be generic to handle various contexts
19
+ */
20
+ exports.BalanceType = BalanceType;
21
+ (function (BalanceType) {
22
+ BalanceType["TRANSFERABLE"] = "transferable";
23
+ BalanceType["TOTAL"] = "total";
24
+ BalanceType["TOTAL_MINUS_RESERVED"] = "totalMinusReserved";
25
+ BalanceType["KEEP_ALIVE"] = "keepAlive";
26
+ })(BalanceType || (exports.BalanceType = BalanceType = {}));
@@ -632,13 +632,36 @@ const stripUrl = url => {
632
632
  };
633
633
  exports.stripUrl = stripUrl;
634
634
  const baseParseIPFSUrl = (input, customDomain) => {
635
- const selectedDomain = customDomain || (0, _config.getRandomIpfsGateway)();
636
635
  if (!input || input.length === 0) {
637
636
  return undefined;
638
637
  }
639
- if (isUrl(input)) {
638
+
639
+ // Case 1: Return as-is for inline data URLs (e.g. base64-encoded images)
640
+ if (input.startsWith('data:')) {
640
641
  return input;
641
642
  }
643
+ const selectedDomain = customDomain || (0, _config.getRandomIpfsGateway)();
644
+
645
+ // Case 2: Replace Pinata private gateways with a public IPFS gateway
646
+ // ==== EX: https://ikzttp.mypinata.cloud/ipfs/QmYDvPAXtiJg7s8JdRBSLWdgSphQdac8j1YuQNNxcGE1hg/9586.png
647
+ // Case 2b: Blockscout IPFS debug gateway -> rewrite to public gateway
648
+ // ==== EX: http://ipfs-debug.node.blockscout.com/ipfs/QmX2qHy1o27KgmHJSG2wKj2qLiv1gMCJCTn4nxzEVtTdgF
649
+ // Case 2c: http://ipfs.node.blockscout.com/ipfs/QmeTETrnJcG3iowfT3tXtz2jKmyeYbeag3AeYfDk5pBjGg
650
+
651
+ const privateGatewayPattern = /https?:\/\/([^/]*pinata\.cloud|[^/]*\.node\.blockscout\.com)\/ipfs\//;
652
+ if (privateGatewayPattern.test(input)) {
653
+ return input.replace(privateGatewayPattern, selectedDomain);
654
+ }
655
+
656
+ // Case 3: Handle NFT.storage subdomain links (e.g. https://<cid>.ipfs.nftstorage.link/...)
657
+ // Always redirect to selectedDomain to avoid SSL version/cipher mismatch errors
658
+ // ==== EX: https://bafybeias6as7k66hkghst3w4jwk6x5dkfk56oglqh44x6jmok6n7kcvg7m.ipfs.nftstorage.link/0.gif?ext=gif
659
+ const nftStorageMatch = input.match(/^https?:\/\/([a-zA-Z0-9]+)\.ipfs\.nftstorage\.link\/(.*)$/);
660
+ if (nftStorageMatch) {
661
+ const cid = nftStorageMatch[1];
662
+ const pathAndQuery = nftStorageMatch[2] || '';
663
+ return `${selectedDomain}${cid}/${pathAndQuery}`;
664
+ }
642
665
  if (isUrl(input) || input.includes('https://') || input.includes('http')) {
643
666
  return input;
644
667
  }
@@ -19,9 +19,4 @@ function setupApiSDK() {
19
19
  platform: _environment.TARGET_ENV,
20
20
  chainListVersion: CHAIN_LIST_VERSION
21
21
  });
22
-
23
- // Custom the price history API with other different base URL
24
- _subwalletServicesSdk.default.priceHistoryApi.updateConfig({
25
- baseUrl: _constants.BACKEND_PRICE_HISTORY_URL
26
- });
27
22
  }
@@ -1,5 +1,4 @@
1
1
  export declare const APP_VERSION: string;
2
2
  export declare const isProductionMode: boolean;
3
3
  export declare const BACKEND_API_URL: string;
4
- export declare const BACKEND_PRICE_HISTORY_URL: string;
5
4
  export declare const SW_EXTERNAL_SERVICES_API: string;
@@ -6,5 +6,4 @@ const branchName = process.env.BRANCH_NAME || 'subwallet-dev';
6
6
  export const APP_VERSION = process.env.PKG_VERSION || '';
7
7
  export const isProductionMode = PRODUCTION_BRANCHES.indexOf(branchName) > -1;
8
8
  export const BACKEND_API_URL = process.env.SUBWALLET_API || (isProductionMode ? 'https://sw-services.subwallet.app/api' : 'https://be-dev.subwallet.app/api');
9
- export const BACKEND_PRICE_HISTORY_URL = process.env.SUBWALLET_PRICE_HISTORY_API || (isProductionMode ? 'https://price-history.subwallet.app/api' : 'https://price-history-dev.subwallet.app/api');
10
9
  export const SW_EXTERNAL_SERVICES_API = process.env.SW_EXTERNAL_SERVICES_API || (isProductionMode ? 'https://external-services.subwallet.app' : 'https://external-services-dev.subwallet.app');
@@ -15,6 +15,7 @@ export declare const CRON_REFRESH_STAKING_REWARD_FAST_INTERVAL = 90000;
15
15
  export declare const CRON_REFRESH_HISTORY_INTERVAL = 900000;
16
16
  export declare const CRON_GET_API_MAP_STATUS = 10000;
17
17
  export declare const CRON_REFRESH_CHAIN_STAKING_METADATA = 90000;
18
+ export declare const CRON_NFT_DETECT_INTERVAL = 7500000;
18
19
  export declare const CRON_REFRESH_CHAIN_NOMINATOR_METADATA = 1800000;
19
20
  export declare const CRON_RECOVER_HISTORY_INTERVAL = 30000;
20
21
  export declare const CRON_SYNC_MANTA_PAY = 300000;
@@ -17,6 +17,7 @@ export const CRON_REFRESH_STAKING_REWARD_FAST_INTERVAL = 90000;
17
17
  export const CRON_REFRESH_HISTORY_INTERVAL = 900000;
18
18
  export const CRON_GET_API_MAP_STATUS = 10000;
19
19
  export const CRON_REFRESH_CHAIN_STAKING_METADATA = 90000;
20
+ export const CRON_NFT_DETECT_INTERVAL = 7500000;
20
21
  export const CRON_REFRESH_CHAIN_NOMINATOR_METADATA = 1800000;
21
22
  export const CRON_RECOVER_HISTORY_INTERVAL = 30000;
22
23
  export const CRON_SYNC_MANTA_PAY = 300000;
@@ -5,3 +5,4 @@ export declare function _canAccountBeReaped(accountInfo: FrameSystemAccountInfo)
5
5
  export declare function _isAccountActive(accountInfo: FrameSystemAccountInfo): boolean;
6
6
  export declare function _getSystemPalletTotalBalance(accountInfo: FrameSystemAccountInfo): bigint;
7
7
  export declare function _getAppliedExistentialDepositWithExtrinsicType(accountInfo: FrameSystemAccountInfo, existentialDeposit: string, extrinsicType?: ExtrinsicType): bigint;
8
+ export declare function _getSystemPalletReservedBalance(accountInfo: FrameSystemAccountInfo): bigint;
@@ -64,4 +64,7 @@ function _getSystemPalletTransferableV1(accountInfo, existentialDeposit, strictM
64
64
  }
65
65
  function _getSystemPalletTotalBalanceV1(accountInfo) {
66
66
  return BigInt(accountInfo.data.free) + BigInt(accountInfo.data.reserved);
67
+ }
68
+ export function _getSystemPalletReservedBalance(accountInfo) {
69
+ return BigInt(accountInfo.data.reserved);
67
70
  }
@@ -18,6 +18,20 @@ export declare type FrameSystemAccountInfoV1 = {
18
18
  feeFrozen: number;
19
19
  };
20
20
  };
21
+ export interface FrameBalancesLocksInfo {
22
+ id: string | Record<string, unknown>;
23
+ amount: string | number | bigint;
24
+ reasons?: string;
25
+ }
26
+ export interface FrameBalancesHoldsInfo {
27
+ id: string | Record<string, unknown>;
28
+ amount: string | number | bigint;
29
+ reason?: string;
30
+ }
31
+ export interface FrameBalancesFreezesInfo {
32
+ id: string | Record<string, unknown>;
33
+ amount: string;
34
+ }
21
35
  export declare type FrameSystemAccountInfo = FrameSystemAccountInfoV1 | FrameSystemAccountInfoV2;
22
36
  export declare type OrmlTokensAccountData = {
23
37
  free: number;
@@ -6,7 +6,7 @@ const options = {
6
6
  method: 'GET',
7
7
  headers: {
8
8
  accept: 'application/json',
9
- 'X-API-KEY': 'ed9df6bf-7eba-4ca2-8a42-9006706be064'
9
+ 'X-API-KEY': 'e5c3b30b-f241-47f6-88b7-7859837ff17c'
10
10
  }
11
11
  };
12
12
  export class RariNftApi extends BaseNftApi {
@@ -29,6 +29,7 @@ export declare class KoniCron {
29
29
  refreshNft: (address: string, apiMap: ApiMap, smartContractNfts: _ChainAsset[], chainInfoMap: Record<string, _ChainInfo>) => () => void;
30
30
  resetNft: (newAddress: string) => void;
31
31
  checkNetworkAvailable: (serviceInfo: ServiceInfo) => boolean;
32
+ detectEvmCollectionNft: (address: string) => () => void;
32
33
  reloadNft(): Promise<boolean>;
33
34
  reloadStaking(): Promise<boolean>;
34
35
  private needUpdateNft;
@@ -1,7 +1,7 @@
1
1
  // Copyright 2019-2022 @subwallet/extension-koni authors & contributors
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { CRON_REFRESH_CHAIN_STAKING_METADATA, CRON_REFRESH_MKT_CAMPAIGN_INTERVAL, CRON_REFRESH_NFT_INTERVAL, CRON_SYNC_MANTA_PAY } from '@subwallet/extension-base/constants';
4
+ import { CRON_NFT_DETECT_INTERVAL, CRON_REFRESH_CHAIN_STAKING_METADATA, CRON_REFRESH_MKT_CAMPAIGN_INTERVAL, CRON_REFRESH_NFT_INTERVAL, CRON_SYNC_MANTA_PAY } from '@subwallet/extension-base/constants';
5
5
  import { _isChainSupportEvmNft, _isChainSupportNativeNft, _isChainSupportWasmNft } from '@subwallet/extension-base/services/chain-service/utils';
6
6
  import { waitTimeout } from '@subwallet/extension-base/utils';
7
7
  import { Subject } from 'rxjs';
@@ -88,6 +88,7 @@ export class KoniCron {
88
88
  // NFT
89
89
  (commonReload || needUpdateNft) && this.resetNft(address);
90
90
  (commonReload || needUpdateNft) && this.removeCron('refreshNft');
91
+ (commonReload || needUpdateNft) && this.removeCron('detectNft');
91
92
  commonReload && this.removeCron('refreshPoolingStakingReward');
92
93
  if (mktCampaignNeedReload) {
93
94
  this.removeCron('fetchMktCampaignData');
@@ -103,6 +104,7 @@ export class KoniCron {
103
104
  if (this.checkNetworkAvailable(serviceInfo)) {
104
105
  // only add cron jobs if there's at least 1 active network
105
106
  (commonReload || needUpdateNft) && this.addCron('refreshNft', this.refreshNft(address, serviceInfo.chainApiMap, this.state.getSmartContractNfts(), this.state.getActiveChainInfoMap()), CRON_REFRESH_NFT_INTERVAL);
107
+ (commonReload || needUpdateNft) && this.addCron('detectNft', this.detectEvmCollectionNft(address), CRON_NFT_DETECT_INTERVAL);
106
108
  reloadMantaPay && this.addCron('syncMantaPay', this.syncMantaPay, CRON_SYNC_MANTA_PAY);
107
109
  }
108
110
  };
@@ -115,6 +117,7 @@ export class KoniCron {
115
117
  if (Object.keys(this.state.getSubstrateApiMap()).length !== 0 || Object.keys(this.state.getEvmApiMap()).length !== 0) {
116
118
  this.resetNft(currentAccountInfo.proxyId);
117
119
  this.addCron('refreshNft', this.refreshNft(currentAccountInfo.proxyId, this.state.getApiMap(), this.state.getSmartContractNfts(), this.state.getActiveChainInfoMap()), CRON_REFRESH_NFT_INTERVAL);
120
+ this.addCron('detectNft', this.detectEvmCollectionNft(currentAccountInfo.proxyId), CRON_NFT_DETECT_INTERVAL);
118
121
  // this.addCron('refreshStakingReward', this.refreshStakingReward(currentAccountInfo.address), CRON_REFRESH_STAKING_REWARD_INTERVAL);
119
122
  this.addCron('syncMantaPay', this.syncMantaPay, CRON_SYNC_MANTA_PAY);
120
123
  }
@@ -164,12 +167,25 @@ export class KoniCron {
164
167
  checkNetworkAvailable = serviceInfo => {
165
168
  return Object.keys(serviceInfo.chainApiMap.substrate).length > 0 || Object.keys(serviceInfo.chainApiMap.evm).length > 0;
166
169
  };
170
+ detectEvmCollectionNft = address => {
171
+ return () => {
172
+ let addresses = [];
173
+ addresses = this.state.keyringService.context.getDecodedAddresses();
174
+ if (!addresses.length) {
175
+ console.warn('[Cron] No decoded addresses found for ALL_ACCOUNT_KEY');
176
+ return;
177
+ }
178
+ this.state.nftDetectionService.fetchEvmCollectionsWithPreview(addresses).catch(err => console.warn(`[Cron] NFT detection failed for ${address}:`, err));
179
+ };
180
+ };
167
181
  async reloadNft() {
168
182
  const address = this.state.keyringService.context.currentAccount.proxyId;
169
183
  const serviceInfo = this.state.getServiceInfo();
170
184
  this.resetNft(address);
171
185
  this.removeCron('refreshNft');
186
+ this.removeCron('detectNft');
172
187
  this.addCron('refreshNft', this.refreshNft(address, serviceInfo.chainApiMap, this.state.getSmartContractNfts(), this.state.getActiveChainInfoMap()), CRON_REFRESH_NFT_INTERVAL);
188
+ this.addCron('detectNft', this.detectEvmCollectionNft(address), CRON_NFT_DETECT_INTERVAL);
173
189
  await waitTimeout(1800);
174
190
  return true;
175
191
  }
@@ -116,6 +116,7 @@ export default class KoniExtension {
116
116
  private subscribeNftCollection;
117
117
  private getNft;
118
118
  private subscribeNft;
119
+ private handleGetNftFullList;
119
120
  private getStakingReward;
120
121
  private subscribeStakingReward;
121
122
  private getStaking;
@@ -145,10 +146,12 @@ export default class KoniExtension {
145
146
  private deleteCustomAsset;
146
147
  private validateCustomAsset;
147
148
  private getAddressTransferableBalance;
149
+ private getAddressAvailableBalanceByType;
148
150
  private getAddressTotalBalance;
149
151
  private subscribeMaxTransferable;
150
152
  private subscribeTransferableWhenConfirmation;
151
153
  private subscribeAddressTransferableBalance;
154
+ private subscribeAddressAvailableBalanceByType;
152
155
  private substrateNftSubmitTransaction;
153
156
  private enableChains;
154
157
  private accountsCreateExternalV2;
@@ -47,7 +47,7 @@ import { DEFAULT_AUTO_LOCK_TIME } from '@subwallet/extension-base/services/setti
47
47
  import { isProposalExpired, isSupportWalletConnectChain, isSupportWalletConnectNamespace } from '@subwallet/extension-base/services/wallet-connect-service/helpers';
48
48
  import { SWStorage } from '@subwallet/extension-base/storage';
49
49
  import { AccountsStore } from '@subwallet/extension-base/stores';
50
- import { AccountChainType, AccountSignMode, BasicTxErrorType, BasicTxWarningCode, CommonStepType, EarningProcessType, ProcessType, StakingTxErrorType, StepStatus, SwapFeeType, YieldPoolType, YieldStepType } from '@subwallet/extension-base/types';
50
+ import { AccountChainType, AccountSignMode, BalanceType, BasicTxErrorType, BasicTxWarningCode, CommonStepType, EarningProcessType, ProcessType, StakingTxErrorType, StepStatus, SwapFeeType, YieldPoolType, YieldStepType } from '@subwallet/extension-base/types';
51
51
  import { _analyzeAddress, calculateMaxTransferable, combineAllAccountProxy, combineBitcoinFee, createPromiseHandler, createTransactionFromRLP, detectTransferTxType, filterUneconomicalUtxos, getAccountSignMode, getSizeInfo, getTransferableBitcoinUtxos, isSameAddress, isSubstrateEcdsaLedgerAssetSupported, MODULE_SUPPORT, reformatAddress, signatureToHex, transformAccounts, transformAddresses, uniqueStringArray } from '@subwallet/extension-base/utils';
52
52
  import { parseContractInput, parseEvmRlp } from '@subwallet/extension-base/utils/eth/parseTransaction';
53
53
  import { getId } from '@subwallet/extension-base/utils/getId';
@@ -1144,6 +1144,9 @@ export default class KoniExtension {
1144
1144
  });
1145
1145
  return this.getNft();
1146
1146
  }
1147
+ async handleGetNftFullList(request) {
1148
+ return this.#koniState.nftDetectionService.getFullNftInstancesByCollection(request);
1149
+ }
1147
1150
  getStakingReward() {
1148
1151
  return new Promise((resolve, reject) => {
1149
1152
  this.#koniState.getStakingReward(rs => {
@@ -2033,20 +2036,40 @@ export default class KoniExtension {
2033
2036
  var _data$metadata;
2034
2037
  const evmApi = this.#koniState.getEvmApi(data.originChain);
2035
2038
  const contractAddress = (_data$metadata = data.metadata) === null || _data$metadata === void 0 ? void 0 : _data$metadata.contractAddress;
2036
-
2037
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
2038
2039
  const tokenContract = new evmApi.api.eth.Contract(_ERC721_ABI, contractAddress);
2039
2040
  try {
2040
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
2041
- await tokenContract.methods.tokenOfOwnerByIndex('0xB7fdD27a8Df011816205a6e3cAA097DC4D8C2C5d', 1).call();
2042
- return true;
2043
- } catch (err) {
2044
- const error = err;
2045
- if (error.message.includes('index out of bounds')) {
2046
- return true;
2047
- } else {
2041
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
2042
+ const supports1155 = await tokenContract.methods.supportsInterface('0xd9b67a26').call().catch(() => false);
2043
+ if (supports1155) {
2048
2044
  return false;
2049
2045
  }
2046
+
2047
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
2048
+ const supports721 = await tokenContract.methods.supportsInterface('0x80ac58cd').call().catch(() => false);
2049
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
2050
+ const supportsMetadata = await tokenContract.methods.supportsInterface('0x5b5e139f').call().catch(() => false);
2051
+ if (supports721 || supportsMetadata) {
2052
+ return true;
2053
+ }
2054
+
2055
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
2056
+ if (tokenContract.methods.ownerOf) {
2057
+ try {
2058
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
2059
+ await tokenContract.methods.ownerOf(1).call();
2060
+ return true;
2061
+ } catch (err) {
2062
+ var _err$message, _err$message2;
2063
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
2064
+ if ((_err$message = err.message) !== null && _err$message !== void 0 && _err$message.includes('nonexistent token') || (_err$message2 = err.message) !== null && _err$message2 !== void 0 && _err$message2.includes('invalid token ID')) {
2065
+ return true;
2066
+ }
2067
+ }
2068
+ }
2069
+ return false;
2070
+ } catch (err) {
2071
+ const error = err;
2072
+ return error.message.includes('index out of bounds');
2050
2073
  }
2051
2074
  }
2052
2075
  async upsertCustomToken(data) {
@@ -2102,6 +2125,21 @@ export default class KoniExtension {
2102
2125
  }
2103
2126
  return await this.#koniState.balanceService.getTransferableBalance(address, networkKey, token, extrinsicType);
2104
2127
  }
2128
+ async getAddressAvailableBalanceByType({
2129
+ address,
2130
+ balanceType,
2131
+ extrinsicType,
2132
+ networkKey,
2133
+ token
2134
+ }) {
2135
+ if (token && _MANTA_ZK_CHAIN_GROUP.includes(networkKey)) {
2136
+ const tokenInfo = this.#koniState.chainService.getAssetBySlug(token);
2137
+ if (tokenInfo.symbol.startsWith(_ZK_ASSET_PREFIX)) {
2138
+ return await this.#koniState.getMantaPayZkBalance(address, tokenInfo);
2139
+ }
2140
+ }
2141
+ return await this.#koniState.balanceService.getBalanceByType(address, networkKey, token, balanceType, extrinsicType);
2142
+ }
2105
2143
  async getAddressTotalBalance({
2106
2144
  address,
2107
2145
  extrinsicType,
@@ -2173,7 +2211,7 @@ export default class KoniExtension {
2173
2211
  });
2174
2212
  const [unsubBalance, freeBalance] = await (async () => {
2175
2213
  try {
2176
- return await this.#koniState.balanceService.subscribeBalance(address, chain, token, 'transferable', extrinsicType, data => {
2214
+ return await this.#koniState.balanceService.subscribeBalance(address, chain, token, BalanceType.TRANSFERABLE, extrinsicType, data => {
2177
2215
  freeBalanceSubject.next(data); // Must be called after subscription
2178
2216
  });
2179
2217
  } catch (e) {
@@ -2311,7 +2349,7 @@ export default class KoniExtension {
2311
2349
  convertData(freeBalance, fee, _feeOptions, feeCustom).then(cb).catch(console.error);
2312
2350
  }
2313
2351
  });
2314
- const [unsubBalance, freeBalance] = await this.#koniState.balanceService.subscribeBalance(address, chain, token, 'transferable', ExtrinsicType.TRANSFER_BALANCE, data => {
2352
+ const [unsubBalance, freeBalance] = await this.#koniState.balanceService.subscribeBalance(address, chain, token, BalanceType.TRANSFERABLE, ExtrinsicType.TRANSFER_BALANCE, data => {
2315
2353
  freeBalanceSubject.next(data); // Must be called after subscription
2316
2354
  });
2317
2355
 
@@ -2354,6 +2392,31 @@ export default class KoniExtension {
2354
2392
  });
2355
2393
  return convertData(currentFreeBalance);
2356
2394
  }
2395
+ async subscribeAddressAvailableBalanceByType({
2396
+ address,
2397
+ balanceType,
2398
+ extrinsicType,
2399
+ networkKey,
2400
+ token
2401
+ }, id, port) {
2402
+ const cb = createSubscription(id, port);
2403
+ const convertData = data => {
2404
+ return {
2405
+ ...data,
2406
+ id
2407
+ };
2408
+ };
2409
+ const _cb = data => {
2410
+ // eslint-disable-next-line node/no-callback-literal
2411
+ cb(convertData(data));
2412
+ };
2413
+ const [unsub, currentFreeBalance] = await this.#koniState.balanceService.subscribeBalanceByType(address, networkKey, token, balanceType, extrinsicType, _cb);
2414
+ this.createUnsubscriptionHandle(id, unsub);
2415
+ port.onDisconnect.addListener(() => {
2416
+ this.cancelSubscription(id);
2417
+ });
2418
+ return convertData(currentFreeBalance);
2419
+ }
2357
2420
  async substrateNftSubmitTransaction(inputData) {
2358
2421
  const {
2359
2422
  params,
@@ -3799,6 +3862,10 @@ export default class KoniExtension {
3799
3862
  });
3800
3863
  }
3801
3864
  }
3865
+ let balanceTypeForPool = BalanceType.TRANSFERABLE;
3866
+ if (!!poolHandler && [YieldPoolType.NATIVE_STAKING, YieldPoolType.NOMINATION_POOL].includes(poolHandler.type)) {
3867
+ balanceTypeForPool = BalanceType.TOTAL_MINUS_RESERVED;
3868
+ }
3802
3869
  return await this.#koniState.transactionService.handleTransaction({
3803
3870
  address,
3804
3871
  chain: txChain,
@@ -3810,6 +3877,7 @@ export default class KoniExtension {
3810
3877
  chainType,
3811
3878
  resolveOnDone: !isLastStep,
3812
3879
  transferNativeAmount,
3880
+ balanceType: balanceTypeForPool,
3813
3881
  skipFeeValidation: isMintingStep && isPoolSupportAlternativeFee,
3814
3882
  errorOnTimeOut,
3815
3883
  ...this.createPassConfirmationParams(isPassConfirmation),
@@ -4807,6 +4875,8 @@ export default class KoniExtension {
4807
4875
  return await this.getNftCollection();
4808
4876
  case 'pri(nftCollection.getSubscription)':
4809
4877
  return await this.subscribeNftCollection(id, port);
4878
+ case 'pri(nft.getFullList)':
4879
+ return await this.handleGetNftFullList(request);
4810
4880
  case 'pri(staking.getStaking)':
4811
4881
  return this.getStaking();
4812
4882
  case 'pri(staking.getSubscription)':
@@ -5015,6 +5085,10 @@ export default class KoniExtension {
5015
5085
  return this.getAddressTransferableBalance(request);
5016
5086
  case 'pri(freeBalance.subscribe)':
5017
5087
  return this.subscribeAddressTransferableBalance(request, id, port);
5088
+ case 'pri(availableBalance.getBalanceByType)':
5089
+ return this.getAddressAvailableBalanceByType(request);
5090
+ case 'pri(availableBalance.subscribeBalanceByType)':
5091
+ return this.subscribeAddressAvailableBalanceByType(request, id, port);
5018
5092
  case 'pri(subscription.cancel)':
5019
5093
  return this.cancelSubscription(request);
5020
5094
  case 'pri(chainService.recoverSubstrateApi)':
@@ -19,6 +19,7 @@ import { KeyringService } from '@subwallet/extension-base/services/keyring-servi
19
19
  import MigrationService from '@subwallet/extension-base/services/migration-service';
20
20
  import MintCampaignService from '@subwallet/extension-base/services/mint-campaign-service';
21
21
  import MktCampaignService from '@subwallet/extension-base/services/mkt-campaign-service';
22
+ import NftService from '@subwallet/extension-base/services/nft-service';
22
23
  import NotificationService from '@subwallet/extension-base/services/notification-service/NotificationService';
23
24
  import { PriceService } from '@subwallet/extension-base/services/price-service';
24
25
  import RequestService from '@subwallet/extension-base/services/request-service';
@@ -73,6 +74,7 @@ export default class KoniState {
73
74
  readonly mintCampaignService: MintCampaignService;
74
75
  readonly campaignService: CampaignService;
75
76
  readonly mktCampaignService: MktCampaignService;
77
+ readonly nftDetectionService: NftService;
76
78
  readonly buyService: BuyService;
77
79
  readonly earningService: EarningService;
78
80
  readonly feeService: FeeService;
@@ -133,6 +135,8 @@ export default class KoniState {
133
135
  resetNft(newAddress: string): void;
134
136
  updateNftData(network: string, nftData: NftItem, address: string, callback?: (nftData: NftItem) => void): void;
135
137
  deleteNftCollection(chain: string, collectionId: string): Promise<void>;
138
+ handleDetectedNfts(address: string, nftItems: NftItem[]): Promise<void>;
139
+ handleDetectedNftCollections(collections: NftCollection[]): Promise<void>;
136
140
  cleanUpNfts(chain: string, owner: string, collectionId: string[], nftIds: string[], ownNothing?: boolean): void;
137
141
  getNft(): Promise<NftJson | undefined>;
138
142
  subscribeNft(): Subject<NftJson>;