@subwallet/extension-base 1.2.13-0 → 1.2.14-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 (49) hide show
  1. package/background/errors/SwapError.js +5 -1
  2. package/cjs/background/errors/SwapError.js +5 -1
  3. package/cjs/core/logic-validation/swap.js +56 -3
  4. package/cjs/koni/api/nft/config.js +28 -18
  5. package/cjs/packageInfo.js +1 -1
  6. package/cjs/services/balance-service/transfer/xcm/utils.js +2 -2
  7. package/cjs/services/chain-service/constants.js +1 -1
  8. package/cjs/services/chain-service/handler/SubstrateApi.js +6 -0
  9. package/cjs/services/chain-service/index.js +17 -4
  10. package/cjs/services/migration-service/scripts/databases/ReloadMetadata.js +35 -0
  11. package/cjs/services/migration-service/scripts/index.js +4 -2
  12. package/cjs/services/storage-service/db-stores/BaseStore.js +4 -0
  13. package/cjs/services/swap-service/handler/asset-hub/handler.js +343 -0
  14. package/cjs/services/swap-service/handler/asset-hub/index.js +12 -0
  15. package/cjs/services/swap-service/handler/asset-hub/router.js +93 -0
  16. package/cjs/services/swap-service/handler/asset-hub/utils.js +158 -0
  17. package/cjs/services/swap-service/index.js +10 -1
  18. package/cjs/services/swap-service/utils.js +10 -1
  19. package/cjs/types/swap/index.js +5 -1
  20. package/core/logic-validation/swap.d.ts +3 -1
  21. package/core/logic-validation/swap.js +55 -4
  22. package/koni/api/nft/config.js +28 -18
  23. package/package.json +36 -11
  24. package/packageInfo.js +1 -1
  25. package/services/balance-service/transfer/xcm/utils.js +2 -2
  26. package/services/chain-service/constants.js +1 -1
  27. package/services/chain-service/handler/SubstrateApi.js +6 -0
  28. package/services/chain-service/index.js +17 -4
  29. package/services/migration-service/scripts/databases/ReloadMetadata.d.ts +5 -0
  30. package/services/migration-service/scripts/databases/ReloadMetadata.js +27 -0
  31. package/services/migration-service/scripts/index.js +4 -2
  32. package/services/storage-service/db-stores/BaseStore.d.ts +3 -0
  33. package/services/storage-service/db-stores/BaseStore.js +4 -0
  34. package/services/swap-service/handler/asset-hub/handler.d.ts +31 -0
  35. package/services/swap-service/handler/asset-hub/handler.js +335 -0
  36. package/services/swap-service/handler/asset-hub/index.d.ts +1 -0
  37. package/services/swap-service/handler/asset-hub/index.js +4 -0
  38. package/services/swap-service/handler/asset-hub/router.d.ts +16 -0
  39. package/services/swap-service/handler/asset-hub/router.js +85 -0
  40. package/services/swap-service/handler/asset-hub/utils.d.ts +18 -0
  41. package/services/swap-service/handler/asset-hub/utils.js +133 -0
  42. package/services/swap-service/index.js +10 -1
  43. package/services/swap-service/utils.d.ts +1 -0
  44. package/services/swap-service/utils.js +9 -1
  45. package/types/swap/index.d.ts +15 -2
  46. package/types/swap/index.js +5 -1
  47. /package/cjs/services/migration-service/scripts/{ClearMetadataDatabase.js → databases/ClearMetadataDatabase.js} +0 -0
  48. /package/services/migration-service/scripts/{ClearMetadataDatabase.d.ts → databases/ClearMetadataDatabase.d.ts} +0 -0
  49. /package/services/migration-service/scripts/{ClearMetadataDatabase.js → databases/ClearMetadataDatabase.js} +0 -0
