@subwallet/extension-base 1.3.26-0 → 1.3.27-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 (50) hide show
  1. package/background/KoniTypes.d.ts +4 -3
  2. package/background/warnings/TransactionWarning.d.ts +2 -0
  3. package/background/warnings/TransactionWarning.js +16 -1
  4. package/cjs/background/warnings/TransactionWarning.js +15 -0
  5. package/cjs/core/logic-validation/index.js +32 -1
  6. package/cjs/core/utils.js +25 -3
  7. package/cjs/koni/background/handlers/Extension.js +86 -94
  8. package/cjs/packageInfo.js +1 -1
  9. package/cjs/services/swap-service/handler/asset-hub/handler.js +182 -40
  10. package/cjs/services/swap-service/handler/asset-hub/utils.js +3 -0
  11. package/cjs/services/swap-service/handler/base-handler.js +326 -12
  12. package/cjs/services/swap-service/handler/chainflip-handler.js +80 -16
  13. package/cjs/services/swap-service/handler/hydradx-handler.js +174 -30
  14. package/cjs/services/swap-service/handler/simpleswap-handler.js +50 -1
  15. package/cjs/services/swap-service/handler/uniswap-handler.js +47 -1
  16. package/cjs/services/swap-service/index.js +191 -27
  17. package/cjs/services/swap-service/interface.js +14 -0
  18. package/cjs/services/swap-service/utils.js +81 -5
  19. package/core/logic-validation/index.d.ts +4 -0
  20. package/core/logic-validation/index.js +22 -1
  21. package/core/utils.d.ts +3 -0
  22. package/core/utils.js +22 -2
  23. package/koni/background/handlers/Extension.d.ts +2 -2
  24. package/koni/background/handlers/Extension.js +20 -28
  25. package/package.json +12 -7
  26. package/packageInfo.js +1 -1
  27. package/services/balance-service/helpers/process.d.ts +3 -3
  28. package/services/balance-service/index.d.ts +2 -3
  29. package/services/swap-service/handler/asset-hub/handler.d.ts +6 -3
  30. package/services/swap-service/handler/asset-hub/handler.js +170 -28
  31. package/services/swap-service/handler/asset-hub/utils.js +3 -0
  32. package/services/swap-service/handler/base-handler.d.ts +12 -3
  33. package/services/swap-service/handler/base-handler.js +329 -15
  34. package/services/swap-service/handler/chainflip-handler.d.ts +4 -3
  35. package/services/swap-service/handler/chainflip-handler.js +74 -10
  36. package/services/swap-service/handler/hydradx-handler.d.ts +8 -3
  37. package/services/swap-service/handler/hydradx-handler.js +176 -32
  38. package/services/swap-service/handler/simpleswap-handler.d.ts +4 -2
  39. package/services/swap-service/handler/simpleswap-handler.js +50 -1
  40. package/services/swap-service/handler/uniswap-handler.d.ts +4 -2
  41. package/services/swap-service/handler/uniswap-handler.js +47 -1
  42. package/services/swap-service/index.d.ts +15 -5
  43. package/services/swap-service/index.js +182 -18
  44. package/services/swap-service/interface.d.ts +9 -0
  45. package/services/swap-service/interface.js +8 -0
  46. package/services/swap-service/utils.d.ts +9 -1
  47. package/services/swap-service/utils.js +74 -4
  48. package/types/service-base.d.ts +6 -2
  49. package/types/swap/index.d.ts +34 -6
  50. package/types/transaction/process.d.ts +0 -6
@@ -2,15 +2,21 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
5
+ import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
6
+ import { validateSpendingAndFeePayment } from '@subwallet/extension-base/core/logic-validation';
5
7
  import { _validateBalanceToSwap, _validateSwapRecipient } from '@subwallet/extension-base/core/logic-validation/swap';
