@subwallet/extension-base 1.3.62-0 → 1.3.63-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/cjs/packageInfo.js +1 -1
- package/cjs/services/balance-service/transfer/smart-contract.js +14 -10
- package/cjs/services/balance-service/transfer/xcm/acrossBridge/index.js +0 -6
- package/cjs/services/balance-service/transfer/xcm/utils.js +2 -0
- package/cjs/services/chain-service/constants.js +16 -0
- package/cjs/services/chain-service/utils/index.js +24 -4
- package/cjs/services/chain-service/utils/patch.js +1 -1
- package/cjs/services/earning-service/constants/chains.js +2 -1
- package/cjs/services/earning-service/handlers/native-staking/base-para.js +6 -3
- package/cjs/services/earning-service/handlers/native-staking/relay-chain.js +4 -1
- package/cjs/services/earning-service/handlers/native-staking/tanssi.js +496 -0
- package/cjs/services/earning-service/service.js +4 -0
- package/cjs/services/earning-service/utils/index.js +2 -0
- package/cjs/services/swap-service/handler/base-handler.js +4 -1
- package/cjs/services/swap-service/handler/chainflip-handler.js +1 -17
- package/cjs/services/swap-service/handler/optimex-handler.js +421 -0
- package/cjs/services/swap-service/handler/simpleswap-handler.js +4 -2
- package/cjs/services/swap-service/index.js +38 -140
- package/cjs/services/swap-service/utils.js +16 -157
- package/cjs/services/transaction-service/helpers/index.js +2 -1
- package/cjs/types/service-base.js +0 -1
- package/cjs/types/swap/index.js +5 -8
- package/cjs/utils/account/common.js +1 -2
- package/package.json +16 -6
- package/packageInfo.js +1 -1
- package/services/balance-service/transfer/smart-contract.d.ts +3 -2
- package/services/balance-service/transfer/smart-contract.js +35 -29
- package/services/balance-service/transfer/xcm/acrossBridge/index.d.ts +0 -4
- package/services/balance-service/transfer/xcm/acrossBridge/index.js +0 -4
- package/services/balance-service/transfer/xcm/utils.js +2 -0
- package/services/chain-service/constants.js +16 -0
- package/services/chain-service/utils/index.d.ts +3 -2
- package/services/chain-service/utils/index.js +20 -1
- package/services/chain-service/utils/patch.d.ts +1 -1
- 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/native-staking/base-para.js +6 -3
- package/services/earning-service/handlers/native-staking/relay-chain.js +4 -1
- package/services/earning-service/handlers/native-staking/tanssi.d.ts +16 -0
- package/services/earning-service/handlers/native-staking/tanssi.js +478 -0
- package/services/earning-service/service.js +4 -0
- package/services/earning-service/utils/index.js +2 -0
- package/services/swap-service/handler/base-handler.js +6 -3
- package/services/swap-service/handler/chainflip-handler.d.ts +0 -2
- package/services/swap-service/handler/chainflip-handler.js +2 -18
- package/services/swap-service/handler/optimex-handler.d.ts +43 -0
- package/services/swap-service/handler/optimex-handler.js +410 -0
- package/services/swap-service/handler/simpleswap-handler.js +5 -3
- package/services/swap-service/index.d.ts +0 -1
- package/services/swap-service/index.js +21 -123
- package/services/swap-service/utils.d.ts +6 -12
- package/services/swap-service/utils.js +8 -138
- package/services/transaction-service/helpers/index.js +2 -1
- package/types/service-base.d.ts +3 -4
- package/types/service-base.js +0 -2
- package/types/swap/index.d.ts +3 -1
- package/types/swap/index.js +7 -6
- package/types/yield/info/account/info.d.ts +5 -0
- package/utils/account/common.js +2 -3
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// Copyright 2019-2022 @subwallet/extension-base
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
|
|
5
|
+
import { ChainType, ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
|
|
6
|
+
import { estimateTxFee } from '@subwallet/extension-base/koni/api/contract-handler/evm/web3';
|
|
7
|
+
import { createBitcoinTransaction } from '@subwallet/extension-base/services/balance-service/transfer/bitcoin-transfer';
|
|
8
|
+
import { getERC20TransactionObject, getEVMTransactionObject } from '@subwallet/extension-base/services/balance-service/transfer/smart-contract';
|
|
9
|
+
import { _chainInfoToChainType, _getChainNativeTokenSlug, _getContractAddressOfToken, _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils';
|
|
10
|
+
import { SwapBaseHandler } from '@subwallet/extension-base/services/swap-service/handler/base-handler';
|
|
11
|
+
import { getAmountAfterSlippage } from '@subwallet/extension-base/services/swap-service/utils';
|
|
12
|
+
import { BasicTxErrorType, CommonStepType, DynamicSwapType, SwapFeeType, SwapProviderId, SwapStepType } from '@subwallet/extension-base/types';
|
|
13
|
+
import { _reformatAddressWithChain, combineBitcoinFee, getSizeInfo } from '@subwallet/extension-base/utils';
|
|
14
|
+
import { getId } from '@subwallet/extension-base/utils/getId';
|
|
15
|
+
import keyring from '@subwallet/ui-keyring';
|
|
16
|
+
import BigNumber from 'bignumber.js';
|
|
17
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
18
|
+
import { hexStripPrefix, u8aToHex } from '@polkadot/util';
|
|
19
|
+
const OptimexBaseUrl = {
|
|
20
|
+
mainnet: 'https://subwallet-provider.optimex.xyz',
|
|
21
|
+
testnet: 'https://provider-stg.bitdex.xyz'
|
|
22
|
+
};
|
|
23
|
+
export class OptimexHandler {
|
|
24
|
+
constructor(chainService, balanceService, feeService, isTestnet = true) {
|
|
25
|
+
this.swapBaseHandler = new SwapBaseHandler({
|
|
26
|
+
chainService,
|
|
27
|
+
balanceService,
|
|
28
|
+
feeService,
|
|
29
|
+
providerName: isTestnet ? 'Optimex Testnet' : 'Optimex',
|
|
30
|
+
providerSlug: isTestnet ? SwapProviderId.OPTIMEX_TESTNET : SwapProviderId.OPTIMEX
|
|
31
|
+
});
|
|
32
|
+
this.providerSlug = isTestnet ? SwapProviderId.OPTIMEX_TESTNET : SwapProviderId.OPTIMEX;
|
|
33
|
+
this.baseUrl = isTestnet ? OptimexBaseUrl.testnet : OptimexBaseUrl.mainnet;
|
|
34
|
+
this.isTestnet = isTestnet;
|
|
35
|
+
}
|
|
36
|
+
get chainService() {
|
|
37
|
+
return this.swapBaseHandler.chainService;
|
|
38
|
+
}
|
|
39
|
+
get providerInfo() {
|
|
40
|
+
return this.swapBaseHandler.providerInfo;
|
|
41
|
+
}
|
|
42
|
+
get name() {
|
|
43
|
+
return this.swapBaseHandler.name;
|
|
44
|
+
}
|
|
45
|
+
get slug() {
|
|
46
|
+
return this.swapBaseHandler.slug;
|
|
47
|
+
}
|
|
48
|
+
async generateOptimalProcessV2(params) {
|
|
49
|
+
const tradeMetadata = await this.initTrade(params);
|
|
50
|
+
if (!tradeMetadata) {
|
|
51
|
+
throw new Error('Error generating optimal process: Cannot init Optimex trade');
|
|
52
|
+
}
|
|
53
|
+
const isNeedApprove = tradeMetadata.need_approve;
|
|
54
|
+
this.currentTradeMetadata = tradeMetadata;
|
|
55
|
+
if (!isNeedApprove) {
|
|
56
|
+
return this.swapBaseHandler.generateOptimalProcessV2(params, [this.getSubmitStep.bind(this)]);
|
|
57
|
+
}
|
|
58
|
+
if (!tradeMetadata.approve_address || !tradeMetadata.approve_payload) {
|
|
59
|
+
throw new Error('Error generating optimal process: Lack of approve info');
|
|
60
|
+
}
|
|
61
|
+
return this.swapBaseHandler.generateOptimalProcessV2(params, [this.getApprovalStep.bind(this), this.getSubmitStep.bind(this)]);
|
|
62
|
+
}
|
|
63
|
+
async initTrade(request) {
|
|
64
|
+
var _request$selectedQuot, _request$selectedQuot2;
|
|
65
|
+
const pair = request.request.pair;
|
|
66
|
+
const fromAsset = this.chainService.getAssetBySlug(pair.from);
|
|
67
|
+
const fromChain = this.chainService.getChainInfoByKey(fromAsset.originChain);
|
|
68
|
+
const fromChainType = _chainInfoToChainType(fromChain);
|
|
69
|
+
const sender = request.request.address;
|
|
70
|
+
const receiver = request.request.recipient;
|
|
71
|
+
const sendingValue = request.request.fromAmount;
|
|
72
|
+
const slippage = request.request.slippage;
|
|
73
|
+
const metadata = (_request$selectedQuot = request.selectedQuote) === null || _request$selectedQuot === void 0 ? void 0 : _request$selectedQuot.metadata;
|
|
74
|
+
const walletFeeInfo = (_request$selectedQuot2 = request.selectedQuote) === null || _request$selectedQuot2 === void 0 ? void 0 : _request$selectedQuot2.feeInfo.feeComponent.find(fee => fee.feeType === SwapFeeType.WALLET_FEE);
|
|
75
|
+
if (!metadata || !walletFeeInfo) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
let initTradeRequest;
|
|
79
|
+
const swAffiliate = walletFeeInfo.metadata;
|
|
80
|
+
if (fromChainType === ChainType.EVM) {
|
|
81
|
+
initTradeRequest = {
|
|
82
|
+
session_id: metadata.session_id,
|
|
83
|
+
amount_in: sendingValue,
|
|
84
|
+
from_user_address: sender,
|
|
85
|
+
// compressPublicKey for BTC and SOLANA, address for EVM
|
|
86
|
+
to_user_address: receiver || '',
|
|
87
|
+
// Receiving address
|
|
88
|
+
user_refund_address: sender,
|
|
89
|
+
// Refund address if trade fails
|
|
90
|
+
user_refund_pubkey: sender,
|
|
91
|
+
// Refund pubkey if trade fails, in btc is pubkey and in evm is address
|
|
92
|
+
creator_public_key: sender,
|
|
93
|
+
// Compressed public key, in btc is pubkey and in evm is address
|
|
94
|
+
from_wallet_address: sender,
|
|
95
|
+
// Creator address
|
|
96
|
+
min_amount_out: getAmountAfterSlippage(metadata.best_quote_after_fees, slippage),
|
|
97
|
+
affiliate_info: [swAffiliate]
|
|
98
|
+
};
|
|
99
|
+
} else if (fromChainType === ChainType.BITCOIN) {
|
|
100
|
+
const fromPublicKey = hexStripPrefix(u8aToHex(keyring.getPair(sender).publicKey));
|
|
101
|
+
initTradeRequest = {
|
|
102
|
+
session_id: metadata.session_id,
|
|
103
|
+
amount_in: sendingValue,
|
|
104
|
+
from_user_address: fromPublicKey,
|
|
105
|
+
to_user_address: receiver || '',
|
|
106
|
+
user_refund_address: sender,
|
|
107
|
+
user_refund_pubkey: fromPublicKey,
|
|
108
|
+
creator_public_key: fromPublicKey,
|
|
109
|
+
from_wallet_address: sender,
|
|
110
|
+
min_amount_out: getAmountAfterSlippage(metadata.best_quote_after_fees, slippage),
|
|
111
|
+
affiliate_info: [swAffiliate]
|
|
112
|
+
};
|
|
113
|
+
} else {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
let tradeInfo;
|
|
117
|
+
try {
|
|
118
|
+
const rawResponse = await fetch(`${this.baseUrl}/v1/trades/initiate`, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
body: JSON.stringify(initTradeRequest),
|
|
121
|
+
headers: {
|
|
122
|
+
'Content-Type': 'application/json'
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
if (!rawResponse.ok) {
|
|
126
|
+
console.log('Error bad request while init quote');
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const response = await rawResponse.json();
|
|
130
|
+
tradeInfo = response.data;
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.log('Error while init quote');
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return tradeInfo;
|
|
136
|
+
}
|
|
137
|
+
async getApprovalStep(params) {
|
|
138
|
+
// todo: handle this when support route has approve step
|
|
139
|
+
// const selectedQuote = params.selectedQuote;
|
|
140
|
+
//
|
|
141
|
+
// if (selectedQuote) {
|
|
142
|
+
// const metadata = selectedQuote.metadata as OptimexMetadata;
|
|
143
|
+
// }
|
|
144
|
+
|
|
145
|
+
return Promise.resolve(undefined);
|
|
146
|
+
}
|
|
147
|
+
async getSubmitStep(params, stepIndex) {
|
|
148
|
+
var _this$currentTradeMet;
|
|
149
|
+
if (!params.selectedQuote) {
|
|
150
|
+
return Promise.resolve(undefined);
|
|
151
|
+
}
|
|
152
|
+
const originTokenInfo = this.chainService.getAssetBySlug(params.selectedQuote.pair.from);
|
|
153
|
+
const originChain = this.chainService.getChainInfoByKey(originTokenInfo.originChain);
|
|
154
|
+
const destinationTokenInfo = this.chainService.getAssetBySlug(params.selectedQuote.pair.to);
|
|
155
|
+
const destinationChain = this.chainService.getChainInfoByKey(destinationTokenInfo.originChain);
|
|
156
|
+
const originChainType = _chainInfoToChainType(originChain);
|
|
157
|
+
const originChainNativeTokenSlug = _getChainNativeTokenSlug(originChain);
|
|
158
|
+
|
|
159
|
+
// Optimex do not return fee in quote. Need calculate network fee manually from client side
|
|
160
|
+
let networkFeeAmount;
|
|
161
|
+
const depositAddress = (_this$currentTradeMet = this.currentTradeMetadata) === null || _this$currentTradeMet === void 0 ? void 0 : _this$currentTradeMet.deposit_address;
|
|
162
|
+
if (!depositAddress) {
|
|
163
|
+
console.log('Optimex Trade metadata is undefined, request for new quote');
|
|
164
|
+
return Promise.resolve(undefined);
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
if (originChainType === ChainType.EVM) {
|
|
168
|
+
const evmApi = this.chainService.getEvmApi(originChain.slug);
|
|
169
|
+
const feeInfo = await this.swapBaseHandler.feeService.subscribeChainFee(getId(), originChain.slug, 'evm');
|
|
170
|
+
let transactionConfig;
|
|
171
|
+
if (_isNativeToken(originTokenInfo)) {
|
|
172
|
+
var _this$currentTradeMet2;
|
|
173
|
+
[transactionConfig] = await getEVMTransactionObject({
|
|
174
|
+
chain: originChain.slug,
|
|
175
|
+
evmApi,
|
|
176
|
+
from: params.request.address,
|
|
177
|
+
to: depositAddress,
|
|
178
|
+
value: params.request.fromAmount,
|
|
179
|
+
feeInfo,
|
|
180
|
+
transferAll: false,
|
|
181
|
+
fallbackFee: true,
|
|
182
|
+
data: (_this$currentTradeMet2 = this.currentTradeMetadata) === null || _this$currentTradeMet2 === void 0 ? void 0 : _this$currentTradeMet2.payload
|
|
183
|
+
});
|
|
184
|
+
} else {
|
|
185
|
+
[transactionConfig] = await getERC20TransactionObject({
|
|
186
|
+
assetAddress: _getContractAddressOfToken(originTokenInfo),
|
|
187
|
+
chain: originChain.slug,
|
|
188
|
+
evmApi: this.chainService.getEvmApi(originChain.slug),
|
|
189
|
+
from: params.request.address,
|
|
190
|
+
to: depositAddress,
|
|
191
|
+
value: params.request.fromAmount,
|
|
192
|
+
feeInfo,
|
|
193
|
+
transferAll: false
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
networkFeeAmount = await estimateTxFee(transactionConfig, evmApi, feeInfo);
|
|
197
|
+
} else if (originChainType === ChainType.BITCOIN) {
|
|
198
|
+
const bitcoinApi = this.chainService.getBitcoinApi(originChain.slug);
|
|
199
|
+
const feeInfo = await this.swapBaseHandler.feeService.subscribeChainFee(getId(), originChain.slug, 'bitcoin');
|
|
200
|
+
const network = originChain.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin;
|
|
201
|
+
const [transaction] = await createBitcoinTransaction({
|
|
202
|
+
bitcoinApi,
|
|
203
|
+
chain: originChain.slug,
|
|
204
|
+
from: params.request.address,
|
|
205
|
+
feeInfo,
|
|
206
|
+
to: depositAddress,
|
|
207
|
+
transferAll: false,
|
|
208
|
+
value: params.request.fromAmount,
|
|
209
|
+
network
|
|
210
|
+
});
|
|
211
|
+
const feeCombine = combineBitcoinFee(feeInfo, undefined, undefined); // todo: recheck when implement custom fee
|
|
212
|
+
|
|
213
|
+
const recipients = [];
|
|
214
|
+
for (const txOutput of transaction.txOutputs) {
|
|
215
|
+
txOutput.address && recipients.push(txOutput.address);
|
|
216
|
+
}
|
|
217
|
+
const sizeInfo = getSizeInfo({
|
|
218
|
+
inputLength: transaction.inputCount,
|
|
219
|
+
recipients: recipients,
|
|
220
|
+
sender: params.request.address
|
|
221
|
+
});
|
|
222
|
+
networkFeeAmount = Math.ceil(feeCombine.feeRate * sizeInfo.txVBytes).toString();
|
|
223
|
+
} else {
|
|
224
|
+
console.log('Unsupported swap from this chain type', originChainType);
|
|
225
|
+
return Promise.resolve(undefined);
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
throw new Error(e.message);
|
|
229
|
+
}
|
|
230
|
+
const networkFee = {
|
|
231
|
+
amount: networkFeeAmount || '0',
|
|
232
|
+
feeType: SwapFeeType.NETWORK_FEE,
|
|
233
|
+
tokenSlug: originChainNativeTokenSlug
|
|
234
|
+
};
|
|
235
|
+
const submitStep = {
|
|
236
|
+
name: 'Swap',
|
|
237
|
+
type: SwapStepType.SWAP,
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
metadata: {
|
|
240
|
+
sendingValue: params.request.fromAmount.toString(),
|
|
241
|
+
expectedReceive: params.selectedQuote.toAmount,
|
|
242
|
+
originTokenInfo,
|
|
243
|
+
destinationTokenInfo,
|
|
244
|
+
sender: _reformatAddressWithChain(params.request.address, originChain),
|
|
245
|
+
receiver: _reformatAddressWithChain(params.request.recipient || params.request.address, destinationChain),
|
|
246
|
+
version: 2
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
const feeInfo = {
|
|
250
|
+
defaultFeeToken: params.selectedQuote.feeInfo.defaultFeeToken,
|
|
251
|
+
feeComponent: [...params.selectedQuote.feeInfo.feeComponent, networkFee],
|
|
252
|
+
feeOptions: params.selectedQuote.feeInfo.feeOptions
|
|
253
|
+
};
|
|
254
|
+
return Promise.resolve([submitStep, feeInfo]);
|
|
255
|
+
}
|
|
256
|
+
async validateSwapProcessV2(params) {
|
|
257
|
+
const {
|
|
258
|
+
process,
|
|
259
|
+
selectedQuote
|
|
260
|
+
} = params;
|
|
261
|
+
if (BigNumber(selectedQuote.fromAmount).lte(0)) {
|
|
262
|
+
return [new TransactionError(BasicTxErrorType.INVALID_PARAMS, 'Amount must be greater than 0')];
|
|
263
|
+
}
|
|
264
|
+
const actionList = JSON.stringify(process.path.map(step => step.action));
|
|
265
|
+
const swap = actionList === JSON.stringify([DynamicSwapType.SWAP]);
|
|
266
|
+
const swapXcm = actionList === JSON.stringify([DynamicSwapType.SWAP, DynamicSwapType.BRIDGE]);
|
|
267
|
+
const xcmSwap = actionList === JSON.stringify([DynamicSwapType.BRIDGE, DynamicSwapType.SWAP]);
|
|
268
|
+
const xcmSwapXcm = actionList === JSON.stringify([DynamicSwapType.BRIDGE, DynamicSwapType.SWAP, DynamicSwapType.BRIDGE]);
|
|
269
|
+
const swapIndex = params.process.steps.findIndex(step => step.type === SwapStepType.SWAP);
|
|
270
|
+
if (swapIndex <= -1) {
|
|
271
|
+
return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
|
|
272
|
+
}
|
|
273
|
+
if (swap) {
|
|
274
|
+
return this.swapBaseHandler.validateSwapOnlyProcess(params, swapIndex);
|
|
275
|
+
}
|
|
276
|
+
if (swapXcm) {
|
|
277
|
+
return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
|
|
278
|
+
}
|
|
279
|
+
if (xcmSwap) {
|
|
280
|
+
return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
|
|
281
|
+
}
|
|
282
|
+
if (xcmSwapXcm) {
|
|
283
|
+
return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
|
|
284
|
+
}
|
|
285
|
+
return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
|
|
286
|
+
}
|
|
287
|
+
async handleSwapProcess(params) {
|
|
288
|
+
const {
|
|
289
|
+
currentStep,
|
|
290
|
+
process
|
|
291
|
+
} = params;
|
|
292
|
+
const type = process.steps[currentStep].type;
|
|
293
|
+
switch (type) {
|
|
294
|
+
case CommonStepType.DEFAULT:
|
|
295
|
+
return Promise.reject(new TransactionError(BasicTxErrorType.UNSUPPORTED));
|
|
296
|
+
case CommonStepType.TOKEN_APPROVAL:
|
|
297
|
+
return this.handleApproveStep(params);
|
|
298
|
+
case SwapStepType.SWAP:
|
|
299
|
+
return this.handleSubmitStep(params);
|
|
300
|
+
default:
|
|
301
|
+
return this.handleSubmitStep(params);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
handleApproveStep(params) {
|
|
305
|
+
// todo: handle this when support route has approve step
|
|
306
|
+
const fromAsset = this.chainService.getAssetBySlug(params.quote.pair.from);
|
|
307
|
+
return {
|
|
308
|
+
txChain: fromAsset.originChain,
|
|
309
|
+
txData: '',
|
|
310
|
+
extrinsic: {},
|
|
311
|
+
extrinsicType: ExtrinsicType.TOKEN_SPENDING_APPROVAL,
|
|
312
|
+
transferNativeAmount: '0',
|
|
313
|
+
chainType: ChainType.EVM
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
async handleSubmitStep(params) {
|
|
317
|
+
var _this$currentTradeMet3, _this$currentTradeMet4, _this$currentTradeMet5;
|
|
318
|
+
const {
|
|
319
|
+
address,
|
|
320
|
+
process,
|
|
321
|
+
quote,
|
|
322
|
+
recipient,
|
|
323
|
+
slippage
|
|
324
|
+
} = params;
|
|
325
|
+
const pair = quote.pair;
|
|
326
|
+
const fromAsset = this.chainService.getAssetBySlug(pair.from);
|
|
327
|
+
const chainInfo = this.chainService.getChainInfoByKey(fromAsset.originChain);
|
|
328
|
+
const chainType = _chainInfoToChainType(chainInfo);
|
|
329
|
+
const depositAddress = (_this$currentTradeMet3 = this.currentTradeMetadata) === null || _this$currentTradeMet3 === void 0 ? void 0 : _this$currentTradeMet3.deposit_address;
|
|
330
|
+
const tradeId = (_this$currentTradeMet4 = this.currentTradeMetadata) === null || _this$currentTradeMet4 === void 0 ? void 0 : _this$currentTradeMet4.trade_id;
|
|
331
|
+
const payload = (_this$currentTradeMet5 = this.currentTradeMetadata) === null || _this$currentTradeMet5 === void 0 ? void 0 : _this$currentTradeMet5.payload; // undefined in case swap from btc
|
|
332
|
+
|
|
333
|
+
if (!depositAddress || !tradeId) {
|
|
334
|
+
throw new Error('Optimex Trade metadata is undefined, request for new quote');
|
|
335
|
+
}
|
|
336
|
+
const txData = {
|
|
337
|
+
address,
|
|
338
|
+
provider: this.providerInfo,
|
|
339
|
+
quote,
|
|
340
|
+
slippage,
|
|
341
|
+
recipient,
|
|
342
|
+
process
|
|
343
|
+
};
|
|
344
|
+
let extrinsic;
|
|
345
|
+
|
|
346
|
+
// dont remove this log
|
|
347
|
+
console.log('Optimex Trade metadata:', this.currentTradeMetadata);
|
|
348
|
+
console.log('Optimex Trade channel:', this.isTestnet ? `https://provider-api-docs.vercel.app/swap/${tradeId}` : `https://provider-api-docs.vercel.app/swap/${tradeId}?env=sub_wallet`);
|
|
349
|
+
if (chainType === ChainType.BITCOIN) {
|
|
350
|
+
const bitcoinApi = this.chainService.getBitcoinApi(chainInfo.slug);
|
|
351
|
+
const feeInfo = await this.swapBaseHandler.feeService.subscribeChainFee(getId(), chainInfo.slug, 'bitcoin');
|
|
352
|
+
const network = chainInfo.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin;
|
|
353
|
+
const [transaction] = await createBitcoinTransaction({
|
|
354
|
+
bitcoinApi,
|
|
355
|
+
chain: chainInfo.slug,
|
|
356
|
+
from: address,
|
|
357
|
+
feeInfo,
|
|
358
|
+
to: depositAddress,
|
|
359
|
+
transferAll: false,
|
|
360
|
+
value: quote.fromAmount,
|
|
361
|
+
network
|
|
362
|
+
});
|
|
363
|
+
extrinsic = transaction;
|
|
364
|
+
} else if (chainType === ChainType.EVM) {
|
|
365
|
+
const feeInfo = await this.swapBaseHandler.feeService.subscribeChainFee(getId(), chainInfo.slug, 'evm');
|
|
366
|
+
if (_isNativeToken(fromAsset)) {
|
|
367
|
+
const [transactionConfig] = await getEVMTransactionObject({
|
|
368
|
+
chain: chainInfo.slug,
|
|
369
|
+
evmApi: this.chainService.getEvmApi(chainInfo.slug),
|
|
370
|
+
from: address,
|
|
371
|
+
to: depositAddress,
|
|
372
|
+
value: quote.fromAmount,
|
|
373
|
+
feeInfo,
|
|
374
|
+
transferAll: false,
|
|
375
|
+
data: payload
|
|
376
|
+
});
|
|
377
|
+
extrinsic = {
|
|
378
|
+
...transactionConfig,
|
|
379
|
+
data: payload
|
|
380
|
+
};
|
|
381
|
+
} else {
|
|
382
|
+
const [transactionConfig] = await getERC20TransactionObject({
|
|
383
|
+
assetAddress: _getContractAddressOfToken(fromAsset),
|
|
384
|
+
chain: chainInfo.slug,
|
|
385
|
+
evmApi: this.chainService.getEvmApi(chainInfo.slug),
|
|
386
|
+
from: address,
|
|
387
|
+
to: depositAddress,
|
|
388
|
+
value: quote.fromAmount,
|
|
389
|
+
feeInfo,
|
|
390
|
+
transferAll: false
|
|
391
|
+
});
|
|
392
|
+
extrinsic = transactionConfig;
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
throw new Error('Unknown swap chain type');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// reset tradeMetadata after use // todo: review to check if need this clear
|
|
399
|
+
// this.currentTradeMetadata = undefined;
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
txChain: fromAsset.originChain,
|
|
403
|
+
txData,
|
|
404
|
+
extrinsic,
|
|
405
|
+
transferNativeAmount: _isNativeToken(fromAsset) ? quote.fromAmount : '0',
|
|
406
|
+
extrinsicType: ExtrinsicType.SWAP,
|
|
407
|
+
chainType
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { SwapError } from '@subwallet/extension-base/background/errors/SwapError';
|
|
5
5
|
import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
|
|
6
6
|
import { ChainType, ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
|
|
7
|
-
import { _getAssetDecimals, _getAssetSymbol, _getContractAddressOfToken,
|
|
7
|
+
import { _chainInfoToChainType, _getAssetDecimals, _getAssetSymbol, _getContractAddressOfToken, _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils';
|
|
8
8
|
import { BasicTxErrorType, CommonStepType, DynamicSwapType, SwapErrorType, SwapProviderId, SwapStepType } from '@subwallet/extension-base/types';
|
|
9
9
|
import { ProxyServiceRoute } from '@subwallet/extension-base/types/environment';
|
|
10
10
|
import { _reformatAddressWithChain, fetchFromProxyService, formatNumber } from '@subwallet/extension-base/utils';
|
|
@@ -159,7 +159,7 @@ export class SimpleSwapHandler {
|
|
|
159
159
|
const toAsset = this.chainService.getAssetBySlug(pair.to);
|
|
160
160
|
const chainInfo = this.chainService.getChainInfoByKey(fromAsset.originChain);
|
|
161
161
|
const toChainInfo = this.chainService.getChainInfoByKey(toAsset.originChain);
|
|
162
|
-
const chainType =
|
|
162
|
+
const chainType = _chainInfoToChainType(chainInfo);
|
|
163
163
|
const sender = _reformatAddressWithChain(address, chainInfo);
|
|
164
164
|
const receiver = _reformatAddressWithChain(recipient !== null && recipient !== void 0 ? recipient : sender, toChainInfo);
|
|
165
165
|
const fromSymbol = _getAssetSymbol(fromAsset).toLowerCase();
|
|
@@ -222,7 +222,7 @@ export class SimpleSwapHandler {
|
|
|
222
222
|
value: quote.fromAmount
|
|
223
223
|
});
|
|
224
224
|
extrinsic = submittableExtrinsic;
|
|
225
|
-
} else {
|
|
225
|
+
} else if (chainType === ChainType.EVM) {
|
|
226
226
|
const feeInfo = await this.swapBaseHandler.feeService.subscribeChainFee(getId(), chainInfo.slug, 'evm');
|
|
227
227
|
if (_isNativeToken(fromAsset)) {
|
|
228
228
|
const [transactionConfig] = await getEVMTransactionObject({
|
|
@@ -248,6 +248,8 @@ export class SimpleSwapHandler {
|
|
|
248
248
|
});
|
|
249
249
|
extrinsic = transactionConfig;
|
|
250
250
|
}
|
|
251
|
+
} else {
|
|
252
|
+
throw new Error('Unknown swap chain type');
|
|
251
253
|
}
|
|
252
254
|
return {
|
|
253
255
|
txChain: fromAsset.originChain,
|
|
@@ -19,7 +19,6 @@ export declare class SwapService implements StoppableServiceInterface {
|
|
|
19
19
|
private getDefaultProcessV2;
|
|
20
20
|
generateOptimalProcessV2(params: OptimalSwapPathParamsV2): Promise<CommonOptimalSwapPath>;
|
|
21
21
|
handleSwapRequestV2(request: SwapRequestV2): Promise<SwapRequestResult>;
|
|
22
|
-
getAvailablePath(request: SwapRequestV2): [DynamicSwapAction[], SwapRequestV2 | undefined];
|
|
23
22
|
getLatestQuoteFromSwapRequest(request: SwapRequestV2): Promise<{
|
|
24
23
|
path: DynamicSwapAction[];
|
|
25
24
|
swapQuoteResponse: SwapQuoteResponse;
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
// Copyright 2019-2022 @subwallet/extension-base
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import { COMMON_CHAIN_SLUGS } from '@subwallet/chain-list';
|
|
5
|
-
import { _AssetRefPath } from '@subwallet/chain-list/types';
|
|
6
4
|
import { SwapError } from '@subwallet/extension-base/background/errors/SwapError';
|
|
7
5
|
import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
|
|
8
6
|
import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
|
|
9
7
|
import { fetchBlockedConfigObjects, fetchLatestBlockedActionsAndFeatures, getPassConfigId } from '@subwallet/extension-base/constants';
|
|
10
8
|
import { ServiceStatus } from '@subwallet/extension-base/services/base/types';
|
|
11
|
-
import { _getAssetOriginChain
|
|
9
|
+
import { _getAssetOriginChain } from '@subwallet/extension-base/services/chain-service/utils';
|
|
12
10
|
import { AssetHubSwapHandler } from '@subwallet/extension-base/services/swap-service/handler/asset-hub';
|
|
13
11
|
import { ChainflipSwapHandler } from '@subwallet/extension-base/services/swap-service/handler/chainflip-handler';
|
|
14
12
|
import { HydradxHandler } from '@subwallet/extension-base/services/swap-service/handler/hydradx-handler';
|
|
15
|
-
import {
|
|
13
|
+
import { OptimexHandler } from '@subwallet/extension-base/services/swap-service/handler/optimex-handler';
|
|
14
|
+
import { getSwapAltToken, getTokenPairFromStep, processStepsToPathActions, SWAP_QUOTE_TIMEOUT_MAP } from '@subwallet/extension-base/services/swap-service/utils';
|
|
16
15
|
import { BasicTxErrorType, DynamicSwapType } from '@subwallet/extension-base/types';
|
|
17
16
|
import { DEFAULT_FIRST_STEP, MOCK_STEP_FEE } from '@subwallet/extension-base/types/service-base';
|
|
18
17
|
import { _SUPPORTED_SWAP_PROVIDERS, SwapErrorType, SwapProviderId, SwapStepType } from '@subwallet/extension-base/types/swap';
|
|
19
|
-
import { _reformatAddressWithChain, createPromiseHandler
|
|
18
|
+
import { _reformatAddressWithChain, createPromiseHandler } from '@subwallet/extension-base/utils';
|
|
20
19
|
import subwalletApiSdk from '@subwallet-monorepos/subwallet-services-sdk';
|
|
21
20
|
import BigN from 'bignumber.js';
|
|
22
21
|
import { t } from 'i18next';
|
|
@@ -107,6 +106,7 @@ export class SwapService {
|
|
|
107
106
|
const providerId = ((_params$request$curre = params.request.currentQuote) === null || _params$request$curre === void 0 ? void 0 : _params$request$curre.id) || params.selectedQuote.provider.id;
|
|
108
107
|
const handler = this.handlers[providerId];
|
|
109
108
|
if (handler) {
|
|
109
|
+
// todo: handle error response from generateOptimalProcess
|
|
110
110
|
return handler.generateOptimalProcessV2(params);
|
|
111
111
|
} else {
|
|
112
112
|
return this.getDefaultProcessV2(params);
|
|
@@ -144,6 +144,12 @@ export class SwapService {
|
|
|
144
144
|
} catch (e) {
|
|
145
145
|
throw new Error(e.message);
|
|
146
146
|
}
|
|
147
|
+
|
|
148
|
+
// override fee for quote because some cases need estimate network fee on Extension (i.e. Optimex)
|
|
149
|
+
if (swapQuoteResponse.optimalQuote) {
|
|
150
|
+
const swapIndex = optimalProcess.steps.findIndex(step => step.type === SwapStepType.SWAP);
|
|
151
|
+
swapQuoteResponse.optimalQuote.feeInfo.feeComponent = optimalProcess.totalFee[swapIndex].feeComponent;
|
|
152
|
+
}
|
|
147
153
|
if (swapQuoteResponse.error) {
|
|
148
154
|
return {
|
|
149
155
|
process: optimalProcess,
|
|
@@ -160,115 +166,6 @@ export class SwapService {
|
|
|
160
166
|
quote: swapQuoteResponse
|
|
161
167
|
};
|
|
162
168
|
}
|
|
163
|
-
|
|
164
|
-
// todo: rewrite this function
|
|
165
|
-
getAvailablePath(request) {
|
|
166
|
-
const {
|
|
167
|
-
address,
|
|
168
|
-
pair
|
|
169
|
-
} = request;
|
|
170
|
-
// todo: control provider tighter
|
|
171
|
-
const supportSwapChains = getSupportedSwapChains();
|
|
172
|
-
const fromToken = this.chainService.getAssetBySlug(pair.from);
|
|
173
|
-
const toToken = this.chainService.getAssetBySlug(pair.to);
|
|
174
|
-
const fromChain = _getAssetOriginChain(fromToken);
|
|
175
|
-
const toChain = _getAssetOriginChain(toToken);
|
|
176
|
-
const toChainInfo = this.chainService.getChainInfoByKey(toChain);
|
|
177
|
-
const assetRefMap = this.chainService.getAssetRefMap();
|
|
178
|
-
let process = [];
|
|
179
|
-
if (!fromToken || !toToken) {
|
|
180
|
-
throw Error('Token not found');
|
|
181
|
-
}
|
|
182
|
-
if (!fromChain || !toChain) {
|
|
183
|
-
throw Error('Token metadata error');
|
|
184
|
-
}
|
|
185
|
-
const directXcmRef = Object.values(assetRefMap).find(assetRef => assetRef.path === _AssetRefPath.XCM && assetRef.srcAsset === fromToken.slug && assetRef.destAsset === toToken.slug);
|
|
186
|
-
if (directXcmRef) {
|
|
187
|
-
return [[], undefined];
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// SWAP: 2 tokens in the same chain and chain has dex
|
|
191
|
-
if (isChainsHasSameProvider(fromChain, toChain)) {
|
|
192
|
-
// there's a dex that can support direct swapping
|
|
193
|
-
process.push(getSwapStep(fromToken.slug, toToken.slug));
|
|
194
|
-
return [process, request];
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ------------------------
|
|
198
|
-
// BRIDGE -> SWAP: Try to find a token in dest chain that can bridge from fromToken
|
|
199
|
-
const bridgeTransit = findBridgeTransitDestination(assetRefMap, fromToken, toToken);
|
|
200
|
-
if (bridgeTransit && supportSwapChains.includes(toChain)) {
|
|
201
|
-
const swapStep = getSwapStep(bridgeTransit, toToken.slug);
|
|
202
|
-
process.push(getBridgeStep(fromToken.slug, bridgeTransit));
|
|
203
|
-
process.push(swapStep);
|
|
204
|
-
return [process, {
|
|
205
|
-
...request,
|
|
206
|
-
address: reformatAddress(address, _getChainSubstrateAddressPrefix(toChainInfo)),
|
|
207
|
-
pair: swapStep.pair
|
|
208
|
-
}];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// ------------------------
|
|
212
|
-
// SWAP -> BRIDGE: Try to find a token in from chain that can bridge to toToken
|
|
213
|
-
const swapTransit = findSwapTransitDestination(assetRefMap, fromToken, toToken);
|
|
214
|
-
if (swapTransit && supportSwapChains.includes(fromChain)) {
|
|
215
|
-
const swapStep = getSwapStep(fromToken.slug, swapTransit);
|
|
216
|
-
process.push(swapStep);
|
|
217
|
-
process.push(getBridgeStep(swapTransit, toToken.slug));
|
|
218
|
-
return [process, {
|
|
219
|
-
...request,
|
|
220
|
-
pair: swapStep.pair
|
|
221
|
-
}];
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// ------------------------
|
|
225
|
-
// BRIDGE -> SWAP -> BRIDGE: Try to find a tri-step path to swap
|
|
226
|
-
const processList = [];
|
|
227
|
-
const swapPairList = [];
|
|
228
|
-
const allBridgeDestinations = findAllBridgeDestinations(assetRefMap, fromToken);
|
|
229
|
-
|
|
230
|
-
// currently find first path. Todo: return all paths or best path.
|
|
231
|
-
for (const bridgeTransit of allBridgeDestinations) {
|
|
232
|
-
process = [];
|
|
233
|
-
const bridgeDestinationInfo = this.chainService.getAssetBySlug(bridgeTransit);
|
|
234
|
-
const swapTransit = findSwapTransitDestination(assetRefMap, bridgeDestinationInfo, toToken);
|
|
235
|
-
if (bridgeTransit === swapTransit) {
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
if (swapTransit && supportSwapChains.includes(bridgeDestinationInfo.originChain)) {
|
|
239
|
-
const swapStep = getSwapStep(bridgeTransit, swapTransit);
|
|
240
|
-
process.push(getBridgeStep(fromToken.slug, bridgeTransit));
|
|
241
|
-
process.push(swapStep);
|
|
242
|
-
process.push(getBridgeStep(swapTransit, toToken.slug));
|
|
243
|
-
|
|
244
|
-
// set the highest priority to hydration provider
|
|
245
|
-
if (bridgeDestinationInfo.originChain === COMMON_CHAIN_SLUGS.HYDRADX) {
|
|
246
|
-
return [process, {
|
|
247
|
-
...request,
|
|
248
|
-
address: _reformatAddressWithChain(address, this.chainService.getChainInfoByKey(COMMON_CHAIN_SLUGS.HYDRADX)),
|
|
249
|
-
pair: swapStep.pair
|
|
250
|
-
}];
|
|
251
|
-
}
|
|
252
|
-
processList.push(process);
|
|
253
|
-
swapPairList.push(swapStep.pair);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// get first process
|
|
258
|
-
if (processList.length && swapPairList.length) {
|
|
259
|
-
const [firstProcess, firstSwapPair] = [processList[0], swapPairList[0]];
|
|
260
|
-
const chainSwap = this.chainService.getAssetBySlug(firstSwapPair.from).originChain;
|
|
261
|
-
return [firstProcess, {
|
|
262
|
-
...request,
|
|
263
|
-
address: _reformatAddressWithChain(address, this.chainService.getChainInfoByKey(chainSwap)),
|
|
264
|
-
pair: firstSwapPair
|
|
265
|
-
}];
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// todo: encapsulate each route type to function
|
|
269
|
-
|
|
270
|
-
return [[], undefined];
|
|
271
|
-
}
|
|
272
169
|
async getLatestQuoteFromSwapRequest(request) {
|
|
273
170
|
let availablePath;
|
|
274
171
|
try {
|
|
@@ -368,9 +265,6 @@ export class SwapService {
|
|
|
368
265
|
case SwapProviderId.CHAIN_FLIP_MAINNET:
|
|
369
266
|
this.handlers[providerId] = new ChainflipSwapHandler(this.chainService, this.state.balanceService, this.state.feeService, false);
|
|
370
267
|
break;
|
|
371
|
-
case SwapProviderId.HYDRADX_TESTNET:
|
|
372
|
-
this.handlers[providerId] = new HydradxHandler(this.chainService, this.state.balanceService, this.state.feeService);
|
|
373
|
-
break;
|
|
374
268
|
case SwapProviderId.HYDRADX_MAINNET:
|
|
375
269
|
this.handlers[providerId] = new HydradxHandler(this.chainService, this.state.balanceService, this.state.feeService, false);
|
|
376
270
|
break;
|
|
@@ -380,12 +274,6 @@ export class SwapService {
|
|
|
380
274
|
case SwapProviderId.KUSAMA_ASSET_HUB:
|
|
381
275
|
this.handlers[providerId] = new AssetHubSwapHandler(this.chainService, this.state.balanceService, this.state.feeService, 'statemine');
|
|
382
276
|
break;
|
|
383
|
-
// case SwapProviderId.ROCOCO_ASSET_HUB:
|
|
384
|
-
// this.handlers[providerId] = new AssetHubSwapHandler(this.chainService, this.state.balanceService, this.state.feeService, 'rococo_assethub');
|
|
385
|
-
// break;
|
|
386
|
-
case SwapProviderId.WESTEND_ASSET_HUB:
|
|
387
|
-
this.handlers[providerId] = new AssetHubSwapHandler(this.chainService, this.state.balanceService, this.state.feeService, 'westend_assethub');
|
|
388
|
-
break;
|
|
389
277
|
case SwapProviderId.SIMPLE_SWAP:
|
|
390
278
|
this.handlers[providerId] = new SimpleSwapHandler(this.chainService, this.state.balanceService, this.state.feeService);
|
|
391
279
|
break;
|
|
@@ -395,6 +283,12 @@ export class SwapService {
|
|
|
395
283
|
case SwapProviderId.KYBER:
|
|
396
284
|
this.handlers[providerId] = new KyberHandler(this.chainService, this.state.balanceService, this.state.transactionService, this.state.feeService);
|
|
397
285
|
break;
|
|
286
|
+
case SwapProviderId.OPTIMEX:
|
|
287
|
+
this.handlers[providerId] = new OptimexHandler(this.chainService, this.state.balanceService, this.state.feeService, false);
|
|
288
|
+
break;
|
|
289
|
+
case SwapProviderId.OPTIMEX_TESTNET:
|
|
290
|
+
this.handlers[providerId] = new OptimexHandler(this.chainService, this.state.balanceService, this.state.feeService, true);
|
|
291
|
+
break;
|
|
398
292
|
default:
|
|
399
293
|
throw new Error('Unsupported provider');
|
|
400
294
|
}
|
|
@@ -445,6 +339,8 @@ export class SwapService {
|
|
|
445
339
|
waitForStopped() {
|
|
446
340
|
return this.stopPromiseHandler.promise;
|
|
447
341
|
}
|
|
342
|
+
|
|
343
|
+
// todo: deprecated
|
|
448
344
|
getSwapPairs() {
|
|
449
345
|
return Object.entries(this.chainService.swapRefMap).map(([slug, assetRef]) => {
|
|
450
346
|
const fromAsset = this.chainService.getAssetBySlug(assetRef.srcAsset);
|
|
@@ -499,6 +395,8 @@ export class SwapService {
|
|
|
499
395
|
return Promise.reject(new TransactionError(BasicTxErrorType.INTERNAL_ERROR));
|
|
500
396
|
}
|
|
501
397
|
}
|
|
398
|
+
|
|
399
|
+
// todo: deprecated
|
|
502
400
|
subscribeSwapPairs(callback) {
|
|
503
401
|
return this.chainService.subscribeSwapRefMap().subscribe(refMap => {
|
|
504
402
|
const latestData = Object.entries(refMap).map(([slug, assetRef]) => {
|