@subwallet/extension-base 1.3.10-0 → 1.3.12-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/KoniTypes.d.ts +3 -0
- package/background/errors/SwapError.js +5 -0
- package/cjs/background/errors/SwapError.js +5 -0
- package/cjs/core/logic-validation/swap.js +27 -0
- package/cjs/core/substrate/xcm-parser.js +16 -2
- package/cjs/koni/api/contract-handler/utils/index.js +29 -1
- package/cjs/koni/background/handlers/Extension.js +16 -4
- package/cjs/packageInfo.js +1 -1
- package/cjs/services/balance-service/transfer/smart-contract.js +2 -0
- package/cjs/services/balance-service/transfer/xcm/index.js +6 -6
- package/cjs/services/balance-service/transfer/xcm/polygonBridge.js +2 -2
- package/cjs/services/balance-service/transfer/xcm/posBridge.js +144 -0
- package/cjs/services/chain-service/utils/patch.js +1 -1
- package/cjs/services/earning-service/constants/chains.js +4 -2
- package/cjs/services/earning-service/handlers/liquid-staking/bifrost-manta.js +2 -1
- package/cjs/services/earning-service/handlers/liquid-staking/bifrost.js +2 -2
- package/cjs/services/inapp-notification-service/index.js +2 -0
- package/cjs/services/inapp-notification-service/utils/polygon.js +1 -1
- package/cjs/services/keyring-service/context/handlers/Ledger.js +3 -1
- package/cjs/services/swap-service/handler/chainflip-handler.js +0 -1
- package/cjs/services/swap-service/handler/simpleswap-handler.js +444 -0
- package/cjs/services/swap-service/index.js +15 -3
- package/cjs/services/swap-service/utils.js +13 -2
- package/cjs/services/transaction-service/index.js +14 -10
- package/cjs/services/transaction-service/utils.js +12 -1
- package/cjs/types/swap/index.js +7 -3
- package/cjs/utils/number.js +10 -0
- package/constants/staking.d.ts +4 -0
- package/core/logic-validation/swap.d.ts +2 -1
- package/core/logic-validation/swap.js +26 -0
- package/core/substrate/xcm-parser.d.ts +1 -0
- package/core/substrate/xcm-parser.js +15 -2
- package/koni/api/contract-handler/utils/index.d.ts +4 -0
- package/koni/api/contract-handler/utils/index.js +24 -0
- package/koni/api/contract-handler/utils/pos_bridge_abi.json +783 -0
- package/koni/api/contract-handler/utils/pos_bridge_l2_abi.json +793 -0
- package/koni/background/handlers/Extension.js +18 -6
- package/package.json +18 -6
- package/packageInfo.js +1 -1
- package/services/balance-service/transfer/smart-contract.js +2 -0
- package/services/balance-service/transfer/xcm/index.js +7 -7
- package/services/balance-service/transfer/xcm/polygonBridge.js +2 -2
- package/services/balance-service/transfer/xcm/posBridge.d.ts +14 -0
- package/services/balance-service/transfer/xcm/posBridge.js +131 -0
- package/services/chain-service/utils/patch.js +1 -1
- package/services/earning-service/constants/chains.d.ts +1 -0
- package/services/earning-service/constants/chains.js +2 -1
- package/services/earning-service/handlers/liquid-staking/bifrost-manta.js +2 -1
- package/services/earning-service/handlers/liquid-staking/bifrost.js +3 -3
- package/services/inapp-notification-service/index.js +2 -0
- package/services/inapp-notification-service/interfaces.d.ts +7 -6
- package/services/inapp-notification-service/utils/polygon.d.ts +6 -6
- package/services/inapp-notification-service/utils/polygon.js +1 -1
- package/services/keyring-service/context/handlers/Ledger.js +3 -1
- package/services/swap-service/handler/chainflip-handler.js +0 -1
- package/services/swap-service/handler/simpleswap-handler.d.ts +24 -0
- package/services/swap-service/handler/simpleswap-handler.js +434 -0
- package/services/swap-service/index.d.ts +2 -1
- package/services/swap-service/index.js +13 -2
- package/services/swap-service/utils.d.ts +2 -0
- package/services/swap-service/utils.js +10 -1
- package/services/transaction-service/index.js +15 -11
- package/services/transaction-service/utils.d.ts +2 -1
- package/services/transaction-service/utils.js +12 -2
- package/types/account/info/keyring.d.ts +2 -0
- package/types/swap/index.d.ts +15 -3
- package/types/swap/index.js +5 -2
- package/utils/number.d.ts +9 -0
- package/utils/number.js +10 -0
|
@@ -0,0 +1,434 @@
|
|
|
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 { ChainType, ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
|
|
7
|
+
import { _getSimpleSwapEarlyValidationError } from '@subwallet/extension-base/core/logic-validation/swap';
|
|
8
|
+
import { _getAssetDecimals, _getChainNativeTokenSlug, _getContractAddressOfToken, _isChainSubstrateCompatible, _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils';
|
|
9
|
+
import { BasicTxErrorType, CommonStepType, SwapErrorType, SwapFeeType, SwapProviderId, SwapStepType } from '@subwallet/extension-base/types';
|
|
10
|
+
import { _reformatAddressWithChain, formatNumber } from '@subwallet/extension-base/utils';
|
|
11
|
+
import BigN, { BigNumber } from 'bignumber.js';
|
|
12
|
+
import { getERC20TransactionObject, getEVMTransactionObject } from "../../balance-service/transfer/smart-contract.js";
|
|
13
|
+
import { createTransferExtrinsic, getTransferMockTxFee } from "../../balance-service/transfer/token.js";
|
|
14
|
+
import { calculateSwapRate, SIMPLE_SWAP_SUPPORTED_TESTNET_ASSET_MAPPING, SWAP_QUOTE_TIMEOUT_MAP } from "../utils.js";
|
|
15
|
+
import { SwapBaseHandler } from "./base-handler.js";
|
|
16
|
+
const apiUrl = 'https://api.simpleswap.io';
|
|
17
|
+
export const simpleSwapApiKey = process.env.SIMPLE_SWAP_API_KEY || '';
|
|
18
|
+
const toBNString = (input, decimal) => {
|
|
19
|
+
const raw = new BigNumber(input);
|
|
20
|
+
return raw.shiftedBy(decimal).integerValue(BigNumber.ROUND_CEIL).toFixed();
|
|
21
|
+
};
|
|
22
|
+
const fetchSwapList = async params => {
|
|
23
|
+
const swapListParams = new URLSearchParams({
|
|
24
|
+
api_key: `${simpleSwapApiKey}`,
|
|
25
|
+
fixed: 'false',
|
|
26
|
+
symbol: params.fromSymbol
|
|
27
|
+
});
|
|
28
|
+
const response = await fetch(`${apiUrl}/get_pairs?${swapListParams.toString()}`, {
|
|
29
|
+
headers: {
|
|
30
|
+
accept: 'application/json'
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return await response.json();
|
|
34
|
+
};
|
|
35
|
+
const fetchRanges = async params => {
|
|
36
|
+
const rangesParams = new URLSearchParams({
|
|
37
|
+
api_key: `${simpleSwapApiKey}`,
|
|
38
|
+
fixed: 'false',
|
|
39
|
+
currency_from: params.fromSymbol,
|
|
40
|
+
currency_to: params.toSymbol
|
|
41
|
+
});
|
|
42
|
+
const response = await fetch(`${apiUrl}/get_ranges?${rangesParams.toString()}`, {
|
|
43
|
+
headers: {
|
|
44
|
+
accept: 'application/json'
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return await response.json();
|
|
48
|
+
};
|
|
49
|
+
async function getEstimate(request, fromAsset, toAsset) {
|
|
50
|
+
const fromSymbol = SIMPLE_SWAP_SUPPORTED_TESTNET_ASSET_MAPPING[fromAsset.slug];
|
|
51
|
+
const toSymbol = SIMPLE_SWAP_SUPPORTED_TESTNET_ASSET_MAPPING[toAsset.slug];
|
|
52
|
+
const assetDecimals = _getAssetDecimals(fromAsset);
|
|
53
|
+
if (!fromSymbol || !toSymbol) {
|
|
54
|
+
throw new SwapError(SwapErrorType.ASSET_NOT_SUPPORTED);
|
|
55
|
+
}
|
|
56
|
+
const formatedAmount = formatNumber(request.fromAmount, assetDecimals, s => s);
|
|
57
|
+
const params = new URLSearchParams({
|
|
58
|
+
api_key: `${simpleSwapApiKey}`,
|
|
59
|
+
fixed: 'false',
|
|
60
|
+
currency_from: fromSymbol,
|
|
61
|
+
currency_to: toSymbol,
|
|
62
|
+
amount: formatedAmount
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(`${apiUrl}/get_estimated?${params.toString()}`, {
|
|
66
|
+
headers: {
|
|
67
|
+
accept: 'application/json'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new SwapError(SwapErrorType.ERROR_FETCHING_QUOTE);
|
|
72
|
+
}
|
|
73
|
+
const resToAmount = await response.json();
|
|
74
|
+
const toAmount = toBNString(resToAmount, _getAssetDecimals(toAsset));
|
|
75
|
+
const bnToAmount = new BigN(toAmount);
|
|
76
|
+
const walletFeeRate = 4 / 1000;
|
|
77
|
+
const toAmountBeforeFee = bnToAmount.dividedBy(new BigN(1 - walletFeeRate));
|
|
78
|
+
const walletFeeAmount = toAmountBeforeFee.multipliedBy(4).dividedBy(1000).toString();
|
|
79
|
+
return {
|
|
80
|
+
toAmount,
|
|
81
|
+
walletFeeAmount
|
|
82
|
+
};
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error('Error:', err);
|
|
85
|
+
throw new SwapError(SwapErrorType.ERROR_FETCHING_QUOTE);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const createSwapRequest = async params => {
|
|
89
|
+
const fromDecimals = _getAssetDecimals(params.fromAsset);
|
|
90
|
+
const toDecimals = _getAssetDecimals(params.toAsset);
|
|
91
|
+
const formatedAmount = formatNumber(params.fromAmount, fromDecimals, s => s);
|
|
92
|
+
const requestBody = {
|
|
93
|
+
fixed: false,
|
|
94
|
+
currency_from: params.fromSymbol,
|
|
95
|
+
currency_to: params.toSymbol,
|
|
96
|
+
amount: formatedAmount,
|
|
97
|
+
// Convert to small number due to require of api
|
|
98
|
+
address_to: params.receiver,
|
|
99
|
+
extra_id_to: '',
|
|
100
|
+
user_refund_address: params.sender,
|
|
101
|
+
user_refund_extra_id: ''
|
|
102
|
+
};
|
|
103
|
+
const response = await fetch(`${apiUrl}/create_exchange?api_key=${simpleSwapApiKey}`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/json',
|
|
107
|
+
Accept: 'application/json'
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify(requestBody)
|
|
110
|
+
});
|
|
111
|
+
const depositAddressResponse = await response.json();
|
|
112
|
+
return {
|
|
113
|
+
id: depositAddressResponse.id,
|
|
114
|
+
addressFrom: depositAddressResponse.address_from,
|
|
115
|
+
amountTo: toBNString(depositAddressResponse.amount_to, toDecimals)
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
export class SimpleSwapHandler {
|
|
119
|
+
constructor(chainService, balanceService) {
|
|
120
|
+
this.swapBaseHandler = new SwapBaseHandler({
|
|
121
|
+
chainService,
|
|
122
|
+
balanceService,
|
|
123
|
+
providerName: 'SimpleSwap',
|
|
124
|
+
providerSlug: SwapProviderId.SIMPLE_SWAP
|
|
125
|
+
});
|
|
126
|
+
this.providerSlug = SwapProviderId.SIMPLE_SWAP;
|
|
127
|
+
}
|
|
128
|
+
async validateSwapProcess(params) {
|
|
129
|
+
const amount = params.selectedQuote.fromAmount;
|
|
130
|
+
const bnAmount = BigInt(amount);
|
|
131
|
+
if (bnAmount <= BigInt(0)) {
|
|
132
|
+
return Promise.resolve([new TransactionError(BasicTxErrorType.INVALID_PARAMS, 'Amount must be greater than 0')]);
|
|
133
|
+
}
|
|
134
|
+
let isXcmOk = false;
|
|
135
|
+
for (const [index, step] of params.process.steps.entries()) {
|
|
136
|
+
const getErrors = async () => {
|
|
137
|
+
switch (step.type) {
|
|
138
|
+
case CommonStepType.DEFAULT:
|
|
139
|
+
return Promise.resolve([]);
|
|
140
|
+
case CommonStepType.TOKEN_APPROVAL:
|
|
141
|
+
return Promise.reject(new TransactionError(BasicTxErrorType.UNSUPPORTED));
|
|
142
|
+
default:
|
|
143
|
+
return this.swapBaseHandler.validateSwapStep(params, isXcmOk, index);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const errors = await getErrors();
|
|
147
|
+
if (errors.length) {
|
|
148
|
+
return errors;
|
|
149
|
+
} else if (step.type === CommonStepType.XCM) {
|
|
150
|
+
isXcmOk = true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
get chainService() {
|
|
156
|
+
return this.swapBaseHandler.chainService;
|
|
157
|
+
}
|
|
158
|
+
get balanceService() {
|
|
159
|
+
return this.swapBaseHandler.balanceService;
|
|
160
|
+
}
|
|
161
|
+
get providerInfo() {
|
|
162
|
+
return this.swapBaseHandler.providerInfo;
|
|
163
|
+
}
|
|
164
|
+
get name() {
|
|
165
|
+
return this.swapBaseHandler.name;
|
|
166
|
+
}
|
|
167
|
+
get slug() {
|
|
168
|
+
return this.swapBaseHandler.slug;
|
|
169
|
+
}
|
|
170
|
+
async getSwapQuote(request) {
|
|
171
|
+
try {
|
|
172
|
+
var _metadata$maxSwap;
|
|
173
|
+
const fromAsset = this.chainService.getAssetBySlug(request.pair.from);
|
|
174
|
+
const toAsset = this.chainService.getAssetBySlug(request.pair.to);
|
|
175
|
+
if (!fromAsset || !toAsset) {
|
|
176
|
+
return new SwapError(SwapErrorType.UNKNOWN);
|
|
177
|
+
}
|
|
178
|
+
const earlyValidation = await this.validateSwapRequest(request);
|
|
179
|
+
const metadata = earlyValidation.metadata;
|
|
180
|
+
if (earlyValidation.error) {
|
|
181
|
+
return _getSimpleSwapEarlyValidationError(earlyValidation.error, metadata);
|
|
182
|
+
}
|
|
183
|
+
const {
|
|
184
|
+
toAmount,
|
|
185
|
+
walletFeeAmount
|
|
186
|
+
} = await getEstimate(request, fromAsset, toAsset);
|
|
187
|
+
const fromAmount = request.fromAmount;
|
|
188
|
+
const rate = calculateSwapRate(request.fromAmount, toAmount, fromAsset, toAsset);
|
|
189
|
+
const fromChain = this.chainService.getChainInfoByKey(fromAsset.originChain);
|
|
190
|
+
const fromChainNativeTokenSlug = _getChainNativeTokenSlug(fromChain);
|
|
191
|
+
const defaultFeeToken = _isNativeToken(fromAsset) ? fromAsset.slug : fromChainNativeTokenSlug;
|
|
192
|
+
const chainType = _isChainSubstrateCompatible(fromChain) ? ChainType.SUBSTRATE : ChainType.EVM;
|
|
193
|
+
let api;
|
|
194
|
+
if (chainType === ChainType.SUBSTRATE) {
|
|
195
|
+
api = this.chainService.getSubstrateApi(fromChain.slug);
|
|
196
|
+
} else {
|
|
197
|
+
api = this.chainService.getEvmApi(fromChain.slug);
|
|
198
|
+
}
|
|
199
|
+
const networkFeeAmount = await getTransferMockTxFee(request.address, fromChain, fromAsset, api);
|
|
200
|
+
const networkFee = {
|
|
201
|
+
tokenSlug: fromChainNativeTokenSlug,
|
|
202
|
+
amount: networkFeeAmount.toString(),
|
|
203
|
+
feeType: SwapFeeType.NETWORK_FEE
|
|
204
|
+
};
|
|
205
|
+
const walletFee = {
|
|
206
|
+
tokenSlug: toAsset.slug,
|
|
207
|
+
amount: walletFeeAmount,
|
|
208
|
+
feeType: SwapFeeType.WALLET_FEE
|
|
209
|
+
};
|
|
210
|
+
return {
|
|
211
|
+
pair: request.pair,
|
|
212
|
+
fromAmount,
|
|
213
|
+
toAmount,
|
|
214
|
+
rate,
|
|
215
|
+
provider: this.providerInfo,
|
|
216
|
+
aliveUntil: +Date.now() + (SWAP_QUOTE_TIMEOUT_MAP[this.slug] || SWAP_QUOTE_TIMEOUT_MAP.default),
|
|
217
|
+
minSwap: toBNString(metadata.minSwap.value, _getAssetDecimals(fromAsset)),
|
|
218
|
+
maxSwap: toBNString((_metadata$maxSwap = metadata.maxSwap) === null || _metadata$maxSwap === void 0 ? void 0 : _metadata$maxSwap.value, _getAssetDecimals(fromAsset)),
|
|
219
|
+
estimatedArrivalTime: 3600,
|
|
220
|
+
isLowLiquidity: false,
|
|
221
|
+
feeInfo: {
|
|
222
|
+
feeComponent: [networkFee, walletFee],
|
|
223
|
+
defaultFeeToken,
|
|
224
|
+
feeOptions: [defaultFeeToken]
|
|
225
|
+
},
|
|
226
|
+
route: {
|
|
227
|
+
path: [fromAsset.slug, toAsset.slug]
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
} catch (e) {
|
|
231
|
+
return new SwapError(SwapErrorType.UNKNOWN);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
generateOptimalProcess(params) {
|
|
235
|
+
return this.swapBaseHandler.generateOptimalProcess(params, [this.getSubmitStep]);
|
|
236
|
+
}
|
|
237
|
+
async getSubmitStep(params) {
|
|
238
|
+
if (params.selectedQuote) {
|
|
239
|
+
const submitStep = {
|
|
240
|
+
name: 'Swap',
|
|
241
|
+
type: SwapStepType.SWAP
|
|
242
|
+
};
|
|
243
|
+
return Promise.resolve([submitStep, params.selectedQuote.feeInfo]);
|
|
244
|
+
}
|
|
245
|
+
return Promise.resolve(undefined);
|
|
246
|
+
}
|
|
247
|
+
async validateSwapRequest(request) {
|
|
248
|
+
try {
|
|
249
|
+
const fromAsset = this.chainService.getAssetBySlug(request.pair.from);
|
|
250
|
+
const toAsset = this.chainService.getAssetBySlug(request.pair.to);
|
|
251
|
+
if (!fromAsset || !toAsset) {
|
|
252
|
+
return {
|
|
253
|
+
error: SwapErrorType.ERROR_FETCHING_QUOTE
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const fromSymbol = SIMPLE_SWAP_SUPPORTED_TESTNET_ASSET_MAPPING[fromAsset.slug];
|
|
257
|
+
const toSymbol = SIMPLE_SWAP_SUPPORTED_TESTNET_ASSET_MAPPING[toAsset.slug];
|
|
258
|
+
if (!fromSymbol || !toSymbol) {
|
|
259
|
+
return {
|
|
260
|
+
error: SwapErrorType.ASSET_NOT_SUPPORTED
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
const swapList = await fetchSwapList({
|
|
265
|
+
fromSymbol
|
|
266
|
+
});
|
|
267
|
+
if (!swapList.includes(toSymbol)) {
|
|
268
|
+
return {
|
|
269
|
+
error: SwapErrorType.ASSET_NOT_SUPPORTED
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error('Error:', err);
|
|
274
|
+
}
|
|
275
|
+
const ranges = await fetchRanges({
|
|
276
|
+
fromSymbol,
|
|
277
|
+
toSymbol
|
|
278
|
+
});
|
|
279
|
+
const {
|
|
280
|
+
max,
|
|
281
|
+
min
|
|
282
|
+
} = ranges;
|
|
283
|
+
const bnMin = toBNString(min, _getAssetDecimals(fromAsset));
|
|
284
|
+
const bnAmount = BigInt(request.fromAmount);
|
|
285
|
+
if (bnAmount < BigInt(bnMin)) {
|
|
286
|
+
return {
|
|
287
|
+
error: SwapErrorType.NOT_MEET_MIN_SWAP,
|
|
288
|
+
metadata: {
|
|
289
|
+
minSwap: {
|
|
290
|
+
value: min,
|
|
291
|
+
symbol: fromAsset.symbol
|
|
292
|
+
},
|
|
293
|
+
maxSwap: max ? {
|
|
294
|
+
value: max,
|
|
295
|
+
symbol: fromAsset.symbol
|
|
296
|
+
} : undefined,
|
|
297
|
+
chain: this.chainService.getChainInfoByKey(fromAsset.originChain)
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (max && bnAmount > BigInt(toBNString(max, _getAssetDecimals(fromAsset)))) {
|
|
302
|
+
return {
|
|
303
|
+
error: SwapErrorType.SWAP_EXCEED_ALLOWANCE,
|
|
304
|
+
metadata: {
|
|
305
|
+
minSwap: {
|
|
306
|
+
value: min,
|
|
307
|
+
symbol: fromAsset.symbol
|
|
308
|
+
},
|
|
309
|
+
maxSwap: {
|
|
310
|
+
value: max,
|
|
311
|
+
symbol: fromAsset.symbol
|
|
312
|
+
},
|
|
313
|
+
chain: this.chainService.getChainInfoByKey(fromAsset.originChain)
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
metadata: {
|
|
319
|
+
minSwap: {
|
|
320
|
+
value: min,
|
|
321
|
+
symbol: fromAsset.symbol
|
|
322
|
+
},
|
|
323
|
+
maxSwap: max ? {
|
|
324
|
+
value: max,
|
|
325
|
+
symbol: fromAsset.symbol
|
|
326
|
+
} : undefined,
|
|
327
|
+
chain: this.chainService.getChainInfoByKey(fromAsset.originChain)
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
} catch (e) {
|
|
331
|
+
console.error(e);
|
|
332
|
+
return {
|
|
333
|
+
error: SwapErrorType.UNKNOWN
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async handleSwapProcess(params) {
|
|
338
|
+
const {
|
|
339
|
+
currentStep,
|
|
340
|
+
process
|
|
341
|
+
} = params;
|
|
342
|
+
const type = process.steps[currentStep].type;
|
|
343
|
+
switch (type) {
|
|
344
|
+
case CommonStepType.DEFAULT:
|
|
345
|
+
return Promise.reject(new TransactionError(BasicTxErrorType.UNSUPPORTED));
|
|
346
|
+
case SwapStepType.SWAP:
|
|
347
|
+
return this.handleSubmitStep(params);
|
|
348
|
+
default:
|
|
349
|
+
return this.handleSubmitStep(params);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async handleSubmitStep(params) {
|
|
353
|
+
const {
|
|
354
|
+
address,
|
|
355
|
+
quote,
|
|
356
|
+
recipient
|
|
357
|
+
} = params;
|
|
358
|
+
const pair = quote.pair;
|
|
359
|
+
const fromAsset = this.chainService.getAssetBySlug(pair.from);
|
|
360
|
+
const toAsset = this.chainService.getAssetBySlug(pair.to);
|
|
361
|
+
const chainInfo = this.chainService.getChainInfoByKey(fromAsset.originChain);
|
|
362
|
+
const toChainInfo = this.chainService.getChainInfoByKey(toAsset.originChain);
|
|
363
|
+
const chainType = _isChainSubstrateCompatible(chainInfo) ? ChainType.SUBSTRATE : ChainType.EVM;
|
|
364
|
+
const sender = _reformatAddressWithChain(address, chainInfo);
|
|
365
|
+
const receiver = _reformatAddressWithChain(recipient !== null && recipient !== void 0 ? recipient : sender, toChainInfo);
|
|
366
|
+
const fromSymbol = SIMPLE_SWAP_SUPPORTED_TESTNET_ASSET_MAPPING[fromAsset.slug];
|
|
367
|
+
const toSymbol = SIMPLE_SWAP_SUPPORTED_TESTNET_ASSET_MAPPING[toAsset.slug];
|
|
368
|
+
const {
|
|
369
|
+
fromAmount
|
|
370
|
+
} = quote;
|
|
371
|
+
const {
|
|
372
|
+
addressFrom,
|
|
373
|
+
amountTo,
|
|
374
|
+
id
|
|
375
|
+
} = await createSwapRequest({
|
|
376
|
+
fromSymbol,
|
|
377
|
+
toSymbol,
|
|
378
|
+
fromAmount,
|
|
379
|
+
fromAsset,
|
|
380
|
+
receiver,
|
|
381
|
+
sender,
|
|
382
|
+
toAsset
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Validate the amount to be swapped
|
|
386
|
+
const rate = BigN(amountTo).div(BigN(quote.toAmount)).multipliedBy(100);
|
|
387
|
+
if (rate.lt(95)) {
|
|
388
|
+
throw new SwapError(SwapErrorType.NOT_MEET_MIN_EXPECTED);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Can modify quote.toAmount to amountTo after confirm real amount received
|
|
392
|
+
|
|
393
|
+
const txData = {
|
|
394
|
+
id: id,
|
|
395
|
+
address,
|
|
396
|
+
provider: this.providerInfo,
|
|
397
|
+
quote: params.quote,
|
|
398
|
+
slippage: params.slippage,
|
|
399
|
+
recipient: receiver,
|
|
400
|
+
process: params.process
|
|
401
|
+
};
|
|
402
|
+
let extrinsic;
|
|
403
|
+
if (chainType === ChainType.SUBSTRATE) {
|
|
404
|
+
const chainApi = this.chainService.getSubstrateApi(chainInfo.slug);
|
|
405
|
+
const substrateApi = await chainApi.isReady;
|
|
406
|
+
const [submittableExtrinsic] = await createTransferExtrinsic({
|
|
407
|
+
from: address,
|
|
408
|
+
networkKey: chainInfo.slug,
|
|
409
|
+
substrateApi,
|
|
410
|
+
to: addressFrom,
|
|
411
|
+
tokenInfo: fromAsset,
|
|
412
|
+
transferAll: false,
|
|
413
|
+
value: quote.fromAmount
|
|
414
|
+
});
|
|
415
|
+
extrinsic = submittableExtrinsic;
|
|
416
|
+
} else {
|
|
417
|
+
if (_isNativeToken(fromAsset)) {
|
|
418
|
+
const [transactionConfig] = await getEVMTransactionObject(chainInfo, address, addressFrom, quote.fromAmount, false, this.chainService.getEvmApi(chainInfo.slug));
|
|
419
|
+
extrinsic = transactionConfig;
|
|
420
|
+
} else {
|
|
421
|
+
const [transactionConfig] = await getERC20TransactionObject(_getContractAddressOfToken(fromAsset), chainInfo, address, addressFrom, quote.fromAmount, false, this.chainService.getEvmApi(chainInfo.slug));
|
|
422
|
+
extrinsic = transactionConfig;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
txChain: fromAsset.originChain,
|
|
427
|
+
txData,
|
|
428
|
+
extrinsic,
|
|
429
|
+
transferNativeAmount: _isNativeToken(fromAsset) ? quote.fromAmount : '0',
|
|
430
|
+
extrinsicType: ExtrinsicType.SWAP,
|
|
431
|
+
chainType
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
@@ -2,8 +2,9 @@ import { TransactionError } from '@subwallet/extension-base/background/errors/Tr
|
|
|
2
2
|
import KoniState from '@subwallet/extension-base/koni/background/handlers/State';
|
|
3
3
|
import { ServiceStatus, ServiceWithProcessInterface, StoppableServiceInterface } from '@subwallet/extension-base/services/base/types';
|
|
4
4
|
import { CommonOptimalPath } from '@subwallet/extension-base/types/service-base';
|
|
5
|
-
import { OptimalSwapPathParams, SwapPair, SwapQuoteResponse, SwapRequest, SwapRequestResult, SwapSubmitParams, SwapSubmitStepData, ValidateSwapProcessParams } from '@subwallet/extension-base/types/swap';
|
|
5
|
+
import { OptimalSwapPathParams, SwapPair, SwapProviderId, SwapQuoteResponse, SwapRequest, SwapRequestResult, SwapSubmitParams, SwapSubmitStepData, ValidateSwapProcessParams } from '@subwallet/extension-base/types/swap';
|
|
6
6
|
import { PromiseHandler } from '@subwallet/extension-base/utils';
|
|
7
|
+
export declare const _isChainSupportedByProvider: (providerSlug: SwapProviderId, chain: string) => boolean;
|
|
7
8
|
export declare class SwapService implements ServiceWithProcessInterface, StoppableServiceInterface {
|
|
8
9
|
protected readonly state: KoniState;
|
|
9
10
|
private eventService;
|
|
@@ -13,6 +13,11 @@ import { DEFAULT_FIRST_STEP, MOCK_STEP_FEE } from '@subwallet/extension-base/typ
|
|
|
13
13
|
import { _SUPPORTED_SWAP_PROVIDERS, SwapErrorType, SwapProviderId, SwapStepType } from '@subwallet/extension-base/types/swap';
|
|
14
14
|
import { createPromiseHandler } from '@subwallet/extension-base/utils';
|
|
15
15
|
import { BehaviorSubject } from 'rxjs';
|
|
16
|
+
import { SimpleSwapHandler } from "./handler/simpleswap-handler.js";
|
|
17
|
+
export const _isChainSupportedByProvider = (providerSlug, chain) => {
|
|
18
|
+
const supportedChains = _PROVIDER_TO_SUPPORTED_PAIR_MAP[providerSlug];
|
|
19
|
+
return supportedChains ? supportedChains.includes(chain) : false;
|
|
20
|
+
};
|
|
16
21
|
export class SwapService {
|
|
17
22
|
swapPairSubject = new BehaviorSubject([]);
|
|
18
23
|
handlers = {};
|
|
@@ -29,7 +34,7 @@ export class SwapService {
|
|
|
29
34
|
const swappingSrcChain = this.chainService.getAssetBySlug(request.pair.from).originChain;
|
|
30
35
|
await Promise.all(Object.values(this.handlers).map(async handler => {
|
|
31
36
|
// temporary solution to reduce number of requests to providers, will work as long as there's only 1 provider for 1 chain
|
|
32
|
-
if (!
|
|
37
|
+
if (!_isChainSupportedByProvider(handler.providerSlug, swappingSrcChain)) {
|
|
33
38
|
return;
|
|
34
39
|
}
|
|
35
40
|
if (handler.init && handler.isReady === false) {
|
|
@@ -116,7 +121,10 @@ export class SwapService {
|
|
|
116
121
|
quoteError = (preferredErrorResp === null || preferredErrorResp === void 0 ? void 0 : preferredErrorResp.error) || (defaultErrorResp === null || defaultErrorResp === void 0 ? void 0 : defaultErrorResp.error);
|
|
117
122
|
} else {
|
|
118
123
|
var _selectedQuote;
|
|
119
|
-
selectedQuote = availableQuotes
|
|
124
|
+
selectedQuote = availableQuotes.find(quote => {
|
|
125
|
+
var _request$currentQuote;
|
|
126
|
+
return quote.provider.id === ((_request$currentQuote = request.currentQuote) === null || _request$currentQuote === void 0 ? void 0 : _request$currentQuote.id);
|
|
127
|
+
}) || availableQuotes[0];
|
|
120
128
|
aliveUntil = ((_selectedQuote = selectedQuote) === null || _selectedQuote === void 0 ? void 0 : _selectedQuote.aliveUntil) || +Date.now() + SWAP_QUOTE_TIMEOUT_MAP.default;
|
|
121
129
|
}
|
|
122
130
|
return {
|
|
@@ -150,6 +158,9 @@ export class SwapService {
|
|
|
150
158
|
case SwapProviderId.ROCOCO_ASSET_HUB:
|
|
151
159
|
this.handlers[providerId] = new AssetHubSwapHandler(this.chainService, this.state.balanceService, 'rococo_assethub');
|
|
152
160
|
break;
|
|
161
|
+
case SwapProviderId.SIMPLE_SWAP:
|
|
162
|
+
this.handlers[providerId] = new SimpleSwapHandler(this.chainService, this.state.balanceService);
|
|
163
|
+
break;
|
|
153
164
|
default:
|
|
154
165
|
throw new Error('Unsupported provider');
|
|
155
166
|
}
|
|
@@ -3,10 +3,12 @@ import { _ChainAsset } from '@subwallet/chain-list/types';
|
|
|
3
3
|
import { SwapPair } from '@subwallet/extension-base/types/swap';
|
|
4
4
|
export declare const CHAIN_FLIP_TESTNET_EXPLORER = "https://blocks-perseverance.chainflip.io";
|
|
5
5
|
export declare const CHAIN_FLIP_MAINNET_EXPLORER = "https://scan.chainflip.io";
|
|
6
|
+
export declare const SIMPLE_SWAP_EXPLORER = "https://simpleswap.io";
|
|
6
7
|
export declare const CHAIN_FLIP_SUPPORTED_MAINNET_MAPPING: Record<string, Chain>;
|
|
7
8
|
export declare const CHAIN_FLIP_SUPPORTED_TESTNET_MAPPING: Record<string, Chain>;
|
|
8
9
|
export declare const CHAIN_FLIP_SUPPORTED_MAINNET_ASSET_MAPPING: Record<string, Asset>;
|
|
9
10
|
export declare const CHAIN_FLIP_SUPPORTED_TESTNET_ASSET_MAPPING: Record<string, Asset>;
|
|
11
|
+
export declare const SIMPLE_SWAP_SUPPORTED_TESTNET_ASSET_MAPPING: Record<string, string>;
|
|
10
12
|
export declare const SWAP_QUOTE_TIMEOUT_MAP: Record<string, number>;
|
|
11
13
|
export declare const _PROVIDER_TO_SUPPORTED_PAIR_MAP: Record<string, string[]>;
|
|
12
14
|
export declare function getSwapAlternativeAsset(swapPair: SwapPair): string | undefined;
|
|
@@ -9,6 +9,7 @@ import { SwapProviderId } from '@subwallet/extension-base/types/swap';
|
|
|
9
9
|
import BigN from 'bignumber.js';
|
|
10
10
|
export const CHAIN_FLIP_TESTNET_EXPLORER = 'https://blocks-perseverance.chainflip.io';
|
|
11
11
|
export const CHAIN_FLIP_MAINNET_EXPLORER = 'https://scan.chainflip.io';
|
|
12
|
+
export const SIMPLE_SWAP_EXPLORER = 'https://simpleswap.io';
|
|
12
13
|
export const CHAIN_FLIP_SUPPORTED_MAINNET_MAPPING = {
|
|
13
14
|
[COMMON_CHAIN_SLUGS.POLKADOT]: Chains.Polkadot,
|
|
14
15
|
[COMMON_CHAIN_SLUGS.ETHEREUM]: Chains.Ethereum,
|
|
@@ -29,6 +30,13 @@ export const CHAIN_FLIP_SUPPORTED_TESTNET_ASSET_MAPPING = {
|
|
|
29
30
|
[COMMON_ASSETS.ETH_SEPOLIA]: Assets.ETH,
|
|
30
31
|
[COMMON_ASSETS.USDC_SEPOLIA]: Assets.USDC
|
|
31
32
|
};
|
|
33
|
+
export const SIMPLE_SWAP_SUPPORTED_TESTNET_ASSET_MAPPING = {
|
|
34
|
+
'bittensor-NATIVE-TAO': 'tao',
|
|
35
|
+
[COMMON_ASSETS.ETH]: 'eth',
|
|
36
|
+
[COMMON_ASSETS.DOT]: 'dot',
|
|
37
|
+
[COMMON_ASSETS.USDC_ETHEREUM]: 'usdc',
|
|
38
|
+
[COMMON_ASSETS.USDT_ETHEREUM]: 'usdterc20'
|
|
39
|
+
};
|
|
32
40
|
export const SWAP_QUOTE_TIMEOUT_MAP = {
|
|
33
41
|
// in milliseconds
|
|
34
42
|
default: 30000,
|
|
@@ -42,7 +50,8 @@ export const _PROVIDER_TO_SUPPORTED_PAIR_MAP = {
|
|
|
42
50
|
[SwapProviderId.CHAIN_FLIP_TESTNET]: [COMMON_CHAIN_SLUGS.CHAINFLIP_POLKADOT, COMMON_CHAIN_SLUGS.ETHEREUM_SEPOLIA],
|
|
43
51
|
[SwapProviderId.POLKADOT_ASSET_HUB]: [COMMON_CHAIN_SLUGS.POLKADOT_ASSET_HUB],
|
|
44
52
|
[SwapProviderId.KUSAMA_ASSET_HUB]: [COMMON_CHAIN_SLUGS.KUSAMA_ASSET_HUB],
|
|
45
|
-
[SwapProviderId.ROCOCO_ASSET_HUB]: [COMMON_CHAIN_SLUGS.ROCOCO_ASSET_HUB]
|
|
53
|
+
[SwapProviderId.ROCOCO_ASSET_HUB]: [COMMON_CHAIN_SLUGS.ROCOCO_ASSET_HUB],
|
|
54
|
+
[SwapProviderId.SIMPLE_SWAP]: ['bittensor', COMMON_CHAIN_SLUGS.ETHEREUM, COMMON_CHAIN_SLUGS.POLKADOT]
|
|
46
55
|
};
|
|
47
56
|
export function getSwapAlternativeAsset(swapPair) {
|
|
48
57
|
var _swapPair$metadata;
|
|
@@ -15,7 +15,7 @@ import { getBaseTransactionInfo, getTransactionId, isSubstrateTransaction, isTon
|
|
|
15
15
|
import { getExplorerLink, parseTransactionData } from '@subwallet/extension-base/services/transaction-service/utils';
|
|
16
16
|
import { isWalletConnectRequest } from '@subwallet/extension-base/services/wallet-connect-service/helpers';
|
|
17
17
|
import { BasicTxErrorType, YieldPoolType } from '@subwallet/extension-base/types';
|
|
18
|
-
import {
|
|
18
|
+
import { anyNumberToBN, pairToAccount, reformatAddress } from '@subwallet/extension-base/utils';
|
|
19
19
|
import { mergeTransactionAndSignature } from '@subwallet/extension-base/utils/eth/mergeTransactionAndSignature';
|
|
20
20
|
import { isContractAddress, parseContractInput } from '@subwallet/extension-base/utils/eth/parseTransaction';
|
|
21
21
|
import { BN_ZERO } from '@subwallet/extension-base/utils/number';
|
|
@@ -1047,7 +1047,7 @@ export default class TransactionService {
|
|
|
1047
1047
|
}
|
|
1048
1048
|
return emitter;
|
|
1049
1049
|
}
|
|
1050
|
-
|
|
1050
|
+
signAndSendSubstrateTransaction({
|
|
1051
1051
|
address,
|
|
1052
1052
|
chain,
|
|
1053
1053
|
id,
|
|
@@ -1062,8 +1062,9 @@ export default class TransactionService {
|
|
|
1062
1062
|
extrinsicHash: id
|
|
1063
1063
|
};
|
|
1064
1064
|
const extrinsic = transaction;
|
|
1065
|
-
const registry = extrinsic.registry;
|
|
1066
|
-
const signedExtensions = registry.signedExtensions;
|
|
1065
|
+
// const registry = extrinsic.registry;
|
|
1066
|
+
// const signedExtensions = registry.signedExtensions;
|
|
1067
|
+
|
|
1067
1068
|
const signerOption = {
|
|
1068
1069
|
signer: {
|
|
1069
1070
|
signPayload: async payload => {
|
|
@@ -1080,13 +1081,16 @@ export default class TransactionService {
|
|
|
1080
1081
|
},
|
|
1081
1082
|
withSignedTransaction: true
|
|
1082
1083
|
};
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1084
|
+
|
|
1085
|
+
// if (_isRuntimeUpdated(signedExtensions)) {
|
|
1086
|
+
// const metadataHash = await this.state.chainService.calculateMetadataHash(chain);
|
|
1087
|
+
//
|
|
1088
|
+
// if (metadataHash) {
|
|
1089
|
+
// signerOption.mode = 1;
|
|
1090
|
+
// signerOption.metadataHash = metadataHash;
|
|
1091
|
+
// }
|
|
1092
|
+
// }
|
|
1093
|
+
|
|
1090
1094
|
extrinsic.signAsync(address, signerOption).then(async rs => {
|
|
1091
1095
|
// Emit signed event
|
|
1092
1096
|
emitter.emit('signed', eventData);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { _ChainInfo } from '@subwallet/chain-list/types';
|
|
2
2
|
import { ExtrinsicDataTypeMap, ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
|
|
3
|
-
import { ChainflipSwapTxData } from '@subwallet/extension-base/types/swap';
|
|
3
|
+
import { ChainflipSwapTxData, SimpleSwapTxData } from '@subwallet/extension-base/types/swap';
|
|
4
4
|
export declare function parseTransactionData<T extends ExtrinsicType>(data: unknown): ExtrinsicDataTypeMap[T];
|
|
5
5
|
export declare function getExplorerLink(chainInfo: _ChainInfo, value: string, type: 'account' | 'tx'): string | undefined;
|
|
6
6
|
export declare function getChainflipExplorerLink(data: ChainflipSwapTxData, chainInfo: _ChainInfo): string;
|
|
7
|
+
export declare function getSimpleSwapExplorerLink(data: SimpleSwapTxData): string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import { _getBlockExplorerFromChain, _isChainTestNet, _isPureEvmChain } from '@subwallet/extension-base/services/chain-service/utils';
|
|
5
|
-
import { CHAIN_FLIP_MAINNET_EXPLORER, CHAIN_FLIP_TESTNET_EXPLORER } from '@subwallet/extension-base/services/swap-service/utils';
|
|
5
|
+
import { CHAIN_FLIP_MAINNET_EXPLORER, CHAIN_FLIP_TESTNET_EXPLORER, SIMPLE_SWAP_EXPLORER } from '@subwallet/extension-base/services/swap-service/utils';
|
|
6
6
|
// @ts-ignore
|
|
7
7
|
export function parseTransactionData(data) {
|
|
8
8
|
// @ts-ignore
|
|
@@ -31,6 +31,9 @@ function getBlockExplorerAccountRoute(explorerLink) {
|
|
|
31
31
|
if (explorerLink.includes('tangle.statescan.io')) {
|
|
32
32
|
return '#/accounts';
|
|
33
33
|
}
|
|
34
|
+
if (explorerLink.includes('laos.statescan.io')) {
|
|
35
|
+
return '#/accounts';
|
|
36
|
+
}
|
|
34
37
|
if (explorerLink.includes('explorer.zkverify.io')) {
|
|
35
38
|
return 'account';
|
|
36
39
|
}
|
|
@@ -46,7 +49,7 @@ function getBlockExplorerTxRoute(chainInfo) {
|
|
|
46
49
|
if (['aventus', 'deeper_network'].includes(chainInfo.slug)) {
|
|
47
50
|
return 'transaction';
|
|
48
51
|
}
|
|
49
|
-
if (['invarch'].includes(chainInfo.slug)) {
|
|
52
|
+
if (['invarch', 'tangle'].includes(chainInfo.slug)) {
|
|
50
53
|
return '#/extrinsics';
|
|
51
54
|
}
|
|
52
55
|
return 'extrinsic';
|
|
@@ -62,6 +65,9 @@ export function getExplorerLink(chainInfo, value, type) {
|
|
|
62
65
|
return undefined;
|
|
63
66
|
}
|
|
64
67
|
const route = getBlockExplorerTxRoute(chainInfo);
|
|
68
|
+
if (chainInfo.slug === 'tangle') {
|
|
69
|
+
return `${explorerLink}${explorerLink.endsWith('/') ? '' : '/'}extrinsic/${value}${route}/${value}`;
|
|
70
|
+
}
|
|
65
71
|
return `${explorerLink}${explorerLink.endsWith('/') ? '' : '/'}${route}/${value}`;
|
|
66
72
|
}
|
|
67
73
|
return undefined;
|
|
@@ -69,4 +75,8 @@ export function getExplorerLink(chainInfo, value, type) {
|
|
|
69
75
|
export function getChainflipExplorerLink(data, chainInfo) {
|
|
70
76
|
const chainflipDomain = _isChainTestNet(chainInfo) ? CHAIN_FLIP_TESTNET_EXPLORER : CHAIN_FLIP_MAINNET_EXPLORER;
|
|
71
77
|
return `${chainflipDomain}/channels/${data.depositChannelId}`;
|
|
78
|
+
}
|
|
79
|
+
export function getSimpleSwapExplorerLink(data) {
|
|
80
|
+
const simpleswapDomain = SIMPLE_SWAP_EXPLORER;
|
|
81
|
+
return `${simpleswapDomain}/exchange?id=${data.id}`;
|
|
72
82
|
}
|
|
@@ -52,6 +52,8 @@ export interface AccountLedgerData {
|
|
|
52
52
|
originGenesisHash?: string | null;
|
|
53
53
|
/** Ledger's availableGenesisHashes */
|
|
54
54
|
availableGenesisHashes?: string[];
|
|
55
|
+
/** Is Ledger recovery chain */
|
|
56
|
+
isLedgerRecovery?: boolean;
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
57
59
|
* @interface AccountInjectData
|