@subwallet/extension-base 1.3.74-1 → 1.3.75-2
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/background/KoniTypes.d.ts +5 -0
- package/cjs/constants/environment.js +4 -2
- package/cjs/koni/background/handlers/Extension.js +49 -36
- package/cjs/koni/background/handlers/State.js +82 -0
- package/cjs/packageInfo.js +1 -1
- package/cjs/services/subscan-service/index.js +34 -6
- package/constants/environment.d.ts +1 -0
- package/constants/environment.js +2 -1
- package/koni/background/handlers/Extension.d.ts +2 -0
- package/koni/background/handlers/Extension.js +12 -0
- package/koni/background/handlers/State.d.ts +5 -0
- package/koni/background/handlers/State.js +82 -0
- package/package.json +5 -5
- package/packageInfo.js +1 -1
- package/services/subscan-service/index.d.ts +2 -0
- package/services/subscan-service/index.js +33 -5
|
@@ -397,6 +397,9 @@ export declare type RequestSaveBrowserConfig = {
|
|
|
397
397
|
export declare type RequestSaveOSConfig = {
|
|
398
398
|
osConfig: OSConfig;
|
|
399
399
|
};
|
|
400
|
+
export interface RequestSaveSubscanApiKey {
|
|
401
|
+
apiKey: string;
|
|
402
|
+
}
|
|
400
403
|
export interface RandomTestRequest {
|
|
401
404
|
start: number;
|
|
402
405
|
end: number;
|
|
@@ -1976,6 +1979,8 @@ export interface KoniRequestSignatures {
|
|
|
1976
1979
|
'pri(settings.saveAppConfig)': [RequestSaveAppConfig, boolean];
|
|
1977
1980
|
'pri(settings.saveBrowserConfig)': [RequestSaveBrowserConfig, boolean];
|
|
1978
1981
|
'pri(settings.saveOSConfig)': [RequestSaveOSConfig, boolean];
|
|
1982
|
+
'pri(settings.saveSubscanApiKey)': [RequestSaveSubscanApiKey, boolean];
|
|
1983
|
+
'pri(settings.getSubscanApiKey)': [null, string | null];
|
|
1979
1984
|
'pri(yield.subscribePoolInfo)': [null, YieldPoolInfo[], YieldPoolInfo[]];
|
|
1980
1985
|
'pri(yield.subscribeYieldPosition)': [null, YieldPositionInfo[], YieldPositionInfo[]];
|
|
1981
1986
|
'pri(yield.subscribeYieldReward)': [null, EarningRewardJson, EarningRewardJson];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.isProductionMode = exports.SW_EXTERNAL_SERVICES_API = exports.BACKEND_API_URL = exports.APP_VERSION = void 0;
|
|
6
|
+
exports.isProductionMode = exports.SW_EXTERNAL_SERVICES_API = exports.SUBSCAN_GATEWAY_URL = exports.BACKEND_API_URL = exports.APP_VERSION = void 0;
|
|
7
7
|
// Copyright 2019-2022 @subwallet/extension-base authors & contributors
|
|
8
8
|
// SPDX-License-Identifier: Apache-2.0
|
|
9
9
|
|
|
@@ -16,4 +16,6 @@ exports.isProductionMode = isProductionMode;
|
|
|
16
16
|
const BACKEND_API_URL = process.env.SUBWALLET_API || (isProductionMode ? 'https://sw-services.subwallet.app/api' : 'https://be-dev.subwallet.app/api');
|
|
17
17
|
exports.BACKEND_API_URL = BACKEND_API_URL;
|
|
18
18
|
const SW_EXTERNAL_SERVICES_API = process.env.SW_EXTERNAL_SERVICES_API || (isProductionMode ? 'https://external-services.subwallet.app' : 'https://external-services-dev.subwallet.app');
|
|
19
|
-
exports.SW_EXTERNAL_SERVICES_API = SW_EXTERNAL_SERVICES_API;
|
|
19
|
+
exports.SW_EXTERNAL_SERVICES_API = SW_EXTERNAL_SERVICES_API;
|
|
20
|
+
const SUBSCAN_GATEWAY_URL = process.env.SUBSCAN_GATEWAY_URL || 'https://gateway-dev.konistudio.xyz';
|
|
21
|
+
exports.SUBSCAN_GATEWAY_URL = SUBSCAN_GATEWAY_URL;
|
|
@@ -3884,14 +3884,23 @@ class KoniExtension {
|
|
|
3884
3884
|
this.#koniState.saveEnvConfig('osConfig', request.osConfig);
|
|
3885
3885
|
return true;
|
|
3886
3886
|
}
|
|
3887
|
+
async saveSubscanApiKey(_ref60) {
|
|
3888
|
+
let {
|
|
3889
|
+
apiKey
|
|
3890
|
+
} = _ref60;
|
|
3891
|
+
return await this.#koniState.saveSubscanApiKey(apiKey);
|
|
3892
|
+
}
|
|
3893
|
+
async getSubscanApiKey() {
|
|
3894
|
+
return await this.#koniState.getSubscanApiKey();
|
|
3895
|
+
}
|
|
3887
3896
|
|
|
3888
3897
|
/// Wallet connect
|
|
3889
3898
|
|
|
3890
3899
|
// Connect
|
|
3891
|
-
async connectWalletConnect(
|
|
3900
|
+
async connectWalletConnect(_ref61) {
|
|
3892
3901
|
let {
|
|
3893
3902
|
uri
|
|
3894
|
-
} =
|
|
3903
|
+
} = _ref61;
|
|
3895
3904
|
await this.#koniState.walletConnectService.connect(uri);
|
|
3896
3905
|
return true;
|
|
3897
3906
|
}
|
|
@@ -3904,11 +3913,11 @@ class KoniExtension {
|
|
|
3904
3913
|
});
|
|
3905
3914
|
return this.#koniState.requestService.allConnectWCRequests;
|
|
3906
3915
|
}
|
|
3907
|
-
async approveWalletConnectSession(
|
|
3916
|
+
async approveWalletConnectSession(_ref62) {
|
|
3908
3917
|
let {
|
|
3909
3918
|
accounts: selectedAccounts,
|
|
3910
3919
|
id
|
|
3911
|
-
} =
|
|
3920
|
+
} = _ref62;
|
|
3912
3921
|
const request = this.#koniState.requestService.getConnectWCRequest(id);
|
|
3913
3922
|
if ((0, _helpers2.isProposalExpired)(request.request.params)) {
|
|
3914
3923
|
throw new Error('The proposal has been expired');
|
|
@@ -3920,8 +3929,8 @@ class KoniExtension {
|
|
|
3920
3929
|
const availableNamespaces = {};
|
|
3921
3930
|
const namespaces = {};
|
|
3922
3931
|
const chainInfoMap = this.#koniState.getChainInfoMap();
|
|
3923
|
-
Object.entries(requiredNamespaces).forEach(
|
|
3924
|
-
let [key, namespace] =
|
|
3932
|
+
Object.entries(requiredNamespaces).forEach(_ref63 => {
|
|
3933
|
+
let [key, namespace] = _ref63;
|
|
3925
3934
|
if ((0, _helpers2.isSupportWalletConnectNamespace)(key)) {
|
|
3926
3935
|
if (namespace.chains) {
|
|
3927
3936
|
const unSupportChains = namespace.chains.filter(chain => !(0, _helpers2.isSupportWalletConnectChain)(chain, chainInfoMap));
|
|
@@ -3934,8 +3943,8 @@ class KoniExtension {
|
|
|
3934
3943
|
throw new Error((0, _utils1.getSdkError)('UNSUPPORTED_NAMESPACE_KEY').message + ' ' + key);
|
|
3935
3944
|
}
|
|
3936
3945
|
});
|
|
3937
|
-
Object.entries(optionalNamespaces).forEach(
|
|
3938
|
-
let [key, namespace] =
|
|
3946
|
+
Object.entries(optionalNamespaces).forEach(_ref64 => {
|
|
3947
|
+
let [key, namespace] = _ref64;
|
|
3939
3948
|
if ((0, _helpers2.isSupportWalletConnectNamespace)(key)) {
|
|
3940
3949
|
if (namespace.chains) {
|
|
3941
3950
|
const supportChains = namespace.chains.filter(chain => (0, _helpers2.isSupportWalletConnectChain)(chain, chainInfoMap)) || [];
|
|
@@ -3959,8 +3968,8 @@ class KoniExtension {
|
|
|
3959
3968
|
}
|
|
3960
3969
|
}
|
|
3961
3970
|
});
|
|
3962
|
-
Object.entries(availableNamespaces).forEach(
|
|
3963
|
-
let [key, namespace] =
|
|
3971
|
+
Object.entries(availableNamespaces).forEach(_ref65 => {
|
|
3972
|
+
let [key, namespace] = _ref65;
|
|
3964
3973
|
if (namespace.chains) {
|
|
3965
3974
|
const accounts = selectedAccounts.filter(address => {
|
|
3966
3975
|
const [_namespace] = address.split(':');
|
|
@@ -3984,10 +3993,10 @@ class KoniExtension {
|
|
|
3984
3993
|
request.resolve();
|
|
3985
3994
|
return true;
|
|
3986
3995
|
}
|
|
3987
|
-
async rejectWalletConnectSession(
|
|
3996
|
+
async rejectWalletConnectSession(_ref66) {
|
|
3988
3997
|
let {
|
|
3989
3998
|
id
|
|
3990
|
-
} =
|
|
3999
|
+
} = _ref66;
|
|
3991
4000
|
const request = this.#koniState.requestService.getConnectWCRequest(id);
|
|
3992
4001
|
const wcId = request.request.id;
|
|
3993
4002
|
if ((0, _helpers2.isProposalExpired)(request.request.params)) {
|
|
@@ -4009,10 +4018,10 @@ class KoniExtension {
|
|
|
4009
4018
|
});
|
|
4010
4019
|
return this.#koniState.walletConnectService.sessions;
|
|
4011
4020
|
}
|
|
4012
|
-
async disconnectWalletConnectSession(
|
|
4021
|
+
async disconnectWalletConnectSession(_ref67) {
|
|
4013
4022
|
let {
|
|
4014
4023
|
topic
|
|
4015
|
-
} =
|
|
4024
|
+
} = _ref67;
|
|
4016
4025
|
await this.#koniState.walletConnectService.disconnect(topic);
|
|
4017
4026
|
return true;
|
|
4018
4027
|
}
|
|
@@ -4025,18 +4034,18 @@ class KoniExtension {
|
|
|
4025
4034
|
});
|
|
4026
4035
|
return this.#koniState.requestService.allNotSupportWCRequests;
|
|
4027
4036
|
}
|
|
4028
|
-
approveWalletConnectNotSupport(
|
|
4037
|
+
approveWalletConnectNotSupport(_ref68) {
|
|
4029
4038
|
let {
|
|
4030
4039
|
id
|
|
4031
|
-
} =
|
|
4040
|
+
} = _ref68;
|
|
4032
4041
|
const request = this.#koniState.requestService.getNotSupportWCRequest(id);
|
|
4033
4042
|
request.resolve();
|
|
4034
4043
|
return true;
|
|
4035
4044
|
}
|
|
4036
|
-
rejectWalletConnectNotSupport(
|
|
4045
|
+
rejectWalletConnectNotSupport(_ref69) {
|
|
4037
4046
|
let {
|
|
4038
4047
|
id
|
|
4039
|
-
} =
|
|
4048
|
+
} = _ref69;
|
|
4040
4049
|
const request = this.#koniState.requestService.getNotSupportWCRequest(id);
|
|
4041
4050
|
request.reject(new Error('USER_REJECTED'));
|
|
4042
4051
|
return true;
|
|
@@ -4044,11 +4053,11 @@ class KoniExtension {
|
|
|
4044
4053
|
|
|
4045
4054
|
/// Manta
|
|
4046
4055
|
|
|
4047
|
-
async enableMantaPay(
|
|
4056
|
+
async enableMantaPay(_ref70) {
|
|
4048
4057
|
let {
|
|
4049
4058
|
address,
|
|
4050
4059
|
password
|
|
4051
|
-
} =
|
|
4060
|
+
} = _ref70;
|
|
4052
4061
|
// always takes the current account
|
|
4053
4062
|
function timeout() {
|
|
4054
4063
|
return new Promise(resolve => setTimeout(resolve, 1500));
|
|
@@ -4138,11 +4147,11 @@ class KoniExtension {
|
|
|
4138
4147
|
async disableMantaPay(address) {
|
|
4139
4148
|
return this.#koniState.disableMantaPay(address);
|
|
4140
4149
|
}
|
|
4141
|
-
async isTonBounceableAddress(
|
|
4150
|
+
async isTonBounceableAddress(_ref71) {
|
|
4142
4151
|
let {
|
|
4143
4152
|
address,
|
|
4144
4153
|
chain
|
|
4145
|
-
} =
|
|
4154
|
+
} = _ref71;
|
|
4146
4155
|
try {
|
|
4147
4156
|
const tonApi = this.#koniState.getTonApi(chain);
|
|
4148
4157
|
const state = await tonApi.getAccountState(address);
|
|
@@ -4188,10 +4197,10 @@ class KoniExtension {
|
|
|
4188
4197
|
|
|
4189
4198
|
/* Metadata */
|
|
4190
4199
|
|
|
4191
|
-
async findRawMetadata(
|
|
4200
|
+
async findRawMetadata(_ref72) {
|
|
4192
4201
|
let {
|
|
4193
4202
|
genesisHash
|
|
4194
|
-
} =
|
|
4203
|
+
} = _ref72;
|
|
4195
4204
|
const {
|
|
4196
4205
|
metadata,
|
|
4197
4206
|
specVersion,
|
|
@@ -4205,20 +4214,20 @@ class KoniExtension {
|
|
|
4205
4214
|
userExtensions
|
|
4206
4215
|
};
|
|
4207
4216
|
}
|
|
4208
|
-
async calculateMetadataHash(
|
|
4217
|
+
async calculateMetadataHash(_ref73) {
|
|
4209
4218
|
let {
|
|
4210
4219
|
chain
|
|
4211
|
-
} =
|
|
4220
|
+
} = _ref73;
|
|
4212
4221
|
const hash = await this.#koniState.calculateMetadataHash(chain);
|
|
4213
4222
|
return {
|
|
4214
4223
|
metadataHash: hash || ''
|
|
4215
4224
|
};
|
|
4216
4225
|
}
|
|
4217
|
-
async shortenMetadata(
|
|
4226
|
+
async shortenMetadata(_ref74) {
|
|
4218
4227
|
let {
|
|
4219
4228
|
chain,
|
|
4220
4229
|
txBlob
|
|
4221
|
-
} =
|
|
4230
|
+
} = _ref74;
|
|
4222
4231
|
const shorten = await this.#koniState.shortenMetadata(chain, txBlob);
|
|
4223
4232
|
return {
|
|
4224
4233
|
txMetadata: shorten || ''
|
|
@@ -4595,18 +4604,18 @@ class KoniExtension {
|
|
|
4595
4604
|
|
|
4596
4605
|
/* Campaign */
|
|
4597
4606
|
|
|
4598
|
-
unlockDotCheckCanMint(
|
|
4607
|
+
unlockDotCheckCanMint(_ref75) {
|
|
4599
4608
|
let {
|
|
4600
4609
|
address,
|
|
4601
4610
|
network,
|
|
4602
4611
|
slug
|
|
4603
|
-
} =
|
|
4612
|
+
} = _ref75;
|
|
4604
4613
|
return this.#koniState.mintCampaignService.unlockDotCampaign.canMint(address, slug, network);
|
|
4605
4614
|
}
|
|
4606
|
-
unlockDotSubscribeMintedData(id, port,
|
|
4615
|
+
unlockDotSubscribeMintedData(id, port, _ref76) {
|
|
4607
4616
|
let {
|
|
4608
4617
|
transactionId
|
|
4609
|
-
} =
|
|
4618
|
+
} = _ref76;
|
|
4610
4619
|
const cb = (0, _subscriptions.createSubscription)(id, port);
|
|
4611
4620
|
const subscription = this.#koniState.mintCampaignService.unlockDotCampaign.subscribeMintedNft(transactionId, cb);
|
|
4612
4621
|
this.createUnsubscriptionHandle(id, subscription.unsubscribe);
|
|
@@ -4638,10 +4647,10 @@ class KoniExtension {
|
|
|
4638
4647
|
});
|
|
4639
4648
|
return filterBanner(await this.#koniState.campaignService.getProcessingCampaign());
|
|
4640
4649
|
}
|
|
4641
|
-
async completeCampaignBanner(
|
|
4650
|
+
async completeCampaignBanner(_ref77) {
|
|
4642
4651
|
let {
|
|
4643
4652
|
slug
|
|
4644
|
-
} =
|
|
4653
|
+
} = _ref77;
|
|
4645
4654
|
const campaign = await this.#koniState.dbService.getCampaign(slug);
|
|
4646
4655
|
if (campaign) {
|
|
4647
4656
|
await this.#koniState.dbService.upsertCampaign({
|
|
@@ -5176,8 +5185,8 @@ class KoniExtension {
|
|
|
5176
5185
|
resolve();
|
|
5177
5186
|
}
|
|
5178
5187
|
};
|
|
5179
|
-
this.#koniState.balanceService.subscribeTransferableBalance(address, waitXcmData.chain, waitXcmData.token, waitXcmData.nextTxType, onRs).then(
|
|
5180
|
-
let [_unsub, rs] =
|
|
5188
|
+
this.#koniState.balanceService.subscribeTransferableBalance(address, waitXcmData.chain, waitXcmData.token, waitXcmData.nextTxType, onRs).then(_ref78 => {
|
|
5189
|
+
let [_unsub, rs] = _ref78;
|
|
5181
5190
|
unsub = _unsub;
|
|
5182
5191
|
onRs(rs);
|
|
5183
5192
|
}).catch(console.error);
|
|
@@ -5838,6 +5847,10 @@ class KoniExtension {
|
|
|
5838
5847
|
return this.saveBrowserConfig(request);
|
|
5839
5848
|
case 'pri(settings.saveOSConfig)':
|
|
5840
5849
|
return this.saveOSConfig(request);
|
|
5850
|
+
case 'pri(settings.saveSubscanApiKey)':
|
|
5851
|
+
return await this.saveSubscanApiKey(request);
|
|
5852
|
+
case 'pri(settings.getSubscanApiKey)':
|
|
5853
|
+
return await this.getSubscanApiKey();
|
|
5841
5854
|
|
|
5842
5855
|
/// Keyring state
|
|
5843
5856
|
case 'pri(keyring.subscribe)':
|
|
@@ -69,6 +69,8 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
69
69
|
// eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment
|
|
70
70
|
const passworder = require('browser-passworder');
|
|
71
71
|
const ERROR_CONFIRMATION_TYPE = ['errorConnectNetwork'];
|
|
72
|
+
const SUBSCAN_API_KEY_STORAGE = 'subscan_api_key';
|
|
73
|
+
const SUBSCAN_SECRET_STORAGE = 'subscan_secret';
|
|
72
74
|
|
|
73
75
|
// List of providers passed into constructor. This is the list of providers
|
|
74
76
|
// exposed by the extension.
|
|
@@ -228,6 +230,8 @@ class KoniState {
|
|
|
228
230
|
}
|
|
229
231
|
afterChainServiceInit() {
|
|
230
232
|
this.subscanService.setSubscanChainMap(this.chainService.getSubscanChainMap());
|
|
233
|
+
// Sync Subscan API key
|
|
234
|
+
this.syncSubscanApiKey().catch(console.error);
|
|
231
235
|
}
|
|
232
236
|
async init() {
|
|
233
237
|
await this.eventService.waitCryptoReady;
|
|
@@ -1612,6 +1616,84 @@ class KoniState {
|
|
|
1612
1616
|
return null;
|
|
1613
1617
|
}
|
|
1614
1618
|
}
|
|
1619
|
+
async getExtensionSecret() {
|
|
1620
|
+
const result = await _storage.SWStorage.instance.getItem(SUBSCAN_SECRET_STORAGE);
|
|
1621
|
+
let secret = result;
|
|
1622
|
+
if (!secret) {
|
|
1623
|
+
secret = crypto.randomUUID();
|
|
1624
|
+
await _storage.SWStorage.instance.setItem(SUBSCAN_SECRET_STORAGE, secret);
|
|
1625
|
+
}
|
|
1626
|
+
return secret;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// Generate password used to encrypt/decrypt Subscan API key
|
|
1630
|
+
// Password = extensionId + extension secret
|
|
1631
|
+
// -> Bind encrypted data to THIS extension instance
|
|
1632
|
+
// -> Prevent other extensions/apps from decrypting the stored API key
|
|
1633
|
+
async getSubscanApiCipherPassword() {
|
|
1634
|
+
var _chrome, _chrome$runtime;
|
|
1635
|
+
const secret = await this.getExtensionSecret();
|
|
1636
|
+
const extensionId = ((_chrome = chrome) === null || _chrome === void 0 ? void 0 : (_chrome$runtime = _chrome.runtime) === null || _chrome$runtime === void 0 ? void 0 : _chrome$runtime.id) || 'subwallet';
|
|
1637
|
+
return `${extensionId}:${secret}`;
|
|
1638
|
+
}
|
|
1639
|
+
async saveSubscanApiKey(apiKey) {
|
|
1640
|
+
try {
|
|
1641
|
+
// Get cipher password used for encryption
|
|
1642
|
+
const cipherPassword = await this.getSubscanApiCipherPassword();
|
|
1643
|
+
|
|
1644
|
+
// Encrypt API key before saving to storage
|
|
1645
|
+
// -> Avoid storing API key as plain text
|
|
1646
|
+
// -> Prevent leakage if storage is inspected
|
|
1647
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
|
|
1648
|
+
const encryptedData = await passworder.encrypt(cipherPassword, {
|
|
1649
|
+
apiKey
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
// Persist encrypted API key to extension storage
|
|
1653
|
+
await _storage.SWStorage.instance.setItem(SUBSCAN_API_KEY_STORAGE, JSON.stringify(encryptedData));
|
|
1654
|
+
|
|
1655
|
+
// Sync API key to Subscan service instance
|
|
1656
|
+
// -> Ensure API calls immediately use the latest key
|
|
1657
|
+
await this.syncSubscanApiKey();
|
|
1658
|
+
return true;
|
|
1659
|
+
} catch (e) {
|
|
1660
|
+
console.error(e);
|
|
1661
|
+
return false;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
async getSubscanApiKey() {
|
|
1665
|
+
try {
|
|
1666
|
+
// Read encrypted API key from storage
|
|
1667
|
+
const encryptedData = await _storage.SWStorage.instance.getItem(SUBSCAN_API_KEY_STORAGE);
|
|
1668
|
+
if (!encryptedData) {
|
|
1669
|
+
return null;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// Recreate cipher password for decryption
|
|
1673
|
+
const cipherPassword = await this.getSubscanApiCipherPassword();
|
|
1674
|
+
|
|
1675
|
+
// Decrypt stored API key
|
|
1676
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
|
|
1677
|
+
const decryptedData = await passworder.decrypt(cipherPassword, JSON.parse(encryptedData));
|
|
1678
|
+
|
|
1679
|
+
// Return API key if exists
|
|
1680
|
+
return decryptedData.apiKey || null;
|
|
1681
|
+
} catch (e) {
|
|
1682
|
+
console.error(e);
|
|
1683
|
+
return null;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// Sync decrypted API key to Subscan service
|
|
1688
|
+
// -> Allows service layer to use API key for authenticated requests
|
|
1689
|
+
async syncSubscanApiKey() {
|
|
1690
|
+
try {
|
|
1691
|
+
const apiKey = await this.getSubscanApiKey();
|
|
1692
|
+
this.subscanService.setApiKey(apiKey);
|
|
1693
|
+
} catch (e) {
|
|
1694
|
+
console.error('Failed to sync Subscan API key:', e);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1615
1697
|
onCheckToRemindUser() {
|
|
1616
1698
|
this.onHandleRemindExportAccount().catch(console.error);
|
|
1617
1699
|
}
|
package/cjs/packageInfo.js
CHANGED
|
@@ -5,7 +5,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.SubscanService = void 0;
|
|
7
7
|
var _SWError = require("@subwallet/extension-base/background/errors/SWError");
|
|
8
|
-
var _constants = require("@subwallet/extension-base/
|
|
8
|
+
var _constants = require("@subwallet/extension-base/constants");
|
|
9
|
+
var _constants2 = require("@subwallet/extension-base/koni/api/nft/ordinal_nft/constants");
|
|
9
10
|
var _subscanChainMap = require("@subwallet/extension-base/services/subscan-service/subscan-chain-map");
|
|
10
11
|
var _base = require("@subwallet/extension-base/strategy/api-request-strategy/context/base");
|
|
11
12
|
var _apiRequestStrategyV = require("@subwallet/extension-base/strategy/api-request-strategy-v2");
|
|
@@ -15,11 +16,15 @@ var _utils = require("@subwallet/extension-base/utils");
|
|
|
15
16
|
|
|
16
17
|
const QUERY_ROW = 100;
|
|
17
18
|
class SubscanService extends _apiRequestStrategyV.BaseApiRequestStrategyV2 {
|
|
19
|
+
apiKey = null;
|
|
18
20
|
constructor(subscanChainMap, options) {
|
|
19
21
|
const context = new _base.BaseApiRequestContext(options);
|
|
20
22
|
super(context);
|
|
21
23
|
this.subscanChainMap = subscanChainMap;
|
|
22
24
|
}
|
|
25
|
+
setApiKey(key) {
|
|
26
|
+
this.apiKey = key;
|
|
27
|
+
}
|
|
23
28
|
getApiUrl(chain, path) {
|
|
24
29
|
const subscanChain = this.subscanChainMap[chain];
|
|
25
30
|
if (!subscanChain) {
|
|
@@ -28,11 +33,26 @@ class SubscanService extends _apiRequestStrategyV.BaseApiRequestStrategyV2 {
|
|
|
28
33
|
return `https://${subscanChain}.api.subscan.io/${path}`;
|
|
29
34
|
}
|
|
30
35
|
postRequest(url, body) {
|
|
36
|
+
const parsed = new URL(url);
|
|
37
|
+
const headers = {
|
|
38
|
+
'Content-Type': 'application/json'
|
|
39
|
+
};
|
|
40
|
+
if (this.apiKey) {
|
|
41
|
+
headers['X-API-Key'] = this.apiKey;
|
|
42
|
+
}
|
|
43
|
+
if (_utils.targetIsWeb) {
|
|
44
|
+
const suffix = '.api.subscan.io';
|
|
45
|
+
const subscanChain = parsed.hostname.endsWith(suffix) ? parsed.hostname.slice(0, -suffix.length) : parsed.hostname;
|
|
46
|
+
headers['x-network'] = subscanChain;
|
|
47
|
+
return fetch(`${_constants.SUBSCAN_GATEWAY_URL}${parsed.pathname}${parsed.search}`, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers,
|
|
50
|
+
body: JSON.stringify(body)
|
|
51
|
+
});
|
|
52
|
+
}
|
|
31
53
|
return fetch(url, {
|
|
32
54
|
method: 'POST',
|
|
33
|
-
headers
|
|
34
|
-
'Content-Type': 'application/json'
|
|
35
|
-
},
|
|
55
|
+
headers,
|
|
36
56
|
body: JSON.stringify(body)
|
|
37
57
|
});
|
|
38
58
|
}
|
|
@@ -244,7 +264,7 @@ class SubscanService extends _apiRequestStrategyV.BaseApiRequestStrategyV2 {
|
|
|
244
264
|
getAccountRemarkEvents(groupId, chain, address) {
|
|
245
265
|
return this.addRequest(async () => {
|
|
246
266
|
const rs = await this.postRequest(this.getApiUrl(chain, 'api/v2/scan/events'), {
|
|
247
|
-
...
|
|
267
|
+
..._constants2.BASE_FETCH_ORDINAL_EVENT_DATA,
|
|
248
268
|
address
|
|
249
269
|
});
|
|
250
270
|
if (rs.status !== 200) {
|
|
@@ -272,7 +292,15 @@ class SubscanService extends _apiRequestStrategyV.BaseApiRequestStrategyV2 {
|
|
|
272
292
|
|
|
273
293
|
static getInstance() {
|
|
274
294
|
if (!SubscanService._instance) {
|
|
275
|
-
|
|
295
|
+
// Subscan API allows only ~2 requests per second.
|
|
296
|
+
// However, each request from the webapp also triggers an OPTIONS request (CORS preflight),
|
|
297
|
+
// which Subscan counts towards the quota as well → effectively 1 call = 2 requests.
|
|
298
|
+
// To avoid hitting the rate limit, we configure the queue
|
|
299
|
+
// to allow only 1 request per second.
|
|
300
|
+
SubscanService._instance = new SubscanService(_subscanChainMap.SUBSCAN_API_CHAIN_MAP, {
|
|
301
|
+
limitRate: 1,
|
|
302
|
+
intervalCheck: 1000
|
|
303
|
+
});
|
|
276
304
|
}
|
|
277
305
|
return SubscanService._instance;
|
|
278
306
|
}
|
package/constants/environment.js
CHANGED
|
@@ -6,4 +6,5 @@ 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 SW_EXTERNAL_SERVICES_API = process.env.SW_EXTERNAL_SERVICES_API || (isProductionMode ? 'https://external-services.subwallet.app' : 'https://external-services-dev.subwallet.app');
|
|
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');
|
|
10
|
+
export const SUBSCAN_GATEWAY_URL = process.env.SUBSCAN_GATEWAY_URL || 'https://gateway-dev.konistudio.xyz';
|
|
@@ -225,6 +225,8 @@ export default class KoniExtension {
|
|
|
225
225
|
private saveAppConfig;
|
|
226
226
|
private saveBrowserConfig;
|
|
227
227
|
private saveOSConfig;
|
|
228
|
+
private saveSubscanApiKey;
|
|
229
|
+
private getSubscanApiKey;
|
|
228
230
|
private connectWalletConnect;
|
|
229
231
|
private connectWCSubscribe;
|
|
230
232
|
private approveWalletConnectSession;
|
|
@@ -3806,6 +3806,14 @@ export default class KoniExtension {
|
|
|
3806
3806
|
this.#koniState.saveEnvConfig('osConfig', request.osConfig);
|
|
3807
3807
|
return true;
|
|
3808
3808
|
}
|
|
3809
|
+
async saveSubscanApiKey({
|
|
3810
|
+
apiKey
|
|
3811
|
+
}) {
|
|
3812
|
+
return await this.#koniState.saveSubscanApiKey(apiKey);
|
|
3813
|
+
}
|
|
3814
|
+
async getSubscanApiKey() {
|
|
3815
|
+
return await this.#koniState.getSubscanApiKey();
|
|
3816
|
+
}
|
|
3809
3817
|
|
|
3810
3818
|
/// Wallet connect
|
|
3811
3819
|
|
|
@@ -5741,6 +5749,10 @@ export default class KoniExtension {
|
|
|
5741
5749
|
return this.saveBrowserConfig(request);
|
|
5742
5750
|
case 'pri(settings.saveOSConfig)':
|
|
5743
5751
|
return this.saveOSConfig(request);
|
|
5752
|
+
case 'pri(settings.saveSubscanApiKey)':
|
|
5753
|
+
return await this.saveSubscanApiKey(request);
|
|
5754
|
+
case 'pri(settings.getSubscanApiKey)':
|
|
5755
|
+
return await this.getSubscanApiKey();
|
|
5744
5756
|
|
|
5745
5757
|
/// Keyring state
|
|
5746
5758
|
case 'pri(keyring.subscribe)':
|
|
@@ -252,6 +252,11 @@ export default class KoniState {
|
|
|
252
252
|
private onHandleRemindExportAccount;
|
|
253
253
|
setStorageFromWS({ key, value }: StorageDataInterface): Promise<boolean>;
|
|
254
254
|
getStorageFromWS(key: string): Promise<string | null>;
|
|
255
|
+
private getExtensionSecret;
|
|
256
|
+
private getSubscanApiCipherPassword;
|
|
257
|
+
saveSubscanApiKey(apiKey: string): Promise<boolean>;
|
|
258
|
+
getSubscanApiKey(): Promise<string | null>;
|
|
259
|
+
private syncSubscanApiKey;
|
|
255
260
|
onCheckToRemindUser(): void;
|
|
256
261
|
onInstall(): void;
|
|
257
262
|
get activeNetworks(): Record<string, _ChainInfo>;
|
|
@@ -62,6 +62,8 @@ import { KoniSubscription } from "../subscription.js";
|
|
|
62
62
|
// eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment
|
|
63
63
|
const passworder = require('browser-passworder');
|
|
64
64
|
const ERROR_CONFIRMATION_TYPE = ['errorConnectNetwork'];
|
|
65
|
+
const SUBSCAN_API_KEY_STORAGE = 'subscan_api_key';
|
|
66
|
+
const SUBSCAN_SECRET_STORAGE = 'subscan_secret';
|
|
65
67
|
|
|
66
68
|
// List of providers passed into constructor. This is the list of providers
|
|
67
69
|
// exposed by the extension.
|
|
@@ -219,6 +221,8 @@ export default class KoniState {
|
|
|
219
221
|
}
|
|
220
222
|
afterChainServiceInit() {
|
|
221
223
|
this.subscanService.setSubscanChainMap(this.chainService.getSubscanChainMap());
|
|
224
|
+
// Sync Subscan API key
|
|
225
|
+
this.syncSubscanApiKey().catch(console.error);
|
|
222
226
|
}
|
|
223
227
|
async init() {
|
|
224
228
|
await this.eventService.waitCryptoReady;
|
|
@@ -1581,6 +1585,84 @@ export default class KoniState {
|
|
|
1581
1585
|
return null;
|
|
1582
1586
|
}
|
|
1583
1587
|
}
|
|
1588
|
+
async getExtensionSecret() {
|
|
1589
|
+
const result = await SWStorage.instance.getItem(SUBSCAN_SECRET_STORAGE);
|
|
1590
|
+
let secret = result;
|
|
1591
|
+
if (!secret) {
|
|
1592
|
+
secret = crypto.randomUUID();
|
|
1593
|
+
await SWStorage.instance.setItem(SUBSCAN_SECRET_STORAGE, secret);
|
|
1594
|
+
}
|
|
1595
|
+
return secret;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// Generate password used to encrypt/decrypt Subscan API key
|
|
1599
|
+
// Password = extensionId + extension secret
|
|
1600
|
+
// -> Bind encrypted data to THIS extension instance
|
|
1601
|
+
// -> Prevent other extensions/apps from decrypting the stored API key
|
|
1602
|
+
async getSubscanApiCipherPassword() {
|
|
1603
|
+
var _chrome, _chrome$runtime;
|
|
1604
|
+
const secret = await this.getExtensionSecret();
|
|
1605
|
+
const extensionId = ((_chrome = chrome) === null || _chrome === void 0 ? void 0 : (_chrome$runtime = _chrome.runtime) === null || _chrome$runtime === void 0 ? void 0 : _chrome$runtime.id) || 'subwallet';
|
|
1606
|
+
return `${extensionId}:${secret}`;
|
|
1607
|
+
}
|
|
1608
|
+
async saveSubscanApiKey(apiKey) {
|
|
1609
|
+
try {
|
|
1610
|
+
// Get cipher password used for encryption
|
|
1611
|
+
const cipherPassword = await this.getSubscanApiCipherPassword();
|
|
1612
|
+
|
|
1613
|
+
// Encrypt API key before saving to storage
|
|
1614
|
+
// -> Avoid storing API key as plain text
|
|
1615
|
+
// -> Prevent leakage if storage is inspected
|
|
1616
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
|
|
1617
|
+
const encryptedData = await passworder.encrypt(cipherPassword, {
|
|
1618
|
+
apiKey
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
// Persist encrypted API key to extension storage
|
|
1622
|
+
await SWStorage.instance.setItem(SUBSCAN_API_KEY_STORAGE, JSON.stringify(encryptedData));
|
|
1623
|
+
|
|
1624
|
+
// Sync API key to Subscan service instance
|
|
1625
|
+
// -> Ensure API calls immediately use the latest key
|
|
1626
|
+
await this.syncSubscanApiKey();
|
|
1627
|
+
return true;
|
|
1628
|
+
} catch (e) {
|
|
1629
|
+
console.error(e);
|
|
1630
|
+
return false;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
async getSubscanApiKey() {
|
|
1634
|
+
try {
|
|
1635
|
+
// Read encrypted API key from storage
|
|
1636
|
+
const encryptedData = await SWStorage.instance.getItem(SUBSCAN_API_KEY_STORAGE);
|
|
1637
|
+
if (!encryptedData) {
|
|
1638
|
+
return null;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// Recreate cipher password for decryption
|
|
1642
|
+
const cipherPassword = await this.getSubscanApiCipherPassword();
|
|
1643
|
+
|
|
1644
|
+
// Decrypt stored API key
|
|
1645
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
|
|
1646
|
+
const decryptedData = await passworder.decrypt(cipherPassword, JSON.parse(encryptedData));
|
|
1647
|
+
|
|
1648
|
+
// Return API key if exists
|
|
1649
|
+
return decryptedData.apiKey || null;
|
|
1650
|
+
} catch (e) {
|
|
1651
|
+
console.error(e);
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// Sync decrypted API key to Subscan service
|
|
1657
|
+
// -> Allows service layer to use API key for authenticated requests
|
|
1658
|
+
async syncSubscanApiKey() {
|
|
1659
|
+
try {
|
|
1660
|
+
const apiKey = await this.getSubscanApiKey();
|
|
1661
|
+
this.subscanService.setApiKey(apiKey);
|
|
1662
|
+
} catch (e) {
|
|
1663
|
+
console.error('Failed to sync Subscan API key:', e);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1584
1666
|
onCheckToRemindUser() {
|
|
1585
1667
|
this.onHandleRemindExportAccount().catch(console.error);
|
|
1586
1668
|
}
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"./cjs/detectPackage.js"
|
|
18
18
|
],
|
|
19
19
|
"type": "module",
|
|
20
|
-
"version": "1.3.
|
|
20
|
+
"version": "1.3.75-2",
|
|
21
21
|
"main": "./cjs/index.js",
|
|
22
22
|
"module": "./index.js",
|
|
23
23
|
"types": "./index.d.ts",
|
|
@@ -3009,10 +3009,10 @@
|
|
|
3009
3009
|
"@substrate/connect": "^0.8.9",
|
|
3010
3010
|
"@subwallet-monorepos/subwallet-services-sdk": "0.1.16",
|
|
3011
3011
|
"@subwallet/chain-list": "0.2.124",
|
|
3012
|
-
"@subwallet/extension-base": "^1.3.
|
|
3013
|
-
"@subwallet/extension-chains": "^1.3.
|
|
3014
|
-
"@subwallet/extension-dapp": "^1.3.
|
|
3015
|
-
"@subwallet/extension-inject": "^1.3.
|
|
3012
|
+
"@subwallet/extension-base": "^1.3.75-2",
|
|
3013
|
+
"@subwallet/extension-chains": "^1.3.75-2",
|
|
3014
|
+
"@subwallet/extension-dapp": "^1.3.75-2",
|
|
3015
|
+
"@subwallet/extension-inject": "^1.3.75-2",
|
|
3016
3016
|
"@subwallet/keyring": "^0.1.14",
|
|
3017
3017
|
"@subwallet/ui-keyring": "^0.1.14",
|
|
3018
3018
|
"@ton/core": "^0.56.3",
|
package/packageInfo.js
CHANGED
|
@@ -7,5 +7,5 @@ export const packageInfo = {
|
|
|
7
7
|
name: '@subwallet/extension-base',
|
|
8
8
|
path: (import.meta && import.meta.url) ? new URL(import.meta.url).pathname.substring(0, new URL(import.meta.url).pathname.lastIndexOf('/') + 1) : 'auto',
|
|
9
9
|
type: 'esm',
|
|
10
|
-
version: '1.3.
|
|
10
|
+
version: '1.3.75-2'
|
|
11
11
|
};
|
|
@@ -4,7 +4,9 @@ import { BaseApiRequestStrategyV2 } from '@subwallet/extension-base/strategy/api
|
|
|
4
4
|
import { SubscanEventBaseItemData, SubscanExtrinsicParam } from '@subwallet/extension-base/types';
|
|
5
5
|
export declare class SubscanService extends BaseApiRequestStrategyV2 {
|
|
6
6
|
private subscanChainMap;
|
|
7
|
+
private apiKey;
|
|
7
8
|
constructor(subscanChainMap: Record<string, string>, options?: Partial<ApiRequestContextProps>);
|
|
9
|
+
setApiKey(key: string | null): void;
|
|
8
10
|
private getApiUrl;
|
|
9
11
|
private postRequest;
|
|
10
12
|
isRateLimited(e: Error): boolean;
|
|
@@ -2,18 +2,23 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import { SWError } from '@subwallet/extension-base/background/errors/SWError';
|
|
5
|
+
import { SUBSCAN_GATEWAY_URL } from '@subwallet/extension-base/constants';
|
|
5
6
|
import { BASE_FETCH_ORDINAL_EVENT_DATA } from '@subwallet/extension-base/koni/api/nft/ordinal_nft/constants';
|
|
6
7
|
import { SUBSCAN_API_CHAIN_MAP } from '@subwallet/extension-base/services/subscan-service/subscan-chain-map';
|
|
7
8
|
import { BaseApiRequestContext } from '@subwallet/extension-base/strategy/api-request-strategy/context/base';
|
|
8
9
|
import { BaseApiRequestStrategyV2 } from '@subwallet/extension-base/strategy/api-request-strategy-v2';
|
|
9
|
-
import { wait } from '@subwallet/extension-base/utils';
|
|
10
|
+
import { targetIsWeb, wait } from '@subwallet/extension-base/utils';
|
|
10
11
|
const QUERY_ROW = 100;
|
|
11
12
|
export class SubscanService extends BaseApiRequestStrategyV2 {
|
|
13
|
+
apiKey = null;
|
|
12
14
|
constructor(subscanChainMap, options) {
|
|
13
15
|
const context = new BaseApiRequestContext(options);
|
|
14
16
|
super(context);
|
|
15
17
|
this.subscanChainMap = subscanChainMap;
|
|
16
18
|
}
|
|
19
|
+
setApiKey(key) {
|
|
20
|
+
this.apiKey = key;
|
|
21
|
+
}
|
|
17
22
|
getApiUrl(chain, path) {
|
|
18
23
|
const subscanChain = this.subscanChainMap[chain];
|
|
19
24
|
if (!subscanChain) {
|
|
@@ -22,11 +27,26 @@ export class SubscanService extends BaseApiRequestStrategyV2 {
|
|
|
22
27
|
return `https://${subscanChain}.api.subscan.io/${path}`;
|
|
23
28
|
}
|
|
24
29
|
postRequest(url, body) {
|
|
30
|
+
const parsed = new URL(url);
|
|
31
|
+
const headers = {
|
|
32
|
+
'Content-Type': 'application/json'
|
|
33
|
+
};
|
|
34
|
+
if (this.apiKey) {
|
|
35
|
+
headers['X-API-Key'] = this.apiKey;
|
|
36
|
+
}
|
|
37
|
+
if (targetIsWeb) {
|
|
38
|
+
const suffix = '.api.subscan.io';
|
|
39
|
+
const subscanChain = parsed.hostname.endsWith(suffix) ? parsed.hostname.slice(0, -suffix.length) : parsed.hostname;
|
|
40
|
+
headers['x-network'] = subscanChain;
|
|
41
|
+
return fetch(`${SUBSCAN_GATEWAY_URL}${parsed.pathname}${parsed.search}`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers,
|
|
44
|
+
body: JSON.stringify(body)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
25
47
|
return fetch(url, {
|
|
26
48
|
method: 'POST',
|
|
27
|
-
headers
|
|
28
|
-
'Content-Type': 'application/json'
|
|
29
|
-
},
|
|
49
|
+
headers,
|
|
30
50
|
body: JSON.stringify(body)
|
|
31
51
|
});
|
|
32
52
|
}
|
|
@@ -256,7 +276,15 @@ export class SubscanService extends BaseApiRequestStrategyV2 {
|
|
|
256
276
|
|
|
257
277
|
static getInstance() {
|
|
258
278
|
if (!SubscanService._instance) {
|
|
259
|
-
|
|
279
|
+
// Subscan API allows only ~2 requests per second.
|
|
280
|
+
// However, each request from the webapp also triggers an OPTIONS request (CORS preflight),
|
|
281
|
+
// which Subscan counts towards the quota as well → effectively 1 call = 2 requests.
|
|
282
|
+
// To avoid hitting the rate limit, we configure the queue
|
|
283
|
+
// to allow only 1 request per second.
|
|
284
|
+
SubscanService._instance = new SubscanService(SUBSCAN_API_CHAIN_MAP, {
|
|
285
|
+
limitRate: 1,
|
|
286
|
+
intervalCheck: 1000
|
|
287
|
+
});
|
|
260
288
|
}
|
|
261
289
|
return SubscanService._instance;
|
|
262
290
|
}
|