@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.
- package/background/errors/SwapError.js +5 -1
- package/cjs/background/errors/SwapError.js +5 -1
- package/cjs/core/logic-validation/swap.js +56 -3
- package/cjs/koni/api/nft/config.js +28 -18
- package/cjs/packageInfo.js +1 -1
- package/cjs/services/balance-service/transfer/xcm/utils.js +2 -2
- package/cjs/services/chain-service/constants.js +1 -1
- package/cjs/services/chain-service/handler/SubstrateApi.js +6 -0
- package/cjs/services/chain-service/index.js +17 -4
- package/cjs/services/migration-service/scripts/databases/ReloadMetadata.js +35 -0
- package/cjs/services/migration-service/scripts/index.js +4 -2
- package/cjs/services/storage-service/db-stores/BaseStore.js +4 -0
- package/cjs/services/swap-service/handler/asset-hub/handler.js +343 -0
- package/cjs/services/swap-service/handler/asset-hub/index.js +12 -0
- package/cjs/services/swap-service/handler/asset-hub/router.js +93 -0
- package/cjs/services/swap-service/handler/asset-hub/utils.js +158 -0
- package/cjs/services/swap-service/index.js +10 -1
- package/cjs/services/swap-service/utils.js +10 -1
- package/cjs/types/swap/index.js +5 -1
- package/core/logic-validation/swap.d.ts +3 -1
- package/core/logic-validation/swap.js +55 -4
- package/koni/api/nft/config.js +28 -18
- package/package.json +36 -11
- package/packageInfo.js +1 -1
- package/services/balance-service/transfer/xcm/utils.js +2 -2
- package/services/chain-service/constants.js +1 -1
- package/services/chain-service/handler/SubstrateApi.js +6 -0
- package/services/chain-service/index.js +17 -4
- package/services/migration-service/scripts/databases/ReloadMetadata.d.ts +5 -0
- package/services/migration-service/scripts/databases/ReloadMetadata.js +27 -0
- package/services/migration-service/scripts/index.js +4 -2
- package/services/storage-service/db-stores/BaseStore.d.ts +3 -0
- package/services/storage-service/db-stores/BaseStore.js +4 -0
- package/services/swap-service/handler/asset-hub/handler.d.ts +31 -0
- package/services/swap-service/handler/asset-hub/handler.js +335 -0
- package/services/swap-service/handler/asset-hub/index.d.ts +1 -0
- package/services/swap-service/handler/asset-hub/index.js +4 -0
- package/services/swap-service/handler/asset-hub/router.d.ts +16 -0
- package/services/swap-service/handler/asset-hub/router.js +85 -0
- package/services/swap-service/handler/asset-hub/utils.d.ts +18 -0
- package/services/swap-service/handler/asset-hub/utils.js +133 -0
- package/services/swap-service/index.js +10 -1
- package/services/swap-service/utils.d.ts +1 -0
- package/services/swap-service/utils.js +9 -1
- package/types/swap/index.d.ts +15 -2
- package/types/swap/index.js +5 -1
- /package/cjs/services/migration-service/scripts/{ClearMetadataDatabase.js → databases/ClearMetadataDatabase.js} +0 -0
- /package/services/migration-service/scripts/{ClearMetadataDatabase.d.ts → databases/ClearMetadataDatabase.d.ts} +0 -0
- /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,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
|
}
|