6
- import { _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils';
7
- import { getSwapAlternativeAsset } from '@subwallet/extension-base/services/swap-service/utils';
8
- import { BasicTxErrorType } from '@subwallet/extension-base/types';
8
+ import { _isAccountActive } from '@subwallet/extension-base/core/substrate/system-pallet';
9
+ import { _isSnowBridgeXcm } from '@subwallet/extension-base/core/substrate/xcm-parser';
10
+ import { _isSufficientToken } from '@subwallet/extension-base/core/utils';
11
+ import { _getAssetDecimals, _getAssetSymbol, _getTokenMinAmount, _isChainEvmCompatible, _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils';
12
+ import { FEE_RATE_MULTIPLIER, getSwapAlternativeAsset } from '@subwallet/extension-base/services/swap-service/utils';
13
+ import { BasicTxErrorType, TransferTxErrorType } from '@subwallet/extension-base/types';
9
14
  import { DEFAULT_FIRST_STEP, MOCK_STEP_FEE } from '@subwallet/extension-base/types/service-base';
10
15
  import { SwapErrorType, SwapFeeType } from '@subwallet/extension-base/types/swap';
11
- import { formatNumber } from '@subwallet/extension-base/utils';
12
- import BigNumber from 'bignumber.js';
16
+ import { _reformatAddressWithChain, balanceFormatter, formatNumber } from '@subwallet/extension-base/utils';
17
+ import BigN from 'bignumber.js';
13
18
  import { t } from 'i18next';
19
+ import { isEthereumAddress } from '@polkadot/util-crypto';
14
20
  export class SwapBaseHandler {
15
21
  constructor({
16
22
  balanceService,
@@ -30,7 +36,30 @@ export class SwapBaseHandler {
30
36
  async generateOptimalProcess(params, genStepFuncList) {
31
37
  const result = {
32
38
  totalFee: [MOCK_STEP_FEE],
33
- steps: [DEFAULT_FIRST_STEP]
39
+ steps: [DEFAULT_FIRST_STEP],
40
+ path: []
41
+ };
42
+ try {
43
+ for (const genStepFunc of genStepFuncList) {
44
+ const step = await genStepFunc(params);
45
+ if (step) {
46
+ result.steps.push({
47
+ id: result.steps.length,
48
+ ...step[0]
49
+ });
50
+ result.totalFee.push(step[1]);
51
+ }
52
+ }
53
+ return result;
54
+ } catch (e) {
55
+ return result;
56
+ }
57
+ }
58
+ async generateOptimalProcessV2(params, genStepFuncList) {
59
+ const result = {
60
+ totalFee: [MOCK_STEP_FEE],
61
+ steps: [DEFAULT_FIRST_STEP],
62
+ path: params.path
34
63
  };
35
64
  try {
36
65
  for (const genStepFunc of genStepFuncList) {
@@ -49,7 +78,7 @@ export class SwapBaseHandler {
49
78
  }
50
79
  }
51
80
  async validateXcmStep(params, stepIndex) {
52
- const bnAmount = new BigNumber(params.selectedQuote.fromAmount);
81
+ const bnAmount = new BigN(params.selectedQuote.fromAmount);
53
82
  const swapPair = params.selectedQuote.pair;
54
83
  const alternativeAssetSlug = getSwapAlternativeAsset(swapPair);
55
84
  if (!alternativeAssetSlug) {
@@ -58,18 +87,18 @@ export class SwapBaseHandler {
58
87
  const alternativeAsset = this.chainService.getAssetBySlug(alternativeAssetSlug);
59
88
  const fromAsset = this.chainService.getAssetBySlug(swapPair.from);
60
89
  const [alternativeAssetBalance, fromAssetBalance] = await Promise.all([this.balanceService.getTransferableBalance(params.address, alternativeAsset.originChain, alternativeAssetSlug), this.balanceService.getTransferableBalance(params.address, fromAsset.originChain, fromAsset.slug)]);
61
- const bnAlternativeAssetBalance = new BigNumber(alternativeAssetBalance.value);
62
- const bnFromAssetBalance = new BigNumber(fromAssetBalance.value);
90
+ const bnAlternativeAssetBalance = new BigN(alternativeAssetBalance.value);
91
+ const bnFromAssetBalance = new BigN(fromAssetBalance.value);
63
92
  const xcmFeeComponent = params.process.totalFee[stepIndex].feeComponent[0]; // todo: can do better than indexing
64
- const xcmFee = new BigNumber(xcmFeeComponent.amount || '0');
93
+ const xcmFee = new BigN(xcmFeeComponent.amount || '0');
65
94
  let xcmAmount = bnAmount.minus(bnFromAssetBalance);
66
- let editedXcmFee = new BigNumber(0);
95
+ let editedXcmFee = new BigN(0);
67
96
  if (_isNativeToken(alternativeAsset)) {
68
97
  xcmAmount = xcmAmount.plus(xcmFee);
69
98
  editedXcmFee = xcmFee.times(2);
70
99
  }
71
100
  if (!bnAlternativeAssetBalance.minus(_isNativeToken(alternativeAsset) ? xcmAmount.plus(xcmFee) : xcmFee).gt(0)) {
72
- const maxBn = bnFromAssetBalance.plus(new BigNumber(alternativeAssetBalance.value)).minus(_isNativeToken(alternativeAsset) ? editedXcmFee : xcmFee);
101
+ const maxBn = bnFromAssetBalance.plus(new BigN(alternativeAssetBalance.value)).minus(_isNativeToken(alternativeAsset) ? editedXcmFee : xcmFee);
73
102
  const maxValue = formatNumber(maxBn.toString(), fromAsset.decimals || 0);
74
103
  const altInputTokenInfo = this.chainService.getAssetBySlug(alternativeAssetSlug);
75
104
  const symbol = altInputTokenInfo.symbol;
@@ -78,7 +107,7 @@ export class SwapBaseHandler {
78
107
  const inputNetworkName = chain.name;
79
108
  const altNetworkName = alternativeChain.name;
80
109
  const currentValue = formatNumber(bnFromAssetBalance.toString(), fromAsset.decimals || 0);
81
- const bnMaxXCM = new BigNumber(alternativeAssetBalance.value).minus(_isNativeToken(alternativeAsset) ? editedXcmFee : xcmFee);
110
+ const bnMaxXCM = new BigN(alternativeAssetBalance.value).minus(_isNativeToken(alternativeAsset) ? editedXcmFee : xcmFee);
82
111
  const maxXCMValue = formatNumber(bnMaxXCM.toString(), fromAsset.decimals || 0);
83
112
  if (maxBn.lte(0) || bnFromAssetBalance.lte(0) || bnMaxXCM.lte(0)) {
84
113
  return [new TransactionError(BasicTxErrorType.NOT_ENOUGH_BALANCE, t(`Insufficient balance. Deposit ${fromAsset.symbol} and try again.`))];
@@ -96,6 +125,108 @@ export class SwapBaseHandler {
96
125
  }
97
126
  return [];
98
127
  }
128
+ async validateXcmStepV2(params, stepIndex) {
129
+ var _currentFee$feeCompon, _params$recipient;
130
+ const currentStep = params.process.steps[stepIndex];
131
+ const currentFee = params.process.totalFee[stepIndex];
132
+ const feeToken = currentFee.selectedFeeToken || currentFee.defaultFeeToken;
133
+ const feeAmount = (_currentFee$feeCompon = currentFee.feeComponent.find(fee => fee.feeType === SwapFeeType.NETWORK_FEE)) === null || _currentFee$feeCompon === void 0 ? void 0 : _currentFee$feeCompon.amount;
134
+ if (!feeAmount) {
135
+ throw new Error('Fee not found for XCM step');
136
+ }
137
+ const metadata = currentStep.metadata;
138
+ const sendingAmount = metadata.sendingValue;
139
+ const bnAmount = new BigN(sendingAmount);
140
+ const fromAsset = metadata === null || metadata === void 0 ? void 0 : metadata.originTokenInfo;
141
+ const toAsset = metadata === null || metadata === void 0 ? void 0 : metadata.destinationTokenInfo;
142
+ if (!fromAsset || !toAsset) {
143
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
144
+ }
145
+ const fromChain = this.chainService.getChainInfoByKey(fromAsset.originChain);
146
+ const toChain = this.chainService.getChainInfoByKey(toAsset.originChain);
147
+ const toChainNativeAsset = this.chainService.getNativeTokenInfo(toAsset.originChain);
148
+ const sender = _reformatAddressWithChain(params.address, fromChain);
149
+ const receiver = _reformatAddressWithChain((_params$recipient = params.recipient) !== null && _params$recipient !== void 0 ? _params$recipient : sender, toChain);
150
+
151
+ /* Get transferable balance */
152
+ const [fromAssetBalance, feeTokenBalance] = await Promise.all([this.balanceService.getTransferableBalance(sender, fromAsset.originChain, fromAsset.slug, ExtrinsicType.TRANSFER_XCM), this.balanceService.getTransferableBalance(sender, fromAsset.originChain, feeToken, ExtrinsicType.TRANSFER_XCM)]);
153
+ const bnFromAssetBalance = new BigN(fromAssetBalance.value);
154
+ const bnFeeTokenBalance = new BigN(feeTokenBalance.value);
155
+
156
+ /* Compare transferable balance with amount xcm */
157
+ if (bnFromAssetBalance.lt(bnAmount)) {
158
+ return [new TransactionError(BasicTxErrorType.NOT_ENOUGH_BALANCE, t(`Insufficient balance. Deposit ${fromAsset.symbol} and try again.`))];
159
+ }
160
+
161
+ /**
162
+ * Calculate fee token keep alive after xcm
163
+ * If fee token is the same as from token, need to subtract sending amount
164
+ * @TODO: Need to update logic if change fee token (multi with rate)
165
+ * */
166
+ const feeBalanceAfterTransfer = bnFeeTokenBalance.minus(feeAmount).minus(fromAsset.slug === feeToken ? bnAmount : 0);
167
+
168
+ /**
169
+ * Check fee token balance after transfer.
170
+ * Because the balance had subtracted with existence deposit, so only need to check if it's less than 0
171
+ * */
172
+ if (feeBalanceAfterTransfer.lt(0)) {
173
+ return [new TransactionError(BasicTxErrorType.NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t(`Insufficient balance. Deposit ${fromAsset.symbol} and try again.`))];
174
+ }
175
+ const destMinAmount = _getTokenMinAmount(toAsset);
176
+ // TODO: Need to update with new logic, calculate fee to claim on dest chain
177
+ const minSendingRequired = new BigN(destMinAmount).multipliedBy(FEE_RATE_MULTIPLIER.high);
178
+
179
+ // Check sending token ED for receiver
180
+ if (bnAmount.lt(minSendingRequired)) {
181
+ const atLeastStr = formatNumber(minSendingRequired, _getAssetDecimals(toAsset), balanceFormatter, {
182
+ maxNumberFormat: _getAssetDecimals(toAsset) || 6
183
+ });
184
+ return [new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', {
185
+ replace: {
186
+ amount: atLeastStr,
187
+ symbol: fromAsset.symbol
188
+ }
189
+ }))];
190
+ }
191
+
192
+ // Check keepAlive on dest chain for receiver
193
+ if (!_isNativeToken(toAsset)) {
194
+ const toChainApi = this.chainService.getSubstrateApi(toAsset.originChain);
195
+
196
+ // TODO: Need to update, currently only support substrate xcm
197
+ if (!toChainApi) {
198
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR, t('Destination chain is not active'))];
199
+ }
200
+ const isSendingTokenSufficient = await _isSufficientToken(toAsset, toChainApi);
201
+ if (!isSendingTokenSufficient) {
202
+ const toChainNativeAssetBalance = await this.balanceService.getTotalBalance(receiver, toAsset.originChain, toChainNativeAsset.slug, ExtrinsicType.TRANSFER_BALANCE);
203
+ const isReceiverAliveByNativeToken = _isAccountActive(toChainNativeAssetBalance.metadata);
204
+ if (!isReceiverAliveByNativeToken) {
205
+ // TODO: Update message
206
+ return [new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has less than {{amount}} {{nativeSymbol}}, which can lead to your {{localSymbol}} being lost. Change recipient account and try again', {
207
+ replace: {
208
+ amount: toChainNativeAssetBalance.value,
209
+ nativeSymbol: toChainNativeAsset.symbol,
210
+ localSymbol: toAsset.symbol
211
+ }
212
+ }))];
213
+ }
214
+ }
215
+ }
216
+
217
+ // SKIP: BECAUSE CURRENTLY NOT SUPPORT SNOWBRIDGE FOR SWAP FEATURE
218
+ // check native token ED on dest chain for receiver
219
+ // const bnKeepAliveBalance = _isNativeToken(destinationTokenInfo) ? new BigN(receiverNativeBalance).plus(sendingAmount) : new BigN(receiverNativeBalance);
220
+ //
221
+ // if (isSnowBridge && bnKeepAliveBalance.lt(_getChainExistentialDeposit(destChainInfo))) {
222
+ // const { decimals, symbol } = _getChainNativeTokenBasicInfo(destChainInfo);
223
+ // const atLeastStr = formatNumber(_getChainExistentialDeposit(destChainInfo), decimals || 0, balanceFormatter, { maxNumberFormat: 6 });
224
+ //
225
+ // error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t(' Insufficient {{symbol}} on {{chain}} to cover min balance ({{amount}} {{symbol}})', { replace: { amount: atLeastStr, symbol, chain: destChainInfo.name } }));
226
+ // }
227
+
228
+ return [];
229
+ }
99
230
  async validateTokenApproveStep(params, stepIndex) {
100
231
  return Promise.resolve([]);
101
232
  }
@@ -107,8 +238,8 @@ export class SwapBaseHandler {
107
238
  const feeAmount = feeInfo.feeComponent[0];
108
239
  const feeTokenInfo = this.chainService.getAssetBySlug(feeInfo.defaultFeeToken);
109
240
  const feeTokenBalance = await this.balanceService.getTransferableBalance(params.address, feeTokenInfo.originChain, feeTokenInfo.slug);
110
- const bnFeeTokenBalance = new BigNumber(feeTokenBalance.value);
111
- const bnFeeAmount = new BigNumber(feeAmount.amount);
241
+ const bnFeeTokenBalance = new BigN(feeTokenBalance.value);
242
+ const bnFeeAmount = new BigN(feeAmount.amount);
112
243
  if (bnFeeAmount.gte(bnFeeTokenBalance)) {
113
244
  return Promise.resolve([new TransactionError(BasicTxErrorType.NOT_ENOUGH_BALANCE)]);
114
245
  }
@@ -154,6 +285,189 @@ export class SwapBaseHandler {
154
285
  }
155
286
  return Promise.resolve([]);
156
287
  }
288
+ async validateBridgeStep(receiver, fromToken, toToken, selectedFeeToken, toChainNativeToken, bnBridgeAmount, bnFromTokenBalance, bnBridgeFeeAmount, bnFeeTokenBalance, bnBridgeDeliveryFee) {
289
+ const minBridgeAmountRequired = new BigN(_getTokenMinAmount(toToken)).multipliedBy(FEE_RATE_MULTIPLIER.high);
290
+ const spendingAndFeePaymentValidation = validateSpendingAndFeePayment(fromToken, selectedFeeToken, bnBridgeAmount, bnFromTokenBalance, bnBridgeFeeAmount, bnFeeTokenBalance);
291
+ if (spendingAndFeePaymentValidation.length > 0) {
292
+ return spendingAndFeePaymentValidation;
293
+ }
294
+ if (bnBridgeAmount.lte(minBridgeAmountRequired.plus(bnBridgeDeliveryFee))) {
295
+ const atLeastStr = formatNumber(minBridgeAmountRequired.plus(bnBridgeDeliveryFee), _getAssetDecimals(toToken), balanceFormatter, {
296
+ maxNumberFormat: _getAssetDecimals(toToken) || 6
297
+ });
298
+ return [new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', {
299
+ replace: {
300
+ amount: atLeastStr,
301
+ symbol: fromToken.symbol
302
+ }
303
+ }))];
304
+ }
305
+
306
+ // By here, we know that the user is receiving a valid amount of toToken
307
+ const toChainApi = this.chainService.getSubstrateApi(toToken.originChain);
308
+ if (!toChainApi) {
309
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
310
+ }
311
+
312
+ // Only need to check if account is alive with the receiving toToken
313
+ const isToTokenSufficient = await _isSufficientToken(toToken, toChainApi);
314
+ if (!isToTokenSufficient && !_isNativeToken(toToken)) {
315
+ // sending token cannot keep account alive, must check with native token
316
+ const toChainNativeTokenBalance = await this.balanceService.getTotalBalance(receiver, toToken.originChain, toChainNativeToken.slug, ExtrinsicType.TRANSFER_BALANCE);
317
+ if (!_isAccountActive(toChainNativeTokenBalance.metadata)) {
318
+ return [new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has less than {{amount}} {{nativeSymbol}}, which can lead to your {{localSymbol}} being lost. Change recipient account and try again', {
319
+ replace: {
320
+ amount: toChainNativeTokenBalance.value,
321
+ nativeSymbol: toChainNativeToken.symbol,
322
+ localSymbol: toToken.symbol
323
+ }
324
+ }))];
325
+ }
326
+ }
327
+ return [];
328
+ }
329
+ validateSwapStepV2(swapToChain, swapToken, receivingToken, swapFeeToken, bnSwapValue, bnExpectedReceivingAmount, bnSwapFromTokenBalance, bnSwapFeeAmount, bnSwapFeeTokenBalance, recipient) {
330
+ const spendingAndFeePaymentValidation = validateSpendingAndFeePayment(swapToken, swapFeeToken, bnSwapValue, bnSwapFromTokenBalance, bnSwapFeeAmount, bnSwapFeeTokenBalance);
331
+ if (spendingAndFeePaymentValidation.length > 0) {
332
+ return spendingAndFeePaymentValidation;
333
+ }
334
+ if (bnExpectedReceivingAmount.lte(_getTokenMinAmount(receivingToken))) {
335
+ const atLeastStr = formatNumber(_getTokenMinAmount(receivingToken), _getAssetDecimals(receivingToken), balanceFormatter, {
336
+ maxNumberFormat: _getAssetDecimals(receivingToken) || 6
337
+ });
338
+ return [new TransactionError(SwapErrorType.NOT_MEET_MIN_SWAP, t('You can\'t receive less than {{number}} {{symbol}}', {
339
+ replace: {
340
+ number: atLeastStr,
341
+ symbol: _getAssetSymbol(receivingToken)
342
+ }
343
+ }))];
344
+ }
345
+ if (recipient) {
346
+ const isEvmAddress = isEthereumAddress(recipient);
347
+ const isEvmDestChain = _isChainEvmCompatible(swapToChain);
348
+ if (isEvmAddress && !isEvmDestChain || !isEvmAddress && isEvmDestChain) {
349
+ // todo: update this condition
350
+ return [new TransactionError(SwapErrorType.INVALID_RECIPIENT)];
351
+ }
352
+ }
353
+ return [];
354
+ }
355
+ async validateSwapOnlyProcess(params, swapIndex) {
356
+ const swapStepInfo = params.process.steps[swapIndex];
357
+ const swapMetadata = swapStepInfo.metadata; // todo
358
+ const swapFee = params.process.totalFee[swapIndex];
359
+ if (!swapMetadata || !swapMetadata.destinationTokenInfo || !swapMetadata.originTokenInfo || !swapMetadata.sendingValue) {
360
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
361
+ }
362
+
363
+ // Validate quote
364
+ if (!params.selectedQuote) {
365
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
366
+ }
367
+ if (params.selectedQuote.aliveUntil <= +Date.now()) {
368
+ return [new TransactionError(SwapErrorType.QUOTE_TIMEOUT)];
369
+ }
370
+ const swapNetworkFee = swapFee.feeComponent.find(fee => fee.feeType === SwapFeeType.NETWORK_FEE);
371
+ if (!swapNetworkFee) {
372
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
373
+ }
374
+ const swapToken = swapMetadata.originTokenInfo;
375
+ const swapReceivingToken = swapMetadata.destinationTokenInfo;
376
+ const bnSwapReceivingAmount = BigN(params.selectedQuote.toAmount);
377
+ const bnSwapValue = BigN(swapMetadata.sendingValue);
378
+ const bnSwapFeeAmount = BigN(swapNetworkFee.amount);
379
+ const swapFeeToken = this.chainService.getAssetBySlug(swapFee.selectedFeeToken || swapFee.defaultFeeToken);
380
+ const swapToChain = this.chainService.getChainInfoByKey(swapMetadata.destinationTokenInfo.originChain);
381
+ const [swapFeeTokenBalance, swapFromTokenBalance] = await Promise.all([this.balanceService.getTransferableBalance(params.address, swapFeeToken.originChain, swapFeeToken.slug, ExtrinsicType.SWAP), this.balanceService.getTransferableBalance(params.address, swapToken.originChain, swapToken.slug, ExtrinsicType.SWAP)]);
382
+ const bnSwapFromTokenBalance = BigN(swapFromTokenBalance.value);
383
+ const bnSwapFeeTokenBalance = BigN(swapFeeTokenBalance.value);
384
+ return this.validateSwapStepV2(swapToChain, swapToken, swapReceivingToken, swapFeeToken, bnSwapValue, bnSwapReceivingAmount, bnSwapFromTokenBalance, bnSwapFeeAmount, bnSwapFeeTokenBalance, params.recipient);
385
+ }
386
+ async validateXcmSwapProcess(params, swapIndex, xcmIndex) {
387
+ var _currentFee$feeCompon2, _params$recipient2;
388
+ // Bridge
389
+ const currentStep = params.process.steps[xcmIndex];
390
+ const xcmMetadata = currentStep.metadata;
391
+ const currentFee = params.process.totalFee[xcmIndex];
392
+ const bridgeFeeAmount = (_currentFee$feeCompon2 = currentFee.feeComponent.find(fee => fee.feeType === SwapFeeType.NETWORK_FEE)) === null || _currentFee$feeCompon2 === void 0 ? void 0 : _currentFee$feeCompon2.amount;
393
+ if (!xcmMetadata || !xcmMetadata.destinationTokenInfo || !xcmMetadata.originTokenInfo || !xcmMetadata.sendingValue) {
394
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
395
+ }
396
+ if (!bridgeFeeAmount) {
397
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
398
+ }
399
+ const bridgeFromToken = xcmMetadata.originTokenInfo;
400
+ const bridgeToToken = xcmMetadata.destinationTokenInfo;
401
+ const fromChain = this.chainService.getChainInfoByKey(bridgeFromToken.originChain);
402
+ const toChain = this.chainService.getChainInfoByKey(bridgeToToken.originChain);
403
+ if (_isSnowBridgeXcm(fromChain, toChain)) {
404
+ return [new TransactionError(BasicTxErrorType.UNSUPPORTED)];
405
+ }
406
+ const bnBridgeFeeAmount = BigN(bridgeFeeAmount);
407
+ const bnBridgeAmount = new BigN(xcmMetadata.sendingValue);
408
+ const bridgeToChainNativeToken = this.chainService.getNativeTokenInfo(bridgeToToken.originChain);
409
+ const bridgeSelectedFeeToken = this.chainService.getAssetBySlug(currentFee.selectedFeeToken || currentFee.defaultFeeToken);
410
+ const bnBridgeDeliveryFee = BigN(0); // todo
411
+
412
+ const bridgeSender = _reformatAddressWithChain(params.address, this.chainService.getChainInfoByKey(bridgeFromToken.originChain));
413
+ const bridgeReceiver = _reformatAddressWithChain((_params$recipient2 = params.recipient) !== null && _params$recipient2 !== void 0 ? _params$recipient2 : bridgeSender, this.chainService.getChainInfoByKey(bridgeToToken.originChain));
414
+ const [bridgeFromTokenBalance, bridgeFeeTokenBalance] = await Promise.all([this.balanceService.getTransferableBalance(bridgeSender, bridgeFromToken.originChain, bridgeFromToken.slug, ExtrinsicType.TRANSFER_XCM), this.balanceService.getTransferableBalance(bridgeSender, bridgeFromToken.originChain, bridgeSelectedFeeToken.slug, ExtrinsicType.TRANSFER_XCM)]);
415
+
416
+ // Native token balance has already accounted for ED aka strict mode
417
+ const bnBridgeFromTokenBalance = new BigN(bridgeFromTokenBalance.value);
418
+ const bnBridgeFeeTokenBalance = new BigN(bridgeFeeTokenBalance.value);
419
+ const bridgeStepValidation = await this.validateBridgeStep(bridgeReceiver, bridgeFromToken, bridgeToToken, bridgeSelectedFeeToken, bridgeToChainNativeToken, bnBridgeAmount, bnBridgeFromTokenBalance, bnBridgeFeeAmount, bnBridgeFeeTokenBalance, bnBridgeDeliveryFee);
420
+ if (bridgeStepValidation.length > 0) {
421
+ return bridgeStepValidation;
422
+ }
423
+
424
+ // Swap
425
+ const swapStepInfo = params.process.steps[swapIndex];
426
+ const swapMetadata = swapStepInfo.metadata; // todo
427
+ const swapFee = params.process.totalFee[swapIndex];
428
+ if (!swapMetadata || !swapMetadata.destinationTokenInfo || !swapMetadata.originTokenInfo || !swapMetadata.sendingValue) {
429
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
430
+ }
431
+
432
+ // Validate quote
433
+ if (!params.selectedQuote) {
434
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
435
+ }
436
+ if (params.selectedQuote.aliveUntil <= +Date.now()) {
437
+ return [new TransactionError(SwapErrorType.QUOTE_TIMEOUT)];
438
+ }
439
+ const swapNetworkFee = swapFee.feeComponent.find(fee => fee.feeType === SwapFeeType.NETWORK_FEE);
440
+ if (!swapNetworkFee) {
441
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
442
+ }
443
+ const swapToken = swapMetadata.originTokenInfo;
444
+ const swapReceivingToken = swapMetadata.destinationTokenInfo;
445
+ const bnSwapReceivingAmount = BigN(params.selectedQuote.toAmount);
446
+ if (swapToken.slug !== bridgeToToken.slug) {
447
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
448
+ }
449
+ const bnSwapValue = BigN(swapMetadata.sendingValue);
450
+ const bnSwapFeeAmount = BigN(swapNetworkFee.amount);
451
+ if (bnSwapValue.gt(bnBridgeAmount)) {
452
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
453
+ }
454
+ if (bnSwapValue.lte(_getTokenMinAmount(swapToken))) {
455
+ const atLeastString = formatNumber(_getTokenMinAmount(swapToken), _getAssetDecimals(swapToken), balanceFormatter, {
456
+ maxNumberFormat: _getAssetDecimals(swapToken) || 6
457
+ });
458
+ return [new TransactionError(SwapErrorType.NOT_MEET_MIN_SWAP, t(`Swap amount too small. Increase to more than ${atLeastString} ${_getAssetSymbol(swapToken)} and try again`))];
459
+ }
460
+ const swapFeeToken = this.chainService.getAssetBySlug(swapFee.selectedFeeToken || swapFee.defaultFeeToken);
461
+ const swapToChain = this.chainService.getChainInfoByKey(swapMetadata.destinationTokenInfo.originChain);
462
+ const [swapFeeTokenBalance, swapFromTokenBalance] = await Promise.all([this.balanceService.getTransferableBalance(params.address, swapFeeToken.originChain, swapFeeToken.slug, ExtrinsicType.SWAP), this.balanceService.getTransferableBalance(params.address, swapToken.originChain, swapToken.slug, ExtrinsicType.SWAP)]);
463
+ const bnSwapFromTokenBalance = BigN(swapFromTokenBalance.value).plus(bnBridgeAmount);
464
+ const bnSwapFeeTokenBalance = BigN(swapFeeTokenBalance.value);
465
+ const swapStepValidation = this.validateSwapStepV2(swapToChain, swapToken, swapReceivingToken, swapFeeToken, bnSwapValue, bnSwapReceivingAmount, bnSwapFromTokenBalance, bnSwapFeeAmount, bnSwapFeeTokenBalance, params.recipient);
466
+ if (swapStepValidation.length > 0) {
467
+ return swapStepValidation;
468
+ }
469
+ return [];
470
+ }
157
471
  get name() {
158
472
  return this.providerName;
159
473
  }
