@subwallet/extension-base 1.1.54-0 → 1.1.56-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 +30 -2
- package/background/KoniTypes.js +6 -0
- package/background/errors/SwapError.d.ts +6 -0
- package/background/errors/SwapError.js +57 -0
- package/background/errors/TransactionError.js +9 -0
- package/background/types.d.ts +2 -0
- package/cjs/background/KoniTypes.js +8 -1
- package/cjs/background/errors/SwapError.js +64 -0
- package/cjs/background/errors/TransactionError.js +9 -0
- package/cjs/koni/api/nft/{statemint_nft → assethub_nft}/index.js +5 -3
- package/cjs/koni/api/nft/{statemine_nft → assethub_unique}/index.js +5 -3
- package/cjs/koni/api/nft/index.js +13 -13
- package/cjs/koni/api/nft/nft.js +1 -1
- package/cjs/koni/api/nft/transfer.js +11 -15
- package/cjs/koni/api/staking/bonding/utils.js +35 -6
- package/cjs/koni/background/handlers/Extension.js +214 -102
- package/cjs/koni/background/handlers/State.js +5 -2
- package/cjs/packageInfo.js +1 -1
- package/cjs/services/balance-service/index.js +6 -3
- package/cjs/services/chain-service/constants.js +18 -4
- package/cjs/services/chain-service/index.js +39 -18
- package/cjs/services/chain-service/utils/index.js +15 -4
- 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/native-staking/amplitude.js +7 -9
- package/cjs/services/earning-service/handlers/native-staking/astar.js +4 -3
- package/cjs/services/earning-service/handlers/native-staking/para-chain.js +11 -8
- package/cjs/services/earning-service/handlers/native-staking/relay-chain.js +22 -3
- package/cjs/services/earning-service/handlers/nomination-pool/index.js +19 -5
- package/cjs/services/earning-service/service.js +0 -1
- package/cjs/services/migration-service/scripts/MigrateTransactionHistoryBySymbol.js +4 -17
- package/cjs/services/migration-service/scripts/databases/MigrateAssetSetting.js +4 -17
- package/cjs/services/migration-service/scripts/index.js +3 -3
- package/cjs/services/swap-service/handler/base-handler.js +189 -0
- package/cjs/services/swap-service/handler/chainflip-handler.js +407 -0
- package/cjs/services/swap-service/handler/hydradx-handler.js +531 -0
- package/cjs/services/swap-service/index.js +250 -0
- package/cjs/services/swap-service/utils.js +126 -0
- package/cjs/services/transaction-service/index.js +20 -0
- package/cjs/services/transaction-service/utils.js +6 -0
- package/cjs/types/fee/evm.js +1 -0
- package/cjs/types/fee/fee.js +70 -0
- package/cjs/types/fee/index.js +27 -1
- package/cjs/types/service-base.js +1 -0
- package/cjs/types/swap/index.js +50 -0
- package/cjs/utils/index.js +12 -0
- package/cjs/utils/swap.js +78 -0
- package/koni/api/nft/{statemint_nft → assethub_nft}/index.d.ts +1 -1
- package/koni/api/nft/{statemint_nft → assethub_nft}/index.js +4 -2
- package/koni/api/nft/{statemine_nft → assethub_unique}/index.d.ts +1 -1
- package/koni/api/nft/{statemine_nft → assethub_unique}/index.js +4 -2
- package/koni/api/nft/index.js +13 -13
- package/koni/api/nft/nft.js +1 -1
- package/koni/api/nft/transfer.d.ts +1 -2
- package/koni/api/nft/transfer.js +10 -13
- package/koni/api/staking/bonding/utils.d.ts +3 -1
- package/koni/api/staking/bonding/utils.js +32 -6
- package/koni/background/handlers/Extension.d.ts +6 -0
- package/koni/background/handlers/Extension.js +111 -0
- package/koni/background/handlers/State.d.ts +2 -0
- package/koni/background/handlers/State.js +5 -2
- package/package.json +85 -28
- package/packageInfo.js +1 -1
- package/services/balance-service/index.js +6 -3
- package/services/base/types.d.ts +4 -0
- package/services/chain-service/constants.js +18 -4
- package/services/chain-service/index.d.ts +4 -0
- package/services/chain-service/index.js +21 -1
- package/services/chain-service/utils/index.d.ts +7 -5
- package/services/chain-service/utils/index.js +9 -2
- 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 +1 -0
- package/services/earning-service/handlers/native-staking/amplitude.js +7 -9
- package/services/earning-service/handlers/native-staking/astar.js +4 -3
- package/services/earning-service/handlers/native-staking/para-chain.js +12 -9
- package/services/earning-service/handlers/native-staking/relay-chain.js +24 -5
- package/services/earning-service/handlers/nomination-pool/index.js +19 -5
- package/services/earning-service/service.js +0 -1
- package/services/event-service/types.d.ts +1 -0
- package/services/migration-service/scripts/MigrateTransactionHistoryBySymbol.js +4 -17
- package/services/migration-service/scripts/databases/MigrateAssetSetting.js +4 -17
- package/services/migration-service/scripts/index.js +3 -3
- package/services/swap-service/handler/base-handler.d.ts +38 -0
- package/services/swap-service/handler/base-handler.js +180 -0
- package/services/swap-service/handler/chainflip-handler.d.ts +30 -0
- package/services/swap-service/handler/chainflip-handler.js +399 -0
- package/services/swap-service/handler/hydradx-handler.d.ts +36 -0
- package/services/swap-service/handler/hydradx-handler.js +522 -0
- package/services/swap-service/index.d.ts +32 -0
- package/services/swap-service/index.js +241 -0
- package/services/swap-service/utils.d.ts +18 -0
- package/services/swap-service/utils.js +105 -0
- package/services/transaction-service/index.js +20 -0
- package/services/transaction-service/utils.d.ts +2 -0
- package/services/transaction-service/utils.js +6 -2
- package/types/fee/evm.d.ts +49 -0
- package/types/fee/evm.js +1 -0
- package/types/fee/fee.d.ts +32 -0
- package/types/fee/fee.js +63 -0
- package/types/fee/index.d.ts +2 -49
- package/types/fee/index.js +5 -1
- package/types/service-base.d.ts +10 -0
- package/types/service-base.js +1 -0
- package/types/swap/index.d.ts +168 -0
- package/types/swap/index.js +41 -0
- package/types/yield/info/chain/target.d.ts +2 -0
- package/types/yield/info/pallet.d.ts +8 -0
- package/utils/index.d.ts +1 -0
- package/utils/index.js +2 -1
- package/utils/swap.d.ts +3 -0
- package/utils/swap.js +70 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Copyright 2019-2022 @subwallet/extension-base
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { SwapError } from '@subwallet/extension-base/background/errors/SwapError';
|
|
5
|
+
import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
|
|
6
|
+
import { BasicTxErrorType } from '@subwallet/extension-base/background/KoniTypes';
|
|
7
|
+
import { ServiceStatus } from '@subwallet/extension-base/services/base/types';
|
|
8
|
+
import { ChainflipSwapHandler } from '@subwallet/extension-base/services/swap-service/handler/chainflip-handler';
|
|
9
|
+
import { HydradxHandler } from '@subwallet/extension-base/services/swap-service/handler/hydradx-handler';
|
|
10
|
+
import { DEFAULT_SWAP_FIRST_STEP, getSwapAltToken, MOCK_SWAP_FEE, SWAP_QUOTE_TIMEOUT_MAP } from '@subwallet/extension-base/services/swap-service/utils';
|
|
11
|
+
import { _SUPPORTED_SWAP_PROVIDERS, SwapErrorType, SwapProviderId, SwapStepType } from '@subwallet/extension-base/types/swap';
|
|
12
|
+
import { createPromiseHandler } from '@subwallet/extension-base/utils';
|
|
13
|
+
import { BehaviorSubject } from 'rxjs';
|
|
14
|
+
export class SwapService {
|
|
15
|
+
swapPairSubject = new BehaviorSubject([]);
|
|
16
|
+
handlers = {};
|
|
17
|
+
startPromiseHandler = createPromiseHandler();
|
|
18
|
+
stopPromiseHandler = createPromiseHandler();
|
|
19
|
+
status = ServiceStatus.NOT_INITIALIZED;
|
|
20
|
+
constructor(state) {
|
|
21
|
+
this.state = state;
|
|
22
|
+
this.eventService = state.eventService;
|
|
23
|
+
this.chainService = state.chainService;
|
|
24
|
+
}
|
|
25
|
+
async askProvidersForQuote(request) {
|
|
26
|
+
const availableQuotes = [];
|
|
27
|
+
await Promise.all(Object.values(this.handlers).map(async handler => {
|
|
28
|
+
if (handler.init && handler.isReady === false) {
|
|
29
|
+
await handler.init();
|
|
30
|
+
}
|
|
31
|
+
const quote = await handler.getSwapQuote(request);
|
|
32
|
+
if (!(quote instanceof SwapError)) {
|
|
33
|
+
availableQuotes.push({
|
|
34
|
+
quote
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
availableQuotes.push({
|
|
38
|
+
error: quote
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}));
|
|
42
|
+
return availableQuotes; // todo: need to propagate error for further handling
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getDefaultProcess(params) {
|
|
46
|
+
const result = {
|
|
47
|
+
totalFee: [MOCK_SWAP_FEE],
|
|
48
|
+
steps: [DEFAULT_SWAP_FIRST_STEP]
|
|
49
|
+
};
|
|
50
|
+
result.totalFee.push({
|
|
51
|
+
feeComponent: [],
|
|
52
|
+
feeOptions: [params.request.pair.from],
|
|
53
|
+
defaultFeeToken: params.request.pair.from
|
|
54
|
+
});
|
|
55
|
+
result.steps.push({
|
|
56
|
+
id: result.steps.length,
|
|
57
|
+
name: 'Swap',
|
|
58
|
+
type: SwapStepType.SWAP
|
|
59
|
+
});
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
async generateOptimalProcess(params) {
|
|
63
|
+
if (!params.selectedQuote) {
|
|
64
|
+
return this.getDefaultProcess(params);
|
|
65
|
+
} else {
|
|
66
|
+
const providerId = params.selectedQuote.provider.id;
|
|
67
|
+
const handler = this.handlers[providerId];
|
|
68
|
+
if (handler) {
|
|
69
|
+
return handler.generateOptimalProcess(params);
|
|
70
|
+
} else {
|
|
71
|
+
return this.getDefaultProcess(params);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async handleSwapRequest(request) {
|
|
76
|
+
/*
|
|
77
|
+
* 1. Ask swap quotes from providers
|
|
78
|
+
* 2. Select the best quote
|
|
79
|
+
* 3. Generate optimal process for that quote
|
|
80
|
+
* */
|
|
81
|
+
|
|
82
|
+
const swapQuoteResponse = await this.getLatestQuotes(request);
|
|
83
|
+
const optimalProcess = await this.generateOptimalProcess({
|
|
84
|
+
request,
|
|
85
|
+
selectedQuote: swapQuoteResponse.optimalQuote
|
|
86
|
+
});
|
|
87
|
+
console.log('optimalProcess', optimalProcess);
|
|
88
|
+
return {
|
|
89
|
+
process: optimalProcess,
|
|
90
|
+
quote: swapQuoteResponse
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async getLatestQuotes(request) {
|
|
94
|
+
request.pair.metadata = this.getSwapPairMetadata(request.pair.slug); // todo: improve this
|
|
95
|
+
const quoteAskResponses = await this.askProvidersForQuote(request);
|
|
96
|
+
|
|
97
|
+
// todo: handle error to return back to UI
|
|
98
|
+
// todo: more logic to select the best quote
|
|
99
|
+
|
|
100
|
+
const availableQuotes = quoteAskResponses.filter(quote => !quote.error).map(quote => quote.quote);
|
|
101
|
+
let quoteError;
|
|
102
|
+
let selectedQuote;
|
|
103
|
+
let aliveUntil = +Date.now() + SWAP_QUOTE_TIMEOUT_MAP.default;
|
|
104
|
+
if (availableQuotes.length === 0) {
|
|
105
|
+
const preferredErrorResp = quoteAskResponses.find(quote => {
|
|
106
|
+
return !!quote.error && ![SwapErrorType.UNKNOWN, SwapErrorType.ASSET_NOT_SUPPORTED].includes(quote.error.errorType);
|
|
107
|
+
});
|
|
108
|
+
const defaultErrorResp = quoteAskResponses.find(quote => !!quote.error);
|
|
109
|
+
quoteError = (preferredErrorResp === null || preferredErrorResp === void 0 ? void 0 : preferredErrorResp.error) || (defaultErrorResp === null || defaultErrorResp === void 0 ? void 0 : defaultErrorResp.error);
|
|
110
|
+
} else {
|
|
111
|
+
var _selectedQuote;
|
|
112
|
+
selectedQuote = availableQuotes[0];
|
|
113
|
+
aliveUntil = ((_selectedQuote = selectedQuote) === null || _selectedQuote === void 0 ? void 0 : _selectedQuote.aliveUntil) || +Date.now() + SWAP_QUOTE_TIMEOUT_MAP.default;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
optimalQuote: selectedQuote,
|
|
117
|
+
quotes: availableQuotes,
|
|
118
|
+
error: quoteError,
|
|
119
|
+
aliveUntil
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
initHandlers() {
|
|
123
|
+
_SUPPORTED_SWAP_PROVIDERS.forEach(providerId => {
|
|
124
|
+
switch (providerId) {
|
|
125
|
+
case SwapProviderId.CHAIN_FLIP_TESTNET:
|
|
126
|
+
this.handlers[providerId] = new ChainflipSwapHandler(this.chainService, this.state.balanceService);
|
|
127
|
+
break;
|
|
128
|
+
case SwapProviderId.CHAIN_FLIP_MAINNET:
|
|
129
|
+
this.handlers[providerId] = new ChainflipSwapHandler(this.chainService, this.state.balanceService, false);
|
|
130
|
+
break;
|
|
131
|
+
case SwapProviderId.HYDRADX_TESTNET:
|
|
132
|
+
this.handlers[providerId] = new HydradxHandler(this.chainService, this.state.balanceService);
|
|
133
|
+
break;
|
|
134
|
+
case SwapProviderId.HYDRADX_MAINNET:
|
|
135
|
+
this.handlers[providerId] = new HydradxHandler(this.chainService, this.state.balanceService, false);
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
throw new Error('Unsupported provider');
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
async init() {
|
|
143
|
+
this.status = ServiceStatus.INITIALIZING;
|
|
144
|
+
this.eventService.emit('swap.ready', true);
|
|
145
|
+
this.status = ServiceStatus.INITIALIZED;
|
|
146
|
+
this.initHandlers();
|
|
147
|
+
await this.start();
|
|
148
|
+
}
|
|
149
|
+
async start() {
|
|
150
|
+
if (this.status === ServiceStatus.STOPPING) {
|
|
151
|
+
await this.waitForStopped();
|
|
152
|
+
}
|
|
153
|
+
if (this.status === ServiceStatus.STARTED || this.status === ServiceStatus.STARTING) {
|
|
154
|
+
return this.waitForStarted();
|
|
155
|
+
}
|
|
156
|
+
this.status = ServiceStatus.STARTING;
|
|
157
|
+
|
|
158
|
+
// todo: start the service jobs, subscribe data,...
|
|
159
|
+
|
|
160
|
+
this.swapPairSubject.next(this.getSwapPairs()); // todo: might need to change it online
|
|
161
|
+
|
|
162
|
+
// Update promise handler
|
|
163
|
+
this.startPromiseHandler.resolve();
|
|
164
|
+
this.stopPromiseHandler = createPromiseHandler();
|
|
165
|
+
this.status = ServiceStatus.STARTED;
|
|
166
|
+
}
|
|
167
|
+
async stop() {
|
|
168
|
+
if (this.status === ServiceStatus.STARTING) {
|
|
169
|
+
await this.waitForStarted();
|
|
170
|
+
}
|
|
171
|
+
if (this.status === ServiceStatus.STOPPED || this.status === ServiceStatus.STOPPING) {
|
|
172
|
+
return this.waitForStopped();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// todo: unsub, persist data,...
|
|
176
|
+
|
|
177
|
+
this.stopPromiseHandler.resolve();
|
|
178
|
+
this.startPromiseHandler = createPromiseHandler();
|
|
179
|
+
this.status = ServiceStatus.STOPPED;
|
|
180
|
+
}
|
|
181
|
+
waitForStarted() {
|
|
182
|
+
return this.startPromiseHandler.promise;
|
|
183
|
+
}
|
|
184
|
+
waitForStopped() {
|
|
185
|
+
return this.stopPromiseHandler.promise;
|
|
186
|
+
}
|
|
187
|
+
getSwapPairs() {
|
|
188
|
+
return Object.entries(this.chainService.swapRefMap).map(([slug, assetRef]) => {
|
|
189
|
+
const fromAsset = this.chainService.getAssetBySlug(assetRef.srcAsset);
|
|
190
|
+
return {
|
|
191
|
+
slug,
|
|
192
|
+
from: assetRef.srcAsset,
|
|
193
|
+
to: assetRef.destAsset,
|
|
194
|
+
metadata: {
|
|
195
|
+
alternativeAsset: getSwapAltToken(fromAsset)
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
getSwapPairMetadata(slug) {
|
|
201
|
+
var _this$getSwapPairs$fi;
|
|
202
|
+
return (_this$getSwapPairs$fi = this.getSwapPairs().find(pair => pair.slug === slug)) === null || _this$getSwapPairs$fi === void 0 ? void 0 : _this$getSwapPairs$fi.metadata;
|
|
203
|
+
}
|
|
204
|
+
async validateSwapProcess(params) {
|
|
205
|
+
const providerId = params.selectedQuote.provider.id;
|
|
206
|
+
const handler = this.handlers[providerId];
|
|
207
|
+
if (handler) {
|
|
208
|
+
return handler.validateSwapProcess(params);
|
|
209
|
+
} else {
|
|
210
|
+
return [new TransactionError(BasicTxErrorType.INTERNAL_ERROR)];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async handleSwapProcess(params) {
|
|
214
|
+
const handler = this.handlers[params.quote.provider.id];
|
|
215
|
+
if (params.process.steps.length === 1) {
|
|
216
|
+
// todo: do better to handle error generating steps
|
|
217
|
+
return Promise.reject(new TransactionError(BasicTxErrorType.INTERNAL_ERROR, 'Please check your network and try again'));
|
|
218
|
+
}
|
|
219
|
+
if (handler) {
|
|
220
|
+
return handler.handleSwapProcess(params);
|
|
221
|
+
} else {
|
|
222
|
+
return Promise.reject(new TransactionError(BasicTxErrorType.INTERNAL_ERROR));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
subscribeSwapPairs(callback) {
|
|
226
|
+
return this.chainService.subscribeSwapRefMap().subscribe(refMap => {
|
|
227
|
+
const latestData = Object.entries(refMap).map(([slug, assetRef]) => {
|
|
228
|
+
const fromAsset = this.chainService.getAssetBySlug(assetRef.srcAsset);
|
|
229
|
+
return {
|
|
230
|
+
slug,
|
|
231
|
+
from: assetRef.srcAsset,
|
|
232
|
+
to: assetRef.destAsset,
|
|
233
|
+
metadata: {
|
|
234
|
+
alternativeAsset: getSwapAltToken(fromAsset)
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
callback(latestData);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Asset, Chain } from '@chainflip/sdk/swap';
|
|
2
|
+
import { _ChainAsset } from '@subwallet/chain-list/types';
|
|
3
|
+
import { SwapError } from '@subwallet/extension-base/background/errors/SwapError';
|
|
4
|
+
import { ChainflipPreValidationMetadata, HydradxPreValidationMetadata, SwapErrorType, SwapFeeInfo, SwapPair, SwapStepDetail } from '@subwallet/extension-base/types/swap';
|
|
5
|
+
export declare const CHAIN_FLIP_TESTNET_EXPLORER = "https://blocks-perseverance.chainflip.io";
|
|
6
|
+
export declare const CHAIN_FLIP_MAINNET_EXPLORER = "https://scan.chainflip.io";
|
|
7
|
+
export declare const CHAIN_FLIP_SUPPORTED_MAINNET_MAPPING: Record<string, Chain>;
|
|
8
|
+
export declare const CHAIN_FLIP_SUPPORTED_TESTNET_MAPPING: Record<string, Chain>;
|
|
9
|
+
export declare const CHAIN_FLIP_SUPPORTED_MAINNET_ASSET_MAPPING: Record<string, Asset>;
|
|
10
|
+
export declare const CHAIN_FLIP_SUPPORTED_TESTNET_ASSET_MAPPING: Record<string, Asset>;
|
|
11
|
+
export declare const SWAP_QUOTE_TIMEOUT_MAP: Record<string, number>;
|
|
12
|
+
export declare const DEFAULT_SWAP_FIRST_STEP: SwapStepDetail;
|
|
13
|
+
export declare const MOCK_SWAP_FEE: SwapFeeInfo;
|
|
14
|
+
export declare function getSwapAlternativeAsset(swapPair: SwapPair): string | undefined;
|
|
15
|
+
export declare function getSwapAltToken(chainAsset: _ChainAsset): string | undefined;
|
|
16
|
+
export declare function calculateSwapRate(fromAmount: string, toAmount: string, fromAsset: _ChainAsset, toAsset: _ChainAsset): number;
|
|
17
|
+
export declare function getChainflipEarlyValidationError(error: SwapErrorType, metadata: ChainflipPreValidationMetadata): SwapError;
|
|
18
|
+
export declare function getEarlyHydradxValidationError(error: SwapErrorType, metadata: HydradxPreValidationMetadata): SwapError;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Copyright 2019-2022 @subwallet/extension-base
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { Assets, Chains } from '@chainflip/sdk/swap';
|
|
5
|
+
import { COMMON_ASSETS, COMMON_CHAIN_SLUGS } from '@subwallet/chain-list';
|
|
6
|
+
import { SwapError } from '@subwallet/extension-base/background/errors/SwapError';
|
|
7
|
+
import { _getAssetDecimals } from '@subwallet/extension-base/services/chain-service/utils';
|
|
8
|
+
import { SwapErrorType, SwapProviderId, SwapStepType } from '@subwallet/extension-base/types/swap';
|
|
9
|
+
import { formatNumber } from '@subwallet/extension-base/utils';
|
|
10
|
+
import BigN from 'bignumber.js';
|
|
11
|
+
export const CHAIN_FLIP_TESTNET_EXPLORER = 'https://blocks-perseverance.chainflip.io';
|
|
12
|
+
export const CHAIN_FLIP_MAINNET_EXPLORER = 'https://scan.chainflip.io';
|
|
13
|
+
export const CHAIN_FLIP_SUPPORTED_MAINNET_MAPPING = {
|
|
14
|
+
[COMMON_CHAIN_SLUGS.POLKADOT]: Chains.Polkadot,
|
|
15
|
+
[COMMON_CHAIN_SLUGS.ETHEREUM]: Chains.Ethereum
|
|
16
|
+
};
|
|
17
|
+
export const CHAIN_FLIP_SUPPORTED_TESTNET_MAPPING = {
|
|
18
|
+
[COMMON_CHAIN_SLUGS.ETHEREUM_SEPOLIA]: Chains.Ethereum,
|
|
19
|
+
[COMMON_CHAIN_SLUGS.CHAINFLIP_POLKADOT]: Chains.Polkadot
|
|
20
|
+
};
|
|
21
|
+
export const CHAIN_FLIP_SUPPORTED_MAINNET_ASSET_MAPPING = {
|
|
22
|
+
[COMMON_ASSETS.DOT]: Assets.DOT,
|
|
23
|
+
[COMMON_ASSETS.ETH]: Assets.ETH,
|
|
24
|
+
[COMMON_ASSETS.USDC_ETHEREUM]: Assets.USDC
|
|
25
|
+
};
|
|
26
|
+
export const CHAIN_FLIP_SUPPORTED_TESTNET_ASSET_MAPPING = {
|
|
27
|
+
[COMMON_ASSETS.PDOT]: Assets.DOT,
|
|
28
|
+
[COMMON_ASSETS.ETH_SEPOLIA]: Assets.ETH,
|
|
29
|
+
[COMMON_ASSETS.USDC_SEPOLIA]: Assets.USDC
|
|
30
|
+
};
|
|
31
|
+
export const SWAP_QUOTE_TIMEOUT_MAP = {
|
|
32
|
+
// in milliseconds
|
|
33
|
+
default: 30000,
|
|
34
|
+
[SwapProviderId.CHAIN_FLIP_TESTNET]: 30000,
|
|
35
|
+
[SwapProviderId.CHAIN_FLIP_MAINNET]: 30000
|
|
36
|
+
};
|
|
37
|
+
export const DEFAULT_SWAP_FIRST_STEP = {
|
|
38
|
+
id: 0,
|
|
39
|
+
name: 'Fill information',
|
|
40
|
+
type: SwapStepType.DEFAULT
|
|
41
|
+
};
|
|
42
|
+
export const MOCK_SWAP_FEE = {
|
|
43
|
+
feeComponent: [],
|
|
44
|
+
defaultFeeToken: '',
|
|
45
|
+
feeOptions: []
|
|
46
|
+
};
|
|
47
|
+
export function getSwapAlternativeAsset(swapPair) {
|
|
48
|
+
var _swapPair$metadata;
|
|
49
|
+
return swapPair === null || swapPair === void 0 ? void 0 : (_swapPair$metadata = swapPair.metadata) === null || _swapPair$metadata === void 0 ? void 0 : _swapPair$metadata.alternativeAsset;
|
|
50
|
+
}
|
|
51
|
+
export function getSwapAltToken(chainAsset) {
|
|
52
|
+
var _chainAsset$metadata;
|
|
53
|
+
return (_chainAsset$metadata = chainAsset.metadata) === null || _chainAsset$metadata === void 0 ? void 0 : _chainAsset$metadata.alternativeSwapAsset;
|
|
54
|
+
}
|
|
55
|
+
export function calculateSwapRate(fromAmount, toAmount, fromAsset, toAsset) {
|
|
56
|
+
const bnFromAmount = new BigN(fromAmount);
|
|
57
|
+
const bnToAmount = new BigN(toAmount);
|
|
58
|
+
const decimalDiff = _getAssetDecimals(toAsset) - _getAssetDecimals(fromAsset);
|
|
59
|
+
const bnRate = bnFromAmount.div(bnToAmount);
|
|
60
|
+
return 1 / bnRate.times(10 ** decimalDiff).toNumber();
|
|
61
|
+
}
|
|
62
|
+
export function getChainflipEarlyValidationError(error, metadata) {
|
|
63
|
+
// todo: support more providers
|
|
64
|
+
switch (error) {
|
|
65
|
+
case SwapErrorType.NOT_MEET_MIN_SWAP:
|
|
66
|
+
{
|
|
67
|
+
const parsedMinSwapValue = formatNumber(metadata.minSwap.value, metadata.minSwap.decimals);
|
|
68
|
+
const message = `Amount too low. Increase your amount above ${parsedMinSwapValue} ${metadata.minSwap.symbol} and try again`;
|
|
69
|
+
return new SwapError(error, message);
|
|
70
|
+
}
|
|
71
|
+
case SwapErrorType.SWAP_EXCEED_ALLOWANCE:
|
|
72
|
+
{
|
|
73
|
+
if (metadata.maxSwap) {
|
|
74
|
+
const parsedMaxSwapValue = formatNumber(metadata.maxSwap.value, metadata.maxSwap.decimals);
|
|
75
|
+
return new SwapError(error, `Amount too high. Lower your amount below ${parsedMaxSwapValue} ${metadata.maxSwap.symbol} and try again`);
|
|
76
|
+
} else {
|
|
77
|
+
return new SwapError(error, 'Amount too high. Lower your amount and try again');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
case SwapErrorType.ASSET_NOT_SUPPORTED:
|
|
81
|
+
return new SwapError(error, 'This swap pair is not supported');
|
|
82
|
+
case SwapErrorType.UNKNOWN:
|
|
83
|
+
return new SwapError(error, `Undefined error. Check your Internet and ${metadata.chain.slug} connection or contact support`);
|
|
84
|
+
case SwapErrorType.ERROR_FETCHING_QUOTE:
|
|
85
|
+
return new SwapError(error, 'No swap quote found. Adjust your amount or try again later.');
|
|
86
|
+
default:
|
|
87
|
+
return new SwapError(error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function getEarlyHydradxValidationError(error, metadata) {
|
|
91
|
+
switch (error) {
|
|
92
|
+
case SwapErrorType.AMOUNT_CANNOT_BE_ZERO:
|
|
93
|
+
{
|
|
94
|
+
return new SwapError(error, 'Amount too low. Increase your amount above 0 and try again');
|
|
95
|
+
}
|
|
96
|
+
case SwapErrorType.ASSET_NOT_SUPPORTED:
|
|
97
|
+
return new SwapError(error, 'This swap pair is not supported');
|
|
98
|
+
case SwapErrorType.UNKNOWN:
|
|
99
|
+
return new SwapError(error, `Undefined error. Check your Internet and ${metadata.chain.slug} connection or contact support`);
|
|
100
|
+
case SwapErrorType.ERROR_FETCHING_QUOTE:
|
|
101
|
+
return new SwapError(error, 'No swap quote found. Adjust your amount or try again later.');
|
|
102
|
+
default:
|
|
103
|
+
return new SwapError(error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -639,6 +639,18 @@ export default class TransactionService {
|
|
|
639
639
|
};
|
|
640
640
|
break;
|
|
641
641
|
}
|
|
642
|
+
case ExtrinsicType.SWAP:
|
|
643
|
+
{
|
|
644
|
+
const data = parseTransactionData(transaction.data); // TODO: switch by provider
|
|
645
|
+
const inputAsset = this.state.chainService.getAssetBySlug(data.quote.pair.from);
|
|
646
|
+
historyItem.amount = {
|
|
647
|
+
value: data.quote.fromAmount,
|
|
648
|
+
symbol: _getAssetSymbol(inputAsset),
|
|
649
|
+
decimals: _getAssetDecimals(inputAsset)
|
|
650
|
+
};
|
|
651
|
+
historyItem.additionalInfo = data;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
642
654
|
case ExtrinsicType.UNKNOWN:
|
|
643
655
|
break;
|
|
644
656
|
}
|
|
@@ -732,6 +744,14 @@ export default class TransactionService {
|
|
|
732
744
|
}
|
|
733
745
|
} else if ([ExtrinsicType.STAKING_BOND, ExtrinsicType.STAKING_UNBOND, ExtrinsicType.STAKING_WITHDRAW, ExtrinsicType.STAKING_CANCEL_UNSTAKE, ExtrinsicType.STAKING_CLAIM_REWARD, ExtrinsicType.STAKING_JOIN_POOL, ExtrinsicType.STAKING_POOL_WITHDRAW, ExtrinsicType.STAKING_LEAVE_POOL].includes(transaction.extrinsicType)) {
|
|
734
746
|
this.state.eventService.emit('transaction.submitStaking', transaction.chain);
|
|
747
|
+
} else if (transaction.extrinsicType === ExtrinsicType.SWAP) {
|
|
748
|
+
const inputData = parseTransactionData(transaction.data);
|
|
749
|
+
const toAssetSlug = inputData.quote.pair.to;
|
|
750
|
+
|
|
751
|
+
// todo: consider async
|
|
752
|
+
this.state.chainService.updateAssetSetting(toAssetSlug, {
|
|
753
|
+
visible: true
|
|
754
|
+
}, true).catch(console.error);
|
|
735
755
|
}
|
|
736
756
|
}
|
|
737
757
|
onSuccess({
|
|
@@ -1,4 +1,6 @@
|
|
|
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
4
|
export declare function parseTransactionData<T extends ExtrinsicType>(data: unknown): ExtrinsicDataTypeMap[T];
|
|
4
5
|
export declare function getExplorerLink(chainInfo: _ChainInfo, value: string, type: 'account' | 'tx'): string | undefined;
|
|
6
|
+
export declare function getChainflipExplorerLink(data: ChainflipSwapTxData, chainInfo: _ChainInfo): string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Copyright 2019-2022 @subwallet/extension-base authors & contributors
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import { _getBlockExplorerFromChain, _isPureEvmChain } from '@subwallet/extension-base/services/chain-service/utils';
|
|
5
|
-
|
|
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';
|
|
6
6
|
// @ts-ignore
|
|
7
7
|
export function parseTransactionData(data) {
|
|
8
8
|
// @ts-ignore
|
|
@@ -47,4 +47,8 @@ export function getExplorerLink(chainInfo, value, type) {
|
|
|
47
47
|
return `${explorerLink}${explorerLink.endsWith('/') ? '' : '/'}${route}/${value}`;
|
|
48
48
|
}
|
|
49
49
|
return undefined;
|
|
50
|
+
}
|
|
51
|
+
export function getChainflipExplorerLink(data, chainInfo) {
|
|
52
|
+
const chainflipDomain = _isChainTestNet(chainInfo) ? CHAIN_FLIP_TESTNET_EXPLORER : CHAIN_FLIP_MAINNET_EXPLORER;
|
|
53
|
+
return `${chainflipDomain}/channels/${data.depositChannelId}`;
|
|
50
54
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import BigN from 'bignumber.js';
|
|
2
|
+
interface BaseFeeInfo {
|
|
3
|
+
busyNetwork: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface EvmLegacyFeeInfo extends BaseFeeInfo {
|
|
6
|
+
gasPrice: string;
|
|
7
|
+
maxFeePerGas: undefined;
|
|
8
|
+
maxPriorityFeePerGas: undefined;
|
|
9
|
+
baseGasFee: undefined;
|
|
10
|
+
}
|
|
11
|
+
export interface EvmEIP1995FeeInfo extends BaseFeeInfo {
|
|
12
|
+
gasPrice: undefined;
|
|
13
|
+
maxFeePerGas: BigN;
|
|
14
|
+
maxPriorityFeePerGas: BigN;
|
|
15
|
+
baseGasFee: BigN;
|
|
16
|
+
}
|
|
17
|
+
export declare type EvmFeeInfo = EvmLegacyFeeInfo | EvmEIP1995FeeInfo;
|
|
18
|
+
export interface EvmLegacyFeeInfoCache extends BaseFeeInfo {
|
|
19
|
+
gasPrice: string;
|
|
20
|
+
maxFeePerGas: undefined;
|
|
21
|
+
maxPriorityFeePerGas: undefined;
|
|
22
|
+
baseGasFee: undefined;
|
|
23
|
+
}
|
|
24
|
+
export interface EvmEIP1995FeeInfoCache extends BaseFeeInfo {
|
|
25
|
+
gasPrice: undefined;
|
|
26
|
+
maxFeePerGas: string;
|
|
27
|
+
maxPriorityFeePerGas: string;
|
|
28
|
+
baseGasFee: string;
|
|
29
|
+
}
|
|
30
|
+
export declare type EvmFeeInfoCache = EvmLegacyFeeInfoCache | EvmEIP1995FeeInfoCache;
|
|
31
|
+
export interface InfuraFeeDetail {
|
|
32
|
+
suggestedMaxPriorityFeePerGas: string;
|
|
33
|
+
suggestedMaxFeePerGas: string;
|
|
34
|
+
minWaitTimeEstimate: number;
|
|
35
|
+
maxWaitTimeEstimate: number;
|
|
36
|
+
}
|
|
37
|
+
export interface InfuraFeeInfo {
|
|
38
|
+
low: InfuraFeeDetail;
|
|
39
|
+
medium: InfuraFeeDetail;
|
|
40
|
+
high: InfuraFeeDetail;
|
|
41
|
+
networkCongestion: number;
|
|
42
|
+
estimatedBaseFee: string;
|
|
43
|
+
latestPriorityFeeRange: [string, string];
|
|
44
|
+
historicalPriorityFeeRange: [string, string];
|
|
45
|
+
historicalBaseFeeRange: [string, string];
|
|
46
|
+
priorityFeeTrend: 'down' | 'up';
|
|
47
|
+
baseFeeTrend: 'down' | 'up';
|
|
48
|
+
}
|
|
49
|
+
export {};
|
package/types/fee/evm.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { _ChainAsset } from '@subwallet/chain-list/types';
|
|
2
|
+
export declare enum SWFeeType {
|
|
3
|
+
EIP_1559 = "EIP_1559",
|
|
4
|
+
LEGACY = "LEGACY",
|
|
5
|
+
SUBSTRATE = "SUBSTRATE"
|
|
6
|
+
}
|
|
7
|
+
export interface FeeOption {
|
|
8
|
+
tokenInfo: _ChainAsset;
|
|
9
|
+
isDefault: boolean;
|
|
10
|
+
amount?: string;
|
|
11
|
+
type: SWFeeType;
|
|
12
|
+
metadata?: any;
|
|
13
|
+
}
|
|
14
|
+
export interface MultiFee {
|
|
15
|
+
feeOptions: FeeOption[];
|
|
16
|
+
totalFeeValue?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class SWFee {
|
|
19
|
+
chain: string;
|
|
20
|
+
feeOptions: FeeOption[];
|
|
21
|
+
private _selectedFeeOption?;
|
|
22
|
+
private _transactionId?;
|
|
23
|
+
get transactionId(): string | undefined;
|
|
24
|
+
get selectedFeeOption(): FeeOption | undefined;
|
|
25
|
+
static build(feeOptions: FeeOption[], chain: string, selectedFeeOption?: FeeOption): SWFee;
|
|
26
|
+
static buildSimpleFee(tokenInfo: _ChainAsset, amount: string, type: SWFeeType): SWFee;
|
|
27
|
+
static buildFeeOption(tokenInfo: _ChainAsset, amount: string, type: SWFeeType): FeeOption;
|
|
28
|
+
setTransactionId(transactionId: string): void;
|
|
29
|
+
setSelectedFeeOption(feeOption: FeeOption): void;
|
|
30
|
+
constructor(feeOptions: FeeOption[], chain: string, selectedFeeOption?: FeeOption);
|
|
31
|
+
getDefaultFeeOption(): FeeOption;
|
|
32
|
+
}
|
package/types/fee/fee.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Copyright 2019-2022 @subwallet/extension-base
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils';
|
|
5
|
+
export let SWFeeType;
|
|
6
|
+
(function (SWFeeType) {
|
|
7
|
+
SWFeeType["EIP_1559"] = "EIP_1559";
|
|
8
|
+
SWFeeType["LEGACY"] = "LEGACY";
|
|
9
|
+
SWFeeType["SUBSTRATE"] = "SUBSTRATE";
|
|
10
|
+
})(SWFeeType || (SWFeeType = {}));
|
|
11
|
+
export class SWFee {
|
|
12
|
+
get transactionId() {
|
|
13
|
+
return this._transactionId;
|
|
14
|
+
}
|
|
15
|
+
get selectedFeeOption() {
|
|
16
|
+
return this._selectedFeeOption;
|
|
17
|
+
}
|
|
18
|
+
static build(feeOptions, chain, selectedFeeOption) {
|
|
19
|
+
// todo: might need to parse metadata depending on the type
|
|
20
|
+
if (selectedFeeOption) {
|
|
21
|
+
return new SWFee(feeOptions, chain, selectedFeeOption);
|
|
22
|
+
} else {
|
|
23
|
+
const defaultFeeOption = feeOptions.find(option => option.isDefault);
|
|
24
|
+
return new SWFee(feeOptions, chain, defaultFeeOption);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
static buildSimpleFee(tokenInfo, amount, type) {
|
|
28
|
+
// used for simple transaction paid in native tokens
|
|
29
|
+
const defaultOption = {
|
|
30
|
+
tokenInfo,
|
|
31
|
+
isDefault: _isNativeToken(tokenInfo),
|
|
32
|
+
// if it's the native token of the chain
|
|
33
|
+
amount,
|
|
34
|
+
type
|
|
35
|
+
};
|
|
36
|
+
return new SWFee([defaultOption], tokenInfo.originChain, defaultOption);
|
|
37
|
+
}
|
|
38
|
+
static buildFeeOption(tokenInfo, amount, type) {
|
|
39
|
+
// todo: calculate totalFeeValue
|
|
40
|
+
// todo: implement more logic to handle complicated transaction process
|
|
41
|
+
return {
|
|
42
|
+
tokenInfo,
|
|
43
|
+
isDefault: _isNativeToken(tokenInfo),
|
|
44
|
+
// if it's the native token of the chain
|
|
45
|
+
amount,
|
|
46
|
+
type
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
setTransactionId(transactionId) {
|
|
50
|
+
this._transactionId = transactionId;
|
|
51
|
+
}
|
|
52
|
+
setSelectedFeeOption(feeOption) {
|
|
53
|
+
this._selectedFeeOption = feeOption;
|
|
54
|
+
}
|
|
55
|
+
constructor(feeOptions, chain, selectedFeeOption) {
|
|
56
|
+
this.chain = chain;
|
|
57
|
+
this.feeOptions = feeOptions;
|
|
58
|
+
this._selectedFeeOption = selectedFeeOption;
|
|
59
|
+
}
|
|
60
|
+
getDefaultFeeOption() {
|
|
61
|
+
return this.feeOptions.find(option => option.isDefault);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/types/fee/index.d.ts
CHANGED
|
@@ -1,49 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
busyNetwork: boolean;
|
|
4
|
-
}
|
|
5
|
-
export interface EvmLegacyFeeInfo extends BaseFeeInfo {
|
|
6
|
-
gasPrice: string;
|
|
7
|
-
maxFeePerGas: undefined;
|
|
8
|
-
maxPriorityFeePerGas: undefined;
|
|
9
|
-
baseGasFee: undefined;
|
|
10
|
-
}
|
|
11
|
-
export interface EvmEIP1995FeeInfo extends BaseFeeInfo {
|
|
12
|
-
gasPrice: undefined;
|
|
13
|
-
maxFeePerGas: BigN;
|
|
14
|
-
maxPriorityFeePerGas: BigN;
|
|
15
|
-
baseGasFee: BigN;
|
|
16
|
-
}
|
|
17
|
-
export declare type EvmFeeInfo = EvmLegacyFeeInfo | EvmEIP1995FeeInfo;
|
|
18
|
-
export interface EvmLegacyFeeInfoCache extends BaseFeeInfo {
|
|
19
|
-
gasPrice: string;
|
|
20
|
-
maxFeePerGas: undefined;
|
|
21
|
-
maxPriorityFeePerGas: undefined;
|
|
22
|
-
baseGasFee: undefined;
|
|
23
|
-
}
|
|
24
|
-
export interface EvmEIP1995FeeInfoCache extends BaseFeeInfo {
|
|
25
|
-
gasPrice: undefined;
|
|
26
|
-
maxFeePerGas: string;
|
|
27
|
-
maxPriorityFeePerGas: string;
|
|
28
|
-
baseGasFee: string;
|
|
29
|
-
}
|
|
30
|
-
export declare type EvmFeeInfoCache = EvmLegacyFeeInfoCache | EvmEIP1995FeeInfoCache;
|
|
31
|
-
export interface InfuraFeeDetail {
|
|
32
|
-
suggestedMaxPriorityFeePerGas: string;
|
|
33
|
-
suggestedMaxFeePerGas: string;
|
|
34
|
-
minWaitTimeEstimate: number;
|
|
35
|
-
maxWaitTimeEstimate: number;
|
|
36
|
-
}
|
|
37
|
-
export interface InfuraFeeInfo {
|
|
38
|
-
low: InfuraFeeDetail;
|
|
39
|
-
medium: InfuraFeeDetail;
|
|
40
|
-
high: InfuraFeeDetail;
|
|
41
|
-
networkCongestion: number;
|
|
42
|
-
estimatedBaseFee: string;
|
|
43
|
-
latestPriorityFeeRange: [string, string];
|
|
44
|
-
historicalPriorityFeeRange: [string, string];
|
|
45
|
-
historicalBaseFeeRange: [string, string];
|
|
46
|
-
priorityFeeTrend: 'down' | 'up';
|
|
47
|
-
baseFeeTrend: 'down' | 'up';
|
|
48
|
-
}
|
|
49
|
-
export {};
|
|
1
|
+
export * from './evm';
|
|
2
|
+
export * from './fee';
|
package/types/fee/index.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { OptimalSwapPath, OptimalSwapPathParams, SwapStepType } from '@subwallet/extension-base/types/swap';
|
|
2
|
+
import { OptimalYieldPath, OptimalYieldPathParams, YieldStepType } from '@subwallet/extension-base/types/yield';
|
|
3
|
+
export declare type OptimalProcessParams = OptimalYieldPathParams | OptimalSwapPathParams;
|
|
4
|
+
export declare type OptimalProcessResult = OptimalYieldPath | OptimalSwapPath;
|
|
5
|
+
export declare type BaseStepType = SwapStepType | YieldStepType;
|
|
6
|
+
export interface BaseStepDetail {
|
|
7
|
+
type: BaseStepType;
|
|
8
|
+
name: string;
|
|
9
|
+
metadata?: Record<string, unknown>;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|