@@ -0,0 +1,335 @@
1
+ // Copyright 2019-2022 @subwallet/extension-base
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { SwapError } from '@subwallet/extension-base/background/errors/SwapError';
5
+ import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
6
+ import { BasicTxErrorType, ChainType, ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
7
+ import { _getEarlyAssetHubValidationError, _validateBalanceToSwapOnAssetHub, _validateSwapRecipient } from '@subwallet/extension-base/core/logic-validation/swap';
8
+ import { createXcmExtrinsic } from '@subwallet/extension-base/services/balance-service/transfer/xcm';
9
+ import { _getChainNativeTokenSlug, _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils';
10
+ import { convertSwapRate, getSwapAlternativeAsset, SWAP_QUOTE_TIMEOUT_MAP } from '@subwallet/extension-base/services/swap-service/utils';
11
+ import { CommonStepType } from '@subwallet/extension-base/types/service-base';
12
+ import { SwapErrorType, SwapFeeType, SwapProviderId, SwapStepType } from '@subwallet/extension-base/types/swap';
13
+ import BigN from 'bignumber.js';
14
+ import { SwapBaseHandler } from "../base-handler.js";
15
+ import { AssetHubRouter } from "./router.js";
16
+ const PAH_LOW_LIQUIDITY_THRESHOLD = 0.15;
17
+ export class AssetHubSwapHandler {
18
+ isReady = false;
19
+ constructor(chainService, balanceService, chain) {
20
+ const chainInfo = chainService.getChainInfoByKey(chain);
21
+ const providerSlug = chain === 'statemint' ? SwapProviderId.POLKADOT_ASSET_HUB : chain === 'statemine' ? SwapProviderId.KUSAMA_ASSET_HUB : SwapProviderId.ROCOCO_ASSET_HUB;
22
+ this.swapBaseHandler = new SwapBaseHandler({
23
+ balanceService,
24
+ chainService,
25
+ providerName: chainInfo.name,
26
+ providerSlug
27
+ });
28
+ this.providerSlug = providerSlug;
29
+ this.chain = chain;
30
+ }
31
+ get chainService() {
32
+ return this.swapBaseHandler.chainService;
33
+ }
34
+ get balanceService() {
35
+ return this.swapBaseHandler.balanceService;
36
+ }
37
+ get providerInfo() {
38
+ return this.swapBaseHandler.providerInfo;
39
+ }
40
+ get name() {
41
+ return this.swapBaseHandler.name;
42
+ }
43
+ get slug() {
44
+ return this.swapBaseHandler.slug;
45
+ }
46
+ async init() {
47
+ const chainState = this.chainService.getChainStateByKey(this.chain);
48
+ if (!chainState.active) {
49
+ await this.chainService.enableChain(this.chain);
50
+ }
51
+ const substrateApi = this.chainService.getSubstrateApi(this.chain);
52
+ await substrateApi.api.isReady;
53
+ this.router = new AssetHubRouter(this.chain, this.chainService);
54
+ this.isReady = true;
55
+ }
56
+ async getXcmStep(params) {
57
+ const bnAmount = new BigN(params.request.fromAmount);
58
+ const fromAsset = this.chainService.getAssetBySlug(params.request.pair.from);
59
+ const fromAssetBalance = await this.balanceService.getTransferableBalance(params.request.address, fromAsset.originChain, fromAsset.slug);
60
+ const bnFromAssetBalance = new BigN(fromAssetBalance.value);
61
+ if (bnFromAssetBalance.gte(bnAmount)) {
62
+ return undefined; // enough balance, no need to xcm
63
+ }
64
+
65
+ const alternativeAssetSlug = getSwapAlternativeAsset(params.request.pair);
66
+ if (!alternativeAssetSlug) {
67
+ return undefined;
68
+ }
69
+ const alternativeAsset = this.chainService.getAssetBySlug(alternativeAssetSlug);
70
+ const alternativeAssetBalance = await this.balanceService.getTransferableBalance(params.request.address, alternativeAsset.originChain, alternativeAsset.slug);
71
+ const bnAlternativeAssetBalance = new BigN(alternativeAssetBalance.value);
72
+ if (bnAlternativeAssetBalance.lte(0)) {
73
+ return undefined;
74
+ }
75
+ try {
76
+ const alternativeChainInfo = this.chainService.getChainInfoByKey(alternativeAsset.originChain);
77
+ const step = {
78
+ metadata: {
79
+ sendingValue: bnAmount.toString(),
80
+ originTokenInfo: alternativeAsset,
81
+ destinationTokenInfo: fromAsset
82
+ },
83
+ name: `Transfer ${alternativeAsset.symbol} from ${alternativeChainInfo.name}`,
84
+ type: CommonStepType.XCM
85
+ };
86
+ const xcmOriginSubstrateApi = await this.chainService.getSubstrateApi(alternativeAsset.originChain).isReady;
87
+ const xcmTransfer = await createXcmExtrinsic({
88
+ originTokenInfo: alternativeAsset,
89
+ destinationTokenInfo: fromAsset,
90
+ sendingValue: bnAmount.toString(),
91
+ recipient: params.request.address,
92
+ chainInfoMap: this.chainService.getChainInfoMap(),
93
+ substrateApi: xcmOriginSubstrateApi
94
+ });
95
+ const _xcmFeeInfo = await xcmTransfer.paymentInfo(params.request.address);
96
+ const xcmFeeInfo = _xcmFeeInfo.toPrimitive();
97
+ const fee = {
98
+ feeComponent: [{
99
+ feeType: SwapFeeType.NETWORK_FEE,
100
+ amount: Math.round(xcmFeeInfo.partialFee * 1.2).toString(),
101
+ tokenSlug: _getChainNativeTokenSlug(alternativeChainInfo)
102
+ }],
103
+ defaultFeeToken: _getChainNativeTokenSlug(alternativeChainInfo),
104
+ feeOptions: [_getChainNativeTokenSlug(alternativeChainInfo)]
105
+ };
106
+ return [step, fee];
107
+ } catch (e) {
108
+ return undefined;
109
+ }
110
+ }
111
+ async getSubmitStep(params) {
112
+ if (params.selectedQuote) {
113
+ const submitStep = {
114
+ name: 'Swap',
115
+ type: SwapStepType.SWAP
116
+ };
117
+ return Promise.resolve([submitStep, params.selectedQuote.feeInfo]);
118
+ }
119
+ return Promise.resolve(undefined);
120
+ }
121
+ generateOptimalProcess(params) {
122
+ return this.swapBaseHandler.generateOptimalProcess(params, [this.getXcmStep, this.getSubmitStep]);
123
+ }
124
+ async getSwapQuote(request) {
125
+ const fromAsset = this.chainService.getAssetBySlug(request.pair.from);
126
+ const toAsset = this.chainService.getAssetBySlug(request.pair.to);
127
+ const fromChain = this.chainService.getChainInfoByKey(fromAsset.originChain);
128
+ const fromChainNativeTokenSlug = _getChainNativeTokenSlug(fromChain);
129
+ if (!this.isReady || !this.router) {
130
+ return new SwapError(SwapErrorType.UNKNOWN);
131
+ }
132
+ const earlyValidation = await this.validateSwapRequest(request);
133
+ if (earlyValidation.error) {
134
+ const metadata = earlyValidation.metadata;
135
+ return _getEarlyAssetHubValidationError(earlyValidation.error, metadata);
136
+ }
137
+ try {
138
+ const paths = this.router.buildPath(request.pair);
139
+ const amountOut = earlyValidation.metadata.toAmount;
140
+ const toAmount = new BigN(amountOut);
141
+ const minReceive = toAmount.times(1 - request.slippage).integerValue(BigN.ROUND_DOWN);
142
+ const extrinsic = await this.router.buildSwapExtrinsic(paths, request.address, request.fromAmount, minReceive.toString());
143
+ const paymentInfo = await extrinsic.paymentInfo(request.address);
144
+ const networkFee = {
145
+ tokenSlug: fromChainNativeTokenSlug,
146
+ amount: paymentInfo.partialFee.toString(),
147
+ feeType: SwapFeeType.NETWORK_FEE
148
+ };
149
+ const feeTokenOptions = [fromChainNativeTokenSlug];
150
+ const selectedFeeToken = fromChainNativeTokenSlug;
151
+ const priceImpactPct = earlyValidation.metadata.priceImpactPct || '0';
152
+ return {
153
+ pair: request.pair,
154
+ fromAmount: request.fromAmount,
155
+ toAmount: toAmount.toString(),
156
+ rate: convertSwapRate(earlyValidation.metadata.quoteRate, fromAsset, toAsset),
157
+ provider: this.providerInfo,
158
+ aliveUntil: +Date.now() + (SWAP_QUOTE_TIMEOUT_MAP[this.slug] || SWAP_QUOTE_TIMEOUT_MAP.default),
159
+ feeInfo: {
160
+ feeComponent: [networkFee],
161
+ defaultFeeToken: fromChainNativeTokenSlug,
162
+ feeOptions: feeTokenOptions,
163
+ // TODO: enable fee options
164
+ selectedFeeToken
165
+ },
166
+ isLowLiquidity: Math.abs(parseFloat(priceImpactPct)) >= PAH_LOW_LIQUIDITY_THRESHOLD,
167
+ route: {
168
+ path: paths.map(asset => asset.slug)
169
+ }
170
+ };
171
+ } catch (e) {
172
+ return new SwapError(SwapErrorType.ERROR_FETCHING_QUOTE);
173
+ }
174
+ }
175
+ async handleXcmStep(params) {
176
+ const pair = params.quote.pair;
177
+ const alternativeAssetSlug = getSwapAlternativeAsset(pair);
178
+ const originAsset = this.chainService.getAssetBySlug(alternativeAssetSlug);
179
+ const destinationAsset = this.chainService.getAssetBySlug(pair.from);
180
+ const substrateApi = this.chainService.getSubstrateApi(originAsset.originChain);
181
+ const chainApi = await substrateApi.isReady;
182
+ const destinationAssetBalance = await this.balanceService.getTransferableBalance(params.address, destinationAsset.originChain, destinationAsset.slug);
183
+ const xcmFee = params.process.totalFee[params.currentStep];
184
+ const bnAmount = new BigN(params.quote.fromAmount);
185
+ const bnDestinationAssetBalance = new BigN(destinationAssetBalance.value);
186
+ let bnTotalAmount = bnAmount.minus(bnDestinationAssetBalance);
187
+ if (_isNativeToken(originAsset)) {
188
+ const bnXcmFee = new BigN(xcmFee.feeComponent[0].amount); // xcm fee is paid in native token but swap token is not always native token
189
+
190
+ bnTotalAmount = bnTotalAmount.plus(bnXcmFee);
191
+ }
192
+ const xcmTransfer = await createXcmExtrinsic({
193
+ originTokenInfo: originAsset,
194
+ destinationTokenInfo: destinationAsset,
195
+ sendingValue: bnTotalAmount.toString(),
196
+ recipient: params.address,
197
+ chainInfoMap: this.chainService.getChainInfoMap(),
198
+ substrateApi: chainApi
199
+ });
200
+ const xcmData = {
201
+ originNetworkKey: originAsset.originChain,
202
+ destinationNetworkKey: destinationAsset.originChain,
203
+ from: params.address,
204
+ to: params.address,
205
+ value: bnTotalAmount.toString(),
206
+ tokenSlug: originAsset.slug,
207
+ showExtraWarning: true
208
+ };
209
+ return {
210
+ txChain: originAsset.originChain,
211
+ extrinsic: xcmTransfer,
212
+ transferNativeAmount: _isNativeToken(originAsset) ? bnTotalAmount.toString() : '0',
213
+ extrinsicType: ExtrinsicType.TRANSFER_XCM,
214
+ chainType: ChainType.SUBSTRATE,
215
+ txData: xcmData
216
+ };
217
+ }
218
+ async handleSubmitStep(params) {
219
+ var _this$router;
220
+ const fromAsset = this.chainService.getAssetBySlug(params.quote.pair.from);
221
+ const txData = {
222
+ provider: this.providerInfo,
223
+ quote: params.quote,
224
+ address: params.address,
225
+ slippage: params.slippage,
226
+ process: params.process
227
+ };
228
+ const paths = params.quote.route.path.map(slug => this.chainService.getAssetBySlug(slug));
229
+ const {
230
+ fromAmount,
231
+ toAmount
232
+ } = params.quote;
233
+ const minReceive = new BigN(1 - params.slippage).times(toAmount).integerValue(BigN.ROUND_DOWN);
234
+ const extrinsic = await ((_this$router = this.router) === null || _this$router === void 0 ? void 0 : _this$router.buildSwapExtrinsic(paths, params.address, fromAmount, minReceive.toString()));
235
+ return {
236
+ txChain: fromAsset.originChain,
237
+ txData,
238
+ extrinsic,
239
+ transferNativeAmount: _isNativeToken(fromAsset) ? params.quote.fromAmount : '0',
240
+ // todo
241
+ extrinsicType: ExtrinsicType.SWAP,
242
+ chainType: ChainType.SUBSTRATE
243
+ };
244
+ }
245
+ handleSwapProcess(params) {
246
+ const {
247
+ currentStep,
248
+ process
249
+ } = params;
250
+ const type = process.steps[currentStep].type;
251
+ switch (type) {
252
+ case CommonStepType.XCM:
253
+ return this.handleXcmStep(params);
254
+ case SwapStepType.SWAP:
255
+ return this.handleSubmitStep(params);
256
+ default:
257
+ return Promise.reject(new TransactionError(BasicTxErrorType.UNSUPPORTED));
258
+ }
259
+ }
260
+ async validateSwapStep(params, isXcmOk, stepIndex) {
261
+ // check swap quote timestamp
262
+ // check balance to pay transaction fee
263
+ // check balance against spending amount
264
+ if (!params.selectedQuote) {
265
+ return Promise.resolve([new TransactionError(BasicTxErrorType.INTERNAL_ERROR)]);
266
+ }
267
+ const selectedQuote = params.selectedQuote;
268
+ const currentTimestamp = +Date.now();
269
+ if (selectedQuote.aliveUntil <= currentTimestamp) {
270
+ return Promise.resolve([new TransactionError(SwapErrorType.QUOTE_TIMEOUT)]);
271
+ }
272
+ const stepFee = params.process.totalFee[stepIndex].feeComponent;
273
+ const networkFee = stepFee.find(fee => fee.feeType === SwapFeeType.NETWORK_FEE);
274
+ if (!networkFee) {
275
+ return Promise.resolve([new TransactionError(BasicTxErrorType.INTERNAL_ERROR)]);
276
+ }
277
+ const fromAsset = this.chainService.getAssetBySlug(params.selectedQuote.pair.from);
278
+ const feeTokenInfo = this.chainService.getAssetBySlug(networkFee.tokenSlug);
279
+ const feeTokenChain = this.chainService.getChainInfoByKey(feeTokenInfo.originChain);
280
+ const {
281
+ fromAmount,
282
+ minSwap
283
+ } = params.selectedQuote;
284
+ const [feeTokenBalance, fromAssetBalance] = await Promise.all([this.balanceService.getTransferableBalance(params.address, feeTokenInfo.originChain, feeTokenInfo.slug), this.balanceService.getTransferableBalance(params.address, fromAsset.originChain, fromAsset.slug)]);
285
+ const balanceError = _validateBalanceToSwapOnAssetHub(fromAsset, feeTokenInfo, feeTokenChain, networkFee.amount, fromAssetBalance.value, feeTokenBalance.value, fromAmount, isXcmOk, minSwap);
286
+ if (balanceError) {
287
+ return Promise.resolve([balanceError]);
288
+ }
289
+ if (!params.recipient) {
290
+ return Promise.resolve([]);
291
+ }
292
+ const toAsset = this.chainService.getAssetBySlug(params.selectedQuote.pair.to);
293
+ const toAssetChain = this.chainService.getChainInfoByKey(toAsset.originChain);
294
+ const recipientError = _validateSwapRecipient(toAssetChain, params.recipient);
295
+ if (recipientError) {
296
+ return Promise.resolve([recipientError]);
297
+ }
298
+ return Promise.resolve([]);
299
+ }
300
+ async validateSwapProcess(params) {
301
+ const amount = params.selectedQuote.fromAmount;
302
+ const bnAmount = new BigN(amount);
303
+ if (bnAmount.lte(0)) {
304
+ return [new TransactionError(BasicTxErrorType.INVALID_PARAMS, 'Amount must be greater than 0')];
305
+ }
306
+ let isXcmOk = false;
307
+ for (const [index, step] of params.process.steps.entries()) {
308
+ const getErrors = async () => {
309
+ switch (step.type) {
310
+ case CommonStepType.DEFAULT:
311
+ return Promise.resolve([]);
312
+ case CommonStepType.XCM:
313
+ return this.swapBaseHandler.validateXcmStep(params, index);
314
+ case SwapStepType.SWAP:
315
+ return this.validateSwapStep(params, isXcmOk, index);
316
+ default:
317
+ return Promise.reject(new TransactionError(BasicTxErrorType.UNSUPPORTED));
318
+ }
319
+ };
320
+ const errors = await getErrors();
321
+ if (errors.length) {
322
+ return errors;
323
+ } else if (step.type === CommonStepType.XCM) {
324
+ isXcmOk = true;
325
+ }
326
+ }
327
+ return [];
328
+ }
329
+ validateSwapRequest(request) {
330
+ if (!this.isReady || !this.router) {
331
+ throw new SwapError(SwapErrorType.ERROR_FETCHING_QUOTE);
332
+ }
333
+ return this.router.earlyValidateSwapValidation(request);
334
+ }
335
+ }
@@ -0,0 +1 @@
1
+ export { AssetHubSwapHandler } from './handler';
@@ -0,0 +1,4 @@
1
+ // Copyright 2019-2022 @subwallet/extension-base
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export { AssetHubSwapHandler } from "./handler.js";
@@ -0,0 +1,16 @@
1
+ import { _ChainAsset } from '@subwallet/chain-list/types';
2
+ import { ChainService } from '@subwallet/extension-base/services/chain-service';
3
+ import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types';
4
+ import { AssetHubSwapEarlyValidation, SwapPair, SwapRequest } from '@subwallet/extension-base/types/swap';
5
+ import { SubmittableExtrinsic } from '@polkadot/api/types';
6
+ export declare class AssetHubRouter {
7
+ private readonly chain;
8
+ readonly chainService: ChainService;
9
+ constructor(chain: string, chainService: ChainService);
10
+ get substrateApi(): _SubstrateApi;
11
+ get nativeToken(): _ChainAsset;
12
+ buildPath(pair: SwapPair): Array<_ChainAsset>;
13
+ earlyValidateSwapValidation(request: SwapRequest): Promise<AssetHubSwapEarlyValidation>;
14
+ estimateAmountOut(pair: SwapPair, amountIn: string): Promise<string>;
15
+ buildSwapExtrinsic(path: Array<_ChainAsset>, recipient: string, amountIn: string, amountOutMin: string): Promise<SubmittableExtrinsic<'promise'>>;
16
+ }
@@ -0,0 +1,85 @@
1
+ // Copyright 2019-2022 @subwallet/extension-base
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { _getTokenMinAmount } from '@subwallet/extension-base/services/chain-service/utils';
5
+ import { buildSwapExtrinsic, checkLiquidityForPath, checkMinAmountForPath, estimatePriceImpactPct, estimateRateAfter, estimateRateForPath, estimateTokensForPath, getReserveForPath } from '@subwallet/extension-base/services/swap-service/handler/asset-hub/utils';
6
+ import { SwapErrorType } from '@subwallet/extension-base/types/swap';
7
+ import BigN from 'bignumber.js';
8
+ export class AssetHubRouter {
9
+ constructor(chain, chainService) {
10
+ this.chain = chain;
11
+ this.chainService = chainService;
12
+ }
13
+ get substrateApi() {
14
+ return this.chainService.getSubstrateApi(this.chain);
15
+ }
16
+ get nativeToken() {
17
+ return this.chainService.getNativeTokenInfo(this.chain);
18
+ }
19
+ buildPath(pair) {
20
+ // const nativeToken = this.nativeToken;
21
+ // const nativeTokenSlug = nativeToken.slug;
22
+
23
+ const assetFrom = this.chainService.getAssetBySlug(pair.from);
24
+ const assetTo = this.chainService.getAssetBySlug(pair.to);
25
+ return [assetFrom, assetTo];
26
+ // if (pair.from === nativeTokenSlug || pair.to === nativeTokenSlug) {
27
+ // return [assetFrom, assetTo];
28
+ // } else {
29
+ // return [assetFrom, nativeToken, assetTo];
30
+ // }
31
+ }
32
+
33
+ async earlyValidateSwapValidation(request) {
34
+ const substrateApi = await this.substrateApi.isReady;
35
+ const paths = this.buildPath(request.pair);
36
+ const api = await substrateApi.api.isReady;
37
+ const amount = request.fromAmount;
38
+ const reserves = await getReserveForPath(api, paths);
39
+ const amounts = estimateTokensForPath(amount, reserves);
40
+ const marketRate = estimateRateForPath(reserves);
41
+ const marketRateAfter = estimateRateAfter(amount, reserves);
42
+ const priceImpactPct = estimatePriceImpactPct(marketRate, marketRateAfter);
43
+ const errors = [];
44
+
45
+ // Check liquidity
46
+ const liquidityError = checkLiquidityForPath(amounts, reserves);
47
+ if (liquidityError) {
48
+ errors.push(liquidityError);
49
+ }
50
+
51
+ // Check amount token in pool after swap
52
+ const minAmounts = paths.map(asset => _getTokenMinAmount(asset));
53
+ const minAmountAfterSwapError = checkMinAmountForPath(reserves, amounts, minAmounts);
54
+ if (minAmountAfterSwapError) {
55
+ errors.push(minAmountAfterSwapError);
56
+ }
57
+ const bnAmount = new BigN(request.fromAmount);
58
+ if (bnAmount.lte(0)) {
59
+ errors.push(SwapErrorType.AMOUNT_CANNOT_BE_ZERO);
60
+ }
61
+ const metadata = {
62
+ chain: this.chainService.getChainInfoByKey(this.chain),
63
+ toAmount: amounts[amounts.length - 1],
64
+ quoteRate: marketRate,
65
+ priceImpactPct: priceImpactPct
66
+ };
67
+ return {
68
+ error: errors[0],
69
+ metadata
70
+ };
71
+ }
72
+ async estimateAmountOut(pair, amountIn) {
73
+ const substrateApi = await this.substrateApi.isReady;
74
+ const paths = this.buildPath(pair);
75
+ const api = await substrateApi.api.isReady;
76
+ const reserves = await getReserveForPath(api, paths);
77
+ const amounts = estimateTokensForPath(amountIn, reserves);
78
+ return amounts[amounts.length - 1];
79
+ }
80
+ async buildSwapExtrinsic(path, recipient, amountIn, amountOutMin) {
81
+ const substrateApi = await this.substrateApi.isReady;
82
+ const api = await substrateApi.api.isReady;
83
+ return buildSwapExtrinsic(api, path, recipient, amountIn, amountOutMin);
84
+ }
85
+ }
@@ -0,0 +1,18 @@
1
+ import { _ChainAsset } from '@subwallet/chain-list/types';
2
+ import { SwapErrorType } from '@subwallet/extension-base/types/swap';
3
+ import { ApiPromise } from '@polkadot/api';
4
+ import { SubmittableExtrinsic } from '@polkadot/api/types';
5
+ export declare const _getPoolInfo: (api: ApiPromise, asset1: _ChainAsset, asset2: _ChainAsset) => Promise<[string, string]>;
6
+ export declare const getReserveForPool: (api: ApiPromise, asset1: _ChainAsset, asset2: _ChainAsset) => Promise<[string, string]>;
7
+ export declare const getReserveForPath: (api: ApiPromise, paths: _ChainAsset[]) => Promise<Array<[string, string]>>;
8
+ export declare const estimateTokensForPool: (amount: string, reserves: [string, string]) => string;
9
+ export declare const estimateTokensForPath: (amount: string, reserves: Array<[string, string]>) => string[];
10
+ export declare const estimateRateForPath: (reserves: Array<[string, string]>) => string;
11
+ export declare const estimateActualRate: (amount: string, reserves: Array<[string, string]>) => string;
12
+ export declare const estimateRateAfter: (amount: string, reserves: Array<[string, string]>) => string;
13
+ export declare const estimatePriceImpactPct: (marketRate: string, marketRateAfter: string) => string;
14
+ export declare const checkLiquidityForPool: (amount: string, reserve1: string, reserve2: string) => SwapErrorType | undefined;
15
+ export declare const checkLiquidityForPath: (amounts: string[], reserves: Array<[string, string]>) => SwapErrorType | undefined;
16
+ export declare const checkMinAmountForPool: (reserve1: string, reserve2: string, amount1: string, amount2: string, minAmount1: string, minAmount2: string) => SwapErrorType | undefined;
17
+ export declare const checkMinAmountForPath: (reserves: Array<[string, string]>, amounts: string[], minAmounts: string[]) => SwapErrorType | undefined;
18
+ export declare const buildSwapExtrinsic: (api: ApiPromise, paths: _ChainAsset[], recipient: string, amountIn: string, amountOutMin: string, keepAlive?: boolean) => SubmittableExtrinsic<'promise'>;
@@ -0,0 +1,133 @@
1
+ // Copyright 2019-2022 @subwallet/extension-base
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { _getXcmAssetMultilocation } from '@subwallet/extension-base/services/chain-service/utils';
5
+ import { SwapErrorType } from '@subwallet/extension-base/types/swap';
6
+ import BigN from 'bignumber.js';
7
+ export const _getPoolInfo = async (api, asset1, asset2) => {
8
+ const assetLocation1 = _getXcmAssetMultilocation(asset1);
9
+ const assetLocation2 = _getXcmAssetMultilocation(asset2);
10
+ const rs = await api.call.assetConversionApi.getReserves(assetLocation1, assetLocation2);
11
+ if (!rs) {
12
+ return ['0', '0'];
13
+ }
14
+ const [balanceAsset1, balanceAsset2] = rs.unwrapOrDefault();
15
+ return [balanceAsset1.toString(), balanceAsset2.toString()];
16
+ };
17
+ export const getReserveForPool = async (api, asset1, asset2) => {
18
+ let [balanceAsset1, balanceAsset2] = await _getPoolInfo(api, asset1, asset2);
19
+ if (balanceAsset1 !== '0' && balanceAsset2 !== '0') {
20
+ return [balanceAsset1, balanceAsset2];
21
+ } else {
22
+ [balanceAsset2, balanceAsset1] = await _getPoolInfo(api, asset2, asset1);
23
+ return [balanceAsset1, balanceAsset2];
24
+ }
25
+ };
26
+ export const getReserveForPath = async (api, paths) => {
27
+ const pairs = [];
28
+ for (let i = 0; i < paths.length - 1; i++) {
29
+ const asset1 = paths[i];
30
+ const asset2 = paths[i + 1];
31
+ pairs.push([asset1, asset2]);
32
+ }
33
+ return await Promise.all(pairs.map(async ([asset1, asset2]) => getReserveForPool(api, asset1, asset2)));
34
+ };
35
+ export const estimateTokensForPool = (amount, reserves) => {
36
+ if (amount === '0') {
37
+ return '0';
38
+ }
39
+ return new BigN(amount).times(reserves[1]).div(reserves[0]).integerValue(BigN.ROUND_DOWN).toString();
40
+ };
41
+ export const estimateTokensForPath = (amount, reserves) => {
42
+ const result = [amount];
43
+ for (let i = 0; i < reserves.length; i++) {
44
+ const reserve = reserves[i];
45
+ const currentAmount = result[i];
46
+ const nextAmount = estimateTokensForPool(currentAmount, reserve);
47
+ result.push(nextAmount);
48
+ }
49
+ return result;
50
+ };
51
+ export const estimateRateForPath = reserves => {
52
+ let result = new BigN(1);
53
+ for (const reserve of reserves) {
54
+ result = result.times(reserve[0]).div(reserve[1]);
55
+ }
56
+ return result.toString();
57
+ };
58
+ export const estimateActualRate = (amount, reserves) => {
59
+ let result = new BigN(1);
60
+ const m = new BigN(amount);
61
+
62
+ // Currently support for direct path swap only
63
+ for (const reserve of reserves) {
64
+ const x = new BigN(reserve[0]);
65
+ const y = new BigN(reserve[1]);
66
+ result = result.times(x.plus(m)).div(y);
67
+ }
68
+ return result.toString();
69
+ };
70
+ export const estimateRateAfter = (amount, reserves) => {
71
+ const m = new BigN(amount);
72
+ const reserve = reserves[0];
73
+ const x = new BigN(reserve[0]);
74
+ const y = new BigN(reserve[1]);
75
+ const n = y.multipliedBy(m).div(x.plus(m));
76
+ const result = x.plus(m).div(y.minus(n));
77
+ return result.toString();
78
+ };
79
+ export const estimatePriceImpactPct = (marketRate, marketRateAfter) => {
80
+ const bnMarketRate = new BigN(marketRate);
81
+ const bnActualRate = new BigN(marketRateAfter);
82
+ return new BigN(1).minus(bnMarketRate.div(bnActualRate)).multipliedBy(100).toString();
83
+ };
84
+ export const checkLiquidityForPool = (amount, reserve1, reserve2) => {
85
+ if (new BigN(reserve1).eq('0') || new BigN(reserve2).eq('0')) {
86
+ return SwapErrorType.ASSET_NOT_SUPPORTED;
87
+ } else if (new BigN(reserve1).lt(amount)) {
88
+ return SwapErrorType.NOT_ENOUGH_LIQUIDITY;
89
+ }
90
+ return undefined;
91
+ };
92
+ export const checkLiquidityForPath = (amounts, reserves) => {
93
+ for (let i = 0; i < reserves.length; i++) {
94
+ const amount = amounts[i];
95
+ const [reserve1, reserve2] = reserves[i];
96
+ const error = checkLiquidityForPool(amount, reserve1, reserve2);
97
+ if (error) {
98
+ return error;
99
+ }
100
+ }
101
+ return undefined;
102
+ };
103
+
104
+ // Swap from asset1 to asset2
105
+ export const checkMinAmountForPool = (reserve1, reserve2, amount1, amount2, minAmount1, minAmount2) => {
106
+ const newReserve1 = new BigN(reserve1).plus(amount1);
107
+ const newReserve2 = new BigN(reserve2).minus(amount2);
108
+ if (newReserve1.lt(minAmount1) || newReserve2.lt(minAmount2)) {
109
+ return SwapErrorType.MAKE_POOL_NOT_ENOUGH_EXISTENTIAL_DEPOSIT;
110
+ }
111
+ return undefined;
112
+ };
113
+ export const checkMinAmountForPath = (reserves, amounts, minAmounts) => {
114
+ for (let i = 0; i < reserves.length; i++) {
115
+ const [amount1, amount2] = amounts.slice(i, 2);
116
+ const [minAmount1, minAmount2] = minAmounts.slice(i, 2);
117
+ const [reserve1, reserve2] = reserves[i];
118
+ const error = checkMinAmountForPool(reserve1, reserve2, amount1, amount2, minAmount1, minAmount2);
119
+ if (error) {
120
+ return error;
121
+ }
122
+ }
123
+ return undefined;
124
+ };
125
+
126
+ // Build extrinsic for swap
127
+ export const buildSwapExtrinsic = (api, paths, recipient, amountIn, amountOutMin, keepAlive = true) => {
128
+ const pathsInfo = paths.map(asset => {
129
+ const multilocation = _getXcmAssetMultilocation(asset);
130
+ return api.createType('MultiLocation', multilocation).toU8a();
131
+ });
132
+ return api.tx.assetConversion.swapExactTokensForTokens(pathsInfo, amountIn, amountOutMin, recipient, keepAlive);
133
+ };
@@ -5,6 +5,7 @@ import { SwapError } from '@subwallet/extension-base/background/errors/SwapError
5
5
  import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
6
6
  import { BasicTxErrorType } from '@subwallet/extension-base/background/KoniTypes';
7
7
  import { ServiceStatus } from '@subwallet/extension-base/services/base/types';
8
+ import { AssetHubSwapHandler } from '@subwallet/extension-base/services/swap-service/handler/asset-hub';
8
9
  import { ChainflipSwapHandler } from '@subwallet/extension-base/services/swap-service/handler/chainflip-handler';
9
10
  import { HydradxHandler } from '@subwallet/extension-base/services/swap-service/handler/hydradx-handler';
10
11
  import { _PROVIDER_TO_SUPPORTED_PAIR_MAP, getSwapAltToken, SWAP_QUOTE_TIMEOUT_MAP } from '@subwallet/extension-base/services/swap-service/utils';
@@ -91,7 +92,6 @@ export class SwapService {
91
92
  request,
92
93
  selectedQuote: swapQuoteResponse.optimalQuote
93
94
  });
94
- console.log('optimalProcess', optimalProcess);
95
95
  return {
96
96
  process: optimalProcess,
97
97
  quote: swapQuoteResponse
@@ -141,6 +141,15 @@ export class SwapService {
141
141
  case SwapProviderId.HYDRADX_MAINNET:
142
142
  this.handlers[providerId] = new HydradxHandler(this.chainService, this.state.balanceService, false);
143
143
  break;
144
+ case SwapProviderId.POLKADOT_ASSET_HUB:
145
+ this.handlers[providerId] = new AssetHubSwapHandler(this.chainService, this.state.balanceService, 'statemint');
146
+ break;
147
+ case SwapProviderId.KUSAMA_ASSET_HUB:
148
+ this.handlers[providerId] = new AssetHubSwapHandler(this.chainService, this.state.balanceService, 'statemine');
149
+ break;
150
+ case SwapProviderId.ROCOCO_ASSET_HUB:
151
+ this.handlers[providerId] = new AssetHubSwapHandler(this.chainService, this.state.balanceService, 'rococo_assethub');
152
+ break;
144
153
  default:
145
154
  throw new Error('Unsupported provider');
146
155
  }
@@ -12,3 +12,4 @@ export declare const _PROVIDER_TO_SUPPORTED_PAIR_MAP: Record<string, string[]>;
12
12
  export declare function getSwapAlternativeAsset(swapPair: SwapPair): string | undefined;
13
13
  export declare function getSwapAltToken(chainAsset: _ChainAsset): string | undefined;
14
14
  export declare function calculateSwapRate(fromAmount: string, toAmount: string, fromAsset: _ChainAsset, toAsset: _ChainAsset): number;
15
+ export declare function convertSwapRate(rate: string, fromAsset: _ChainAsset, toAsset: _ChainAsset): number;
@@ -36,7 +36,10 @@ export const _PROVIDER_TO_SUPPORTED_PAIR_MAP = {
36
36
  [SwapProviderId.HYDRADX_MAINNET]: [COMMON_CHAIN_SLUGS.HYDRADX],
37
37
  [SwapProviderId.HYDRADX_TESTNET]: [COMMON_CHAIN_SLUGS.HYDRADX_TESTNET],
38
38
  [SwapProviderId.CHAIN_FLIP_MAINNET]: [COMMON_CHAIN_SLUGS.POLKADOT, COMMON_CHAIN_SLUGS.ETHEREUM],
39
- [SwapProviderId.CHAIN_FLIP_TESTNET]: [COMMON_CHAIN_SLUGS.CHAINFLIP_POLKADOT, COMMON_CHAIN_SLUGS.ETHEREUM_SEPOLIA]
39
+ [SwapProviderId.CHAIN_FLIP_TESTNET]: [COMMON_CHAIN_SLUGS.CHAINFLIP_POLKADOT, COMMON_CHAIN_SLUGS.ETHEREUM_SEPOLIA],
40
+ [SwapProviderId.POLKADOT_ASSET_HUB]: [COMMON_CHAIN_SLUGS.POLKADOT_ASSET_HUB],
41
+ [SwapProviderId.KUSAMA_ASSET_HUB]: [COMMON_CHAIN_SLUGS.KUSAMA_ASSET_HUB],
42
+ [SwapProviderId.ROCOCO_ASSET_HUB]: [COMMON_CHAIN_SLUGS.ROCOCO_ASSET_HUB]
40
43
  };
41
44
  export function getSwapAlternativeAsset(swapPair) {
42
45
  var _swapPair$metadata;
@@ -52,4 +55,9 @@ export function calculateSwapRate(fromAmount, toAmount, fromAsset, toAsset) {
52
55
  const decimalDiff = _getAssetDecimals(toAsset) - _getAssetDecimals(fromAsset);
53
56
  const bnRate = bnFromAmount.div(bnToAmount);
54
57
  return 1 / bnRate.times(10 ** decimalDiff).toNumber();
58
+ }
59
+ export function convertSwapRate(rate, fromAsset, toAsset) {
60
+ const decimalDiff = _getAssetDecimals(toAsset) - _getAssetDecimals(fromAsset);
61
+ const bnRate = new BigN(rate);
62
+ return bnRate.times(10 ** decimalDiff).pow(-1).toNumber();
55
63
  }