@@ -4,8 +4,7 @@ import { BalanceService } from '@subwallet/extension-base/services/balance-servi
4
4
  import { ChainService } from '@subwallet/extension-base/services/chain-service';
5
5
  import FeeService from '@subwallet/extension-base/services/fee-service/service';
6
6
  import { SwapBaseInterface } from '@subwallet/extension-base/services/swap-service/handler/base-handler';
7
- import { BaseStepDetail, CommonOptimalPath, CommonStepFeeInfo } from '@subwallet/extension-base/types/service-base';
8
- import { OptimalSwapPathParams, SwapProviderId, SwapSubmitParams, SwapSubmitStepData, ValidateSwapProcessParams } from '@subwallet/extension-base/types/swap';
7
+ import { BaseStepDetail, CommonOptimalSwapPath, CommonStepFeeInfo, OptimalSwapPathParams, OptimalSwapPathParamsV2, SwapProviderId, SwapSubmitParams, SwapSubmitStepData, ValidateSwapProcessParams } from '@subwallet/extension-base/types';
9
8
  export declare const CHAINFLIP_BROKER_API: string;
10
9
  export declare class ChainflipSwapHandler implements SwapBaseInterface {
11
10
  private readonly isTestnet;
@@ -23,5 +22,7 @@ export declare class ChainflipSwapHandler implements SwapBaseInterface {
23
22
  handleSubmitStep(params: SwapSubmitParams): Promise<SwapSubmitStepData>;
24
23
  handleSwapProcess(params: SwapSubmitParams): Promise<SwapSubmitStepData>;
25
24
  getSubmitStep(params: OptimalSwapPathParams): Promise<[BaseStepDetail, CommonStepFeeInfo] | undefined>;
26
- generateOptimalProcess(params: OptimalSwapPathParams): Promise<CommonOptimalPath>;
25
+ generateOptimalProcess(params: OptimalSwapPathParams): Promise<CommonOptimalSwapPath>;
26
+ generateOptimalProcessV2(params: OptimalSwapPathParamsV2): Promise<CommonOptimalSwapPath>;
27
+ validateSwapProcessV2(params: ValidateSwapProcessParams): Promise<TransactionError[]>;
27
28
  }
@@ -8,10 +8,9 @@ import { getERC20TransactionObject, getEVMTransactionObject } from '@subwallet/e
8
8
  import { createSubstrateExtrinsic } from '@subwallet/extension-base/services/balance-service/transfer/token';
9
9
  import { _getAssetSymbol, _getContractAddressOfToken, _isChainSubstrateCompatible, _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils';
10
10
  import { SwapBaseHandler } from '@subwallet/extension-base/services/swap-service/handler/base-handler';
11
+ import { DynamicSwapType } from '@subwallet/extension-base/services/swap-service/interface';
11
12
  import { getChainflipSwap } from '@subwallet/extension-base/services/swap-service/utils';
12
- import { BasicTxErrorType } from '@subwallet/extension-base/types';
13
- import { CommonStepType } from '@subwallet/extension-base/types/service-base';
14
- import { SwapProviderId, SwapStepType } from '@subwallet/extension-base/types/swap';
13
+ import { BasicTxErrorType, CommonStepType, SwapProviderId, SwapStepType } from '@subwallet/extension-base/types';
15
14
  import { _reformatAddressWithChain } from '@subwallet/extension-base/utils';
16
15
  import { getId } from '@subwallet/extension-base/utils/getId';
17
16
  import BigNumber from 'bignumber.js';
@@ -97,9 +96,19 @@ export class ChainflipSwapHandler {
97
96
  const fromAssetId = _getAssetSymbol(fromAsset);
98
97
  const toAssetId = _getAssetSymbol(toAsset);
99
98
  const minReceive = new BigNumber(quote.rate).times(1 - slippage).toString();
99
+ const processMetadata = params.process.steps[params.currentStep].metadata;
100
+ const quoteMetadata = params.quote.metadata;
101
+ if (!processMetadata || !quoteMetadata) {
102
+ throw new Error('Metadata for Chainflip not found');
103
+ }
104
+ if (processMetadata.destChain !== quoteMetadata.destChain || processMetadata.srcChain !== quoteMetadata.srcChain) {
105
+ throw new Error('Metadata for Chainflip not found');
106
+ }
100
107
  const depositParams = {
108
+ sourceChain: processMetadata.srcChain,
101
109
  destinationAddress: receiver,
102
110
  destinationAsset: toAssetId,
111
+ destinationChain: processMetadata.destChain,
103
112
  minimumPrice: minReceive,
104
113
  // minimum accepted price for swaps through the channel
105
114
  refundAddress: address,
@@ -113,6 +122,9 @@ export class ChainflipSwapHandler {
113
122
  method: 'GET'
114
123
  });
115
124
  const data = await response.json();
125
+ if (!data.id || !data.address || data.address === '' || !data.issuedBlock || !data.network || !data.channelId) {
126
+ throw new Error('Error get Chainflip data');
127
+ }
116
128
  const depositChannelId = `${data.issuedBlock}-${data.network}-${data.channelId}`;
117
129
  const depositAddress = data.address;
118
130
  const txData = {
@@ -194,16 +206,68 @@ export class ChainflipSwapHandler {
194
206
  }
195
207
  }
196
208
  async getSubmitStep(params) {
197
- if (params.selectedQuote) {
198
- const submitStep = {
199
- name: 'Swap',
200
- type: SwapStepType.SWAP
201
- };
202
- return Promise.resolve([submitStep, params.selectedQuote.feeInfo]);
209
+ var _params$selectedQuote;
210
+ const metadata = (_params$selectedQuote = params.selectedQuote) === null || _params$selectedQuote === void 0 ? void 0 : _params$selectedQuote.metadata;
211
+ if (!params.selectedQuote) {
212
+ return Promise.resolve(undefined);
213
+ }
214
+ if (!metadata || !metadata.srcChain || !metadata.destChain) {
215
+ return Promise.resolve(undefined);
203
216
  }
204
- return Promise.resolve(undefined);
217
+ const submitStep = {
218
+ name: 'Swap',
219
+ type: SwapStepType.SWAP,
220
+ metadata: {
221
+ sendingValue: params.request.fromAmount.toString(),
222
+ originTokenInfo: this.chainService.getAssetBySlug(params.selectedQuote.pair.from),
223
+ destinationTokenInfo: this.chainService.getAssetBySlug(params.selectedQuote.pair.to),
224
+ srcChain: metadata.srcChain,
225
+ destChain: metadata.destChain
226
+ }
227
+ };
228
+ return Promise.resolve([submitStep, params.selectedQuote.feeInfo]);
205
229
  }
206
230
  generateOptimalProcess(params) {
207
231
  return this.swapBaseHandler.generateOptimalProcess(params, [this.getSubmitStep.bind(this)]);
208
232
  }
233
+ generateOptimalProcessV2(params) {
234
+ return this.swapBaseHandler.generateOptimalProcessV2(params, [this.getSubmitStep.bind(this)]);
235
+ }
236
+ async validateSwapProcessV2(params) {
237
+ // todo: recheck address and recipient format in params
238
+ const {
239
+ process,
240
+ selectedQuote
241
+ } = params; // todo: review flow, currentStep param.
242
+
243
+ // todo: validate path with optimalProcess
244
+ // todo: review error message in case many step swap
245
+ if (BigNumber(selectedQuote.fromAmount).lte(0)) {
246
+ return [new TransactionError(BasicTxErrorType.INVALID_PARAMS, 'Amount must be greater than 0')];
247
+ }
248
+ const actionList = JSON.stringify(process.path.map(step => step.action));
249
+ const swap = actionList === JSON.stringify([DynamicSwapType.SWAP]);
250
+ const swapXcm = actionList === JSON.stringify([DynamicSwapType.SWAP, DynamicSwapType.BRIDGE]);
251
+ const xcmSwap = actionList === JSON.stringify([DynamicSwapType.BRIDGE, DynamicSwapType.SWAP]);
252
+ const xcmSwapXcm = actionList === JSON.stringify([DynamicSwapType.BRIDGE, DynamicSwapType.SWAP, DynamicSwapType.BRIDGE]);
253
+ const swapIndex = params.process.steps.findIndex(step => step.type === SwapStepType.SWAP); // todo
254
+
255
+ if (swapIndex <= -1) {
256
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
257
+ }
258
+ if (swap) {
259
+ return this.swapBaseHandler.validateSwapOnlyProcess(params, swapIndex); // todo: create interface for input request
260
+ }
261
+
262
+ if (swapXcm) {
263
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
264
+ }
265
+ if (xcmSwap) {
266
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
267
+ }
268
+ if (xcmSwapXcm) {
269
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
270
+ }
271
+ return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
272
+ }
209
273
  }
@@ -3,8 +3,9 @@ import { BalanceService } from '@subwallet/extension-base/services/balance-servi
3
3
  import { ChainService } from '@subwallet/extension-base/services/chain-service';
4
4
  import FeeService from '@subwallet/extension-base/services/fee-service/service';
5
5
  import { SwapBaseInterface } from '@subwallet/extension-base/services/swap-service/handler/base-handler';
6
- import { BaseStepDetail, CommonOptimalPath, CommonStepFeeInfo } from '@subwallet/extension-base/types/service-base';
7
- import { OptimalSwapPathParams, SwapProviderId, SwapSubmitParams, SwapSubmitStepData, ValidateSwapProcessParams } from '@subwallet/extension-base/types/swap';
6
+ import { OptimalSwapPathParamsV2, ValidateSwapProcessParams } from '@subwallet/extension-base/types';
7
+ import { BaseStepDetail, CommonOptimalSwapPath, CommonStepFeeInfo } from '@subwallet/extension-base/types/service-base';
8
+ import { OptimalSwapPathParams, SwapProviderId, SwapSubmitParams, SwapSubmitStepData } from '@subwallet/extension-base/types/swap';
8
9
  export declare class HydradxHandler implements SwapBaseInterface {
9
10
  private swapBaseHandler;
10
11
  private tradeRouter;
@@ -22,12 +23,16 @@ export declare class HydradxHandler implements SwapBaseInterface {
22
23
  getXcmStep(params: OptimalSwapPathParams): Promise<[BaseStepDetail, CommonStepFeeInfo] | undefined>;
23
24
  getFeeOptionStep(params: OptimalSwapPathParams): Promise<[BaseStepDetail, CommonStepFeeInfo] | undefined>;
24
25
  getSubmitStep(params: OptimalSwapPathParams): Promise<[BaseStepDetail, CommonStepFeeInfo] | undefined>;
25
- generateOptimalProcess(params: OptimalSwapPathParams): Promise<CommonOptimalPath>;
26
+ getXcmStepV2(params: OptimalSwapPathParamsV2): Promise<[BaseStepDetail, CommonStepFeeInfo] | undefined>;
27
+ getSwapStepV2(params: OptimalSwapPathParamsV2): Promise<[BaseStepDetail, CommonStepFeeInfo] | undefined>;
28
+ generateOptimalProcess(params: OptimalSwapPathParams): Promise<CommonOptimalSwapPath>;
29
+ generateOptimalProcessV2(params: OptimalSwapPathParamsV2): Promise<CommonOptimalSwapPath>;
26
30
  handleXcmStep(params: SwapSubmitParams): Promise<SwapSubmitStepData>;
27
31
  handleSetFeeStep(params: SwapSubmitParams): Promise<SwapSubmitStepData>;
28
32
  handleSubmitStep(params: SwapSubmitParams): Promise<SwapSubmitStepData>;
29
33
  handleSwapProcess(params: SwapSubmitParams): Promise<SwapSubmitStepData>;
30
34
  validateSwapProcess(params: ValidateSwapProcessParams): Promise<TransactionError[]>;
35
+ validateSwapProcessV2(params: ValidateSwapProcessParams): Promise<TransactionError[]>;
31
36
  get referralCode(): "WALLET" | "ASSETHUB";
32
37
  get referralAccount(): "7PCsCpkgsHdNaZhv79wCCQ5z97uxVbSeSCtDMUa1eZHKXy4a" | "7LCt6dFqtxzdKVB2648jWW9d85doiFfLSbZJDNAMVJNxh5rJ";
33
38
  }