@rango-dev/widget-embedded 0.61.0 → 0.61.1-next.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/CHANGELOG.md +18 -0
- package/dist/components/ConfirmWalletsModal/ConfirmWalletsModal.d.ts.map +1 -1
- package/dist/components/CustomDestination/CustomDestination.d.ts.map +1 -1
- package/dist/components/Quote/QuoteCostDetails.d.ts.map +1 -1
- package/dist/components/Slippage/Slippage.d.ts.map +1 -1
- package/dist/components/SwapDetails/SwapDetails.d.ts.map +1 -1
- package/dist/containers/Settings/Lists.d.ts.map +1 -1
- package/dist/containers/Wallets/useUpdates.d.ts.map +1 -1
- package/dist/hooks/useSwapInput.d.ts.map +1 -1
- package/dist/hooks/useSyncNotifications/useSyncNotifications.d.ts.map +1 -1
- package/dist/hooks/useWalletList.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +4 -4
- package/dist/pages/ConfirmSwapPage.d.ts.map +1 -1
- package/dist/pages/Home.d.ts.map +1 -1
- package/dist/pages/LiquiditySourcePage.d.ts.map +1 -1
- package/dist/pages/SelectBlockchainPage.d.ts.map +1 -1
- package/dist/pages/SelectSwapItemPage/SelectSwapItemPage.helpers.d.ts +1 -0
- package/dist/pages/SelectSwapItemPage/SelectSwapItemPage.helpers.d.ts.map +1 -1
- package/dist/pages/SelectSwapItemPage/SelectSwapItemsPage.d.ts.map +1 -1
- package/dist/pages/SwapDetailsPage.d.ts.map +1 -1
- package/dist/store/slices/wallets.d.ts +3 -1
- package/dist/store/slices/wallets.d.ts.map +1 -1
- package/dist/types/event.d.ts +160 -6
- package/dist/types/event.d.ts.map +1 -1
- package/dist/utils/eventPayloads.d.ts +8 -0
- package/dist/utils/eventPayloads.d.ts.map +1 -0
- package/dist/utils/events.d.ts +8 -5
- package/dist/utils/events.d.ts.map +1 -1
- package/dist/widget-embedded.build.json +1 -1
- package/package.json +2 -2
- package/src/components/ConfirmWalletsModal/ConfirmWalletsModal.tsx +43 -1
- package/src/components/CustomDestination/CustomDestination.tsx +18 -0
- package/src/components/Quote/QuoteCostDetails.tsx +8 -0
- package/src/components/Slippage/Slippage.tsx +21 -0
- package/src/components/SwapDetails/SwapDetails.tsx +6 -0
- package/src/containers/Settings/Lists.tsx +13 -1
- package/src/containers/Wallets/useUpdates.ts +3 -1
- package/src/hooks/useSwapInput.ts +37 -1
- package/src/hooks/useSyncNotifications/useSyncNotifications.ts +33 -1
- package/src/hooks/useWalletList.ts +36 -0
- package/src/pages/ConfirmSwapPage.tsx +12 -1
- package/src/pages/Home.tsx +39 -14
- package/src/pages/LiquiditySourcePage.tsx +28 -0
- package/src/pages/SelectBlockchainPage.tsx +10 -0
- package/src/pages/SelectSwapItemPage/SelectSwapItemPage.helpers.ts +9 -0
- package/src/pages/SelectSwapItemPage/SelectSwapItemsPage.tsx +30 -0
- package/src/pages/SwapDetailsPage.tsx +6 -0
- package/src/store/slices/wallets.ts +10 -4
- package/src/types/event.ts +189 -10
- package/src/utils/eventPayloads.ts +39 -0
- package/src/utils/events.ts +33 -10
package/package.json
CHANGED
|
@@ -19,6 +19,8 @@ import { WIDGET_UI_ID } from '../../constants';
|
|
|
19
19
|
import { getQuoteErrorMessage } from '../../constants/errors';
|
|
20
20
|
import { useAppStore } from '../../store/AppStore';
|
|
21
21
|
import { useQuoteStore } from '../../store/quote';
|
|
22
|
+
import { UiEventTypes } from '../../types';
|
|
23
|
+
import { emitUiEvent } from '../../utils/events';
|
|
22
24
|
import { getBlockchainShortNameFor } from '../../utils/meta';
|
|
23
25
|
import { isConfirmSwapDisabled } from '../../utils/swap';
|
|
24
26
|
import { getQuoteChains } from '../../utils/wallets';
|
|
@@ -211,6 +213,10 @@ export function ConfirmWalletsModal(props: PropTypes) {
|
|
|
211
213
|
const lastSelectedWallets = selectableWallets.filter(
|
|
212
214
|
(wallet) => wallet.selected
|
|
213
215
|
);
|
|
216
|
+
emitUiEvent({
|
|
217
|
+
type: UiEventTypes.SWAP_WALLETS_CONFIRMED,
|
|
218
|
+
payload: { routeId: selectedQuote?.requestId ?? '' },
|
|
219
|
+
});
|
|
214
220
|
setWalletsAsSelected(lastSelectedWallets);
|
|
215
221
|
selectQuoteWallets(lastSelectedWallets);
|
|
216
222
|
setQuoteWalletConfirmed(true);
|
|
@@ -301,6 +307,36 @@ export function ConfirmWalletsModal(props: PropTypes) {
|
|
|
301
307
|
}
|
|
302
308
|
}, [connectedWallets, nextSelectedWallets]);
|
|
303
309
|
|
|
310
|
+
const routeId = selectedQuote?.requestId ?? '';
|
|
311
|
+
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
if (!open) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const pendingChains = requiredChains.filter(
|
|
317
|
+
(chain) =>
|
|
318
|
+
!connectedWallets.some(
|
|
319
|
+
(connectedWallet) => connectedWallet.chain === chain
|
|
320
|
+
)
|
|
321
|
+
);
|
|
322
|
+
emitUiEvent({
|
|
323
|
+
type: UiEventTypes.SWAP_WALLETS_MODAL_SHOWN,
|
|
324
|
+
payload: {
|
|
325
|
+
chainsRequired: requiredChains.length,
|
|
326
|
+
chainsPending: pendingChains.length,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
}, [open]);
|
|
330
|
+
|
|
331
|
+
useEffect(() => {
|
|
332
|
+
if (isInsufficientBalanceModalOpen) {
|
|
333
|
+
emitUiEvent({
|
|
334
|
+
type: UiEventTypes.GAS_WARNING_SHOWN,
|
|
335
|
+
payload: { routeId },
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}, [isInsufficientBalanceModalOpen, routeId]);
|
|
339
|
+
|
|
304
340
|
const modalContainer = document.getElementById(
|
|
305
341
|
WIDGET_UI_ID.SWAP_BOX_ID
|
|
306
342
|
) as HTMLDivElement;
|
|
@@ -383,7 +419,13 @@ export function ConfirmWalletsModal(props: PropTypes) {
|
|
|
383
419
|
size="large"
|
|
384
420
|
type="primary"
|
|
385
421
|
fullWidth
|
|
386
|
-
onClick={
|
|
422
|
+
onClick={() => {
|
|
423
|
+
emitUiEvent({
|
|
424
|
+
type: UiEventTypes.GAS_WARNING_BYPASSED,
|
|
425
|
+
payload: { routeId },
|
|
426
|
+
});
|
|
427
|
+
onConfirmBalance();
|
|
428
|
+
}}>
|
|
387
429
|
{i18n.t('Proceed anyway')}
|
|
388
430
|
</Button>
|
|
389
431
|
</MessageBox>
|
|
@@ -16,6 +16,8 @@ import React, { useEffect, useRef } from 'react';
|
|
|
16
16
|
|
|
17
17
|
import { useAppStore } from '../../store/AppStore';
|
|
18
18
|
import { useQuoteStore } from '../../store/quote';
|
|
19
|
+
import { UiEventTypes } from '../../types';
|
|
20
|
+
import { emitUiEvent } from '../../utils/events';
|
|
19
21
|
import {
|
|
20
22
|
getBlockchainDisplayNameFor,
|
|
21
23
|
isValidTokenAddress,
|
|
@@ -94,6 +96,22 @@ export function CustomDestination(props: PropTypes) {
|
|
|
94
96
|
}
|
|
95
97
|
}, [configDestination]);
|
|
96
98
|
|
|
99
|
+
const lastReportedDestination = useRef<string | null>(null);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (!customDestination) {
|
|
102
|
+
lastReportedDestination.current = null;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const isValid = open && isValidTokenAddress(blockchain, customDestination);
|
|
106
|
+
if (isValid && lastReportedDestination.current !== customDestination) {
|
|
107
|
+
lastReportedDestination.current = customDestination;
|
|
108
|
+
emitUiEvent({
|
|
109
|
+
type: UiEventTypes.DESTINATION_ADDRESS_SET,
|
|
110
|
+
payload: { destinationChain: blockchain.name },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}, [customDestination, open, blockchain]);
|
|
114
|
+
|
|
97
115
|
return (
|
|
98
116
|
<Container>
|
|
99
117
|
<CustomCollapsible
|
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
USD_VALUE_MAX_DECIMALS,
|
|
22
22
|
USD_VALUE_MIN_DECIMALS,
|
|
23
23
|
} from '../../constants/routing';
|
|
24
|
+
import { UiEventTypes } from '../../types';
|
|
24
25
|
import { getContainer, getExpanded } from '../../utils/common';
|
|
26
|
+
import { emitUiEvent } from '../../utils/events';
|
|
25
27
|
import { numberToString } from '../../utils/numbers';
|
|
26
28
|
import { getFeesGroup, getTotalFeesInUsd, getUsdFee } from '../../utils/swap';
|
|
27
29
|
import { WatermarkedModal } from '../common/WatermarkedModal';
|
|
@@ -84,6 +86,12 @@ export function QuoteCostDetails(props: QuoteCostDetailsProps) {
|
|
|
84
86
|
showModalFee
|
|
85
87
|
? (e) => {
|
|
86
88
|
e.stopPropagation();
|
|
89
|
+
if (!open && quote) {
|
|
90
|
+
emitUiEvent({
|
|
91
|
+
type: UiEventTypes.ROUTE_FEE_VIEWED,
|
|
92
|
+
payload: { routeId: quote.requestId },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
87
95
|
setOpen(!open);
|
|
88
96
|
}
|
|
89
97
|
: undefined
|
|
@@ -12,7 +12,9 @@ import React from 'react';
|
|
|
12
12
|
|
|
13
13
|
import { MAX_SLIPPAGE, SLIPPAGES } from '../../constants/swapSettings';
|
|
14
14
|
import { useAppStore } from '../../store/AppStore';
|
|
15
|
+
import { UiEventTypes } from '../../types';
|
|
15
16
|
import { getContainer } from '../../utils/common';
|
|
17
|
+
import { emitUiEvent } from '../../utils/events';
|
|
16
18
|
import { getSlippageValidation } from '../../utils/settings';
|
|
17
19
|
import { isValidCurrencyFormat } from '../../utils/validation';
|
|
18
20
|
|
|
@@ -31,6 +33,23 @@ export function Slippage() {
|
|
|
31
33
|
const slippageValidation =
|
|
32
34
|
customSlippage !== null ? getSlippageValidation(customSlippage) : null;
|
|
33
35
|
|
|
36
|
+
/** Effective slippage before a change, used as the previous value in events. */
|
|
37
|
+
const effectiveSlippage = customSlippage ?? slippage;
|
|
38
|
+
|
|
39
|
+
const emitSlippageChanged = (previousValue: number, newValue: number) => {
|
|
40
|
+
if (previousValue === newValue) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
emitUiEvent({
|
|
44
|
+
type: UiEventTypes.SETTINGS_CHANGED,
|
|
45
|
+
payload: {
|
|
46
|
+
setting: 'slippage',
|
|
47
|
+
previousValue,
|
|
48
|
+
newValue,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
34
53
|
const onSlippageValueChange = (
|
|
35
54
|
event: React.ChangeEvent<HTMLInputElement>
|
|
36
55
|
) => {
|
|
@@ -43,10 +62,12 @@ export function Slippage() {
|
|
|
43
62
|
if (parsedValue > MAX_SLIPPAGE) {
|
|
44
63
|
slippage = MAX_SLIPPAGE;
|
|
45
64
|
}
|
|
65
|
+
emitSlippageChanged(effectiveSlippage, slippage);
|
|
46
66
|
setCustomSlippage(slippage);
|
|
47
67
|
};
|
|
48
68
|
|
|
49
69
|
const onClickSlippageChip = (slippageItem: number) => {
|
|
70
|
+
emitSlippageChanged(effectiveSlippage, slippageItem);
|
|
50
71
|
if (customSlippage !== null) {
|
|
51
72
|
setCustomSlippage(null);
|
|
52
73
|
}
|
|
@@ -34,7 +34,9 @@ import {
|
|
|
34
34
|
import { useAppStore } from '../../store/AppStore';
|
|
35
35
|
import { useNotificationStore } from '../../store/notification';
|
|
36
36
|
import { useQuoteStore } from '../../store/quote';
|
|
37
|
+
import { UiEventTypes } from '../../types';
|
|
37
38
|
import { getContainer } from '../../utils/common';
|
|
39
|
+
import { emitUiEvent } from '../../utils/events';
|
|
38
40
|
import {
|
|
39
41
|
numberToString,
|
|
40
42
|
roundedSecondsToString,
|
|
@@ -404,6 +406,10 @@ export function SwapDetails(props: SwapDetailsProps) {
|
|
|
404
406
|
type="primary"
|
|
405
407
|
size="large"
|
|
406
408
|
onClick={() => {
|
|
409
|
+
emitUiEvent({
|
|
410
|
+
type: UiEventTypes.SWAP_RETRIED,
|
|
411
|
+
payload: { routeId: swap.requestId },
|
|
412
|
+
});
|
|
407
413
|
const swapInput = createRetryQuote(swap, blockchains, findToken);
|
|
408
414
|
retry(swapInput);
|
|
409
415
|
setTimeout(() => {
|
|
@@ -32,7 +32,9 @@ import { navigationRoutes } from '../../constants/navigationRoutes';
|
|
|
32
32
|
import { ThemeModeEnum } from '../../constants/settings';
|
|
33
33
|
import { useLanguage } from '../../hooks/useLanguage';
|
|
34
34
|
import { useAppStore } from '../../store/AppStore';
|
|
35
|
+
import { UiEventTypes } from '../../types';
|
|
35
36
|
import { getContainer } from '../../utils/common';
|
|
37
|
+
import { emitUiEvent } from '../../utils/events';
|
|
36
38
|
import { getUniqueSwappersGroups, isFeatureHidden } from '../../utils/settings';
|
|
37
39
|
|
|
38
40
|
const ThemeSection = styled('div', {
|
|
@@ -257,7 +259,17 @@ export function SettingsLists() {
|
|
|
257
259
|
),
|
|
258
260
|
start: <InfinityIcon color="gray" size={16} />,
|
|
259
261
|
end: <Switch checked={infiniteApprove} />,
|
|
260
|
-
onClick:
|
|
262
|
+
onClick: () => {
|
|
263
|
+
emitUiEvent({
|
|
264
|
+
type: UiEventTypes.SETTINGS_CHANGED,
|
|
265
|
+
payload: {
|
|
266
|
+
setting: 'infiniteApproval',
|
|
267
|
+
previousValue: infiniteApprove,
|
|
268
|
+
newValue: !infiniteApprove,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
toggleInfiniteApprove();
|
|
272
|
+
},
|
|
261
273
|
};
|
|
262
274
|
|
|
263
275
|
const themeItem = {
|
|
@@ -77,7 +77,9 @@ export function useUpdates(params: UseUpdatesParams): UseUpdates {
|
|
|
77
77
|
);
|
|
78
78
|
|
|
79
79
|
if (data.length) {
|
|
80
|
-
void newWalletConnected(data, info.namespace, state.derivationPath
|
|
80
|
+
void newWalletConnected(data, info.namespace, state.derivationPath, {
|
|
81
|
+
walletName: type,
|
|
82
|
+
});
|
|
81
83
|
}
|
|
82
84
|
};
|
|
83
85
|
|
|
@@ -5,8 +5,10 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
5
5
|
|
|
6
6
|
import { useAppStore } from '../store/AppStore';
|
|
7
7
|
import { useQuoteStore } from '../store/quote';
|
|
8
|
-
import { QuoteErrorType } from '../types';
|
|
8
|
+
import { QuoteErrorType, QuoteEventTypes } from '../types';
|
|
9
9
|
import { debounce } from '../utils/common';
|
|
10
|
+
import { buildSwapEstimatePayload } from '../utils/eventPayloads';
|
|
11
|
+
import { emitQuoteEvent } from '../utils/events';
|
|
10
12
|
import { isPositiveNumber } from '../utils/numbers';
|
|
11
13
|
import {
|
|
12
14
|
generateQuoteWarnings,
|
|
@@ -160,6 +162,16 @@ export function useSwapInput({
|
|
|
160
162
|
updateQuotePartialState('quotes', res);
|
|
161
163
|
setSelectedQuote(quote);
|
|
162
164
|
|
|
165
|
+
if (res.results?.length && quote) {
|
|
166
|
+
emitQuoteEvent({
|
|
167
|
+
type: QuoteEventTypes.ROUTE_REQUESTED,
|
|
168
|
+
payload: {
|
|
169
|
+
...buildSwapEstimatePayload(quote),
|
|
170
|
+
routeCount: res.results.length,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
163
175
|
throwErrorIfResponseIsNotValid({
|
|
164
176
|
diagnosisMessages: res.diagnosisMessages,
|
|
165
177
|
requestId: quote?.requestId || '',
|
|
@@ -176,6 +188,30 @@ export function useSwapInput({
|
|
|
176
188
|
})
|
|
177
189
|
.catch((error) => {
|
|
178
190
|
const quoteError = handleQuoteErrors(error);
|
|
191
|
+
if (quoteError.type === QuoteErrorType.NO_RESULT) {
|
|
192
|
+
emitQuoteEvent({
|
|
193
|
+
type: QuoteEventTypes.ROUTE_NOT_FOUND,
|
|
194
|
+
payload: {
|
|
195
|
+
sourceChain: fromToken.blockchain,
|
|
196
|
+
destinationChain: toToken.blockchain,
|
|
197
|
+
sourceTokenSymbol: fromToken.symbol,
|
|
198
|
+
sourceTokenAddress: fromToken.address,
|
|
199
|
+
destinationTokenSymbol: toToken.symbol,
|
|
200
|
+
destinationTokenAddress: toToken.address,
|
|
201
|
+
inputAmountUsd: inputUsdValue ? inputUsdValue.toNumber() : null,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
} else if (quoteError.type === QuoteErrorType.REQUEST_FAILED) {
|
|
205
|
+
emitQuoteEvent({
|
|
206
|
+
type: QuoteEventTypes.ROUTE_FETCH_FAILED,
|
|
207
|
+
payload: {
|
|
208
|
+
sourceChain: fromToken.blockchain,
|
|
209
|
+
destinationChain: toToken.blockchain,
|
|
210
|
+
|
|
211
|
+
errorCode: String(error?.response?.status ?? ''),
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
179
215
|
if (
|
|
180
216
|
quoteError.type === QuoteErrorType.NO_RESULT ||
|
|
181
217
|
quoteError.type === QuoteErrorType.REQUEST_FAILED
|
|
@@ -2,6 +2,8 @@ import { useManager } from '@rango-dev/queue-manager-react';
|
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
3
|
|
|
4
4
|
import { useNotificationStore } from '../../store/notification';
|
|
5
|
+
import { UiEventTypes } from '../../types';
|
|
6
|
+
import { emitUiEvent } from '../../utils/events';
|
|
5
7
|
import { getPendingSwaps } from '../../utils/queue';
|
|
6
8
|
|
|
7
9
|
export function useSyncNotifications() {
|
|
@@ -15,7 +17,37 @@ export function useSyncNotifications() {
|
|
|
15
17
|
!isSynced;
|
|
16
18
|
|
|
17
19
|
if (shouldSyncNotifications) {
|
|
18
|
-
|
|
20
|
+
const pendingSwaps = getPendingSwaps(manager);
|
|
21
|
+
syncNotifications(pendingSwaps);
|
|
22
|
+
|
|
23
|
+
pendingSwaps.forEach(({ swap }) => {
|
|
24
|
+
const isInProgressMultiStep =
|
|
25
|
+
swap.status === 'running' && swap.steps.length > 1;
|
|
26
|
+
if (!isInProgressMultiStep) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
/*
|
|
30
|
+
* The current step is the first one that hasn't succeeded yet. A
|
|
31
|
+
* running swap should always have one; if not (all steps already
|
|
32
|
+
* succeeded), the state is inconsistent/transient, so skip it rather
|
|
33
|
+
* than report a misleading step.
|
|
34
|
+
*/
|
|
35
|
+
const currentStepIndex = swap.steps.findIndex(
|
|
36
|
+
(step) => step.status !== 'success'
|
|
37
|
+
);
|
|
38
|
+
if (currentStepIndex === -1) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
emitUiEvent({
|
|
43
|
+
type: UiEventTypes.SWAP_RESUMED,
|
|
44
|
+
payload: {
|
|
45
|
+
routeId: swap.requestId,
|
|
46
|
+
stepCount: swap.steps.length,
|
|
47
|
+
stepNumber: currentStepIndex + 1,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
});
|
|
19
51
|
}
|
|
20
52
|
}, [
|
|
21
53
|
useNotificationStore.persist.hasHydrated(),
|
|
@@ -7,6 +7,7 @@ import { detectMobileScreens, WalletTypes } from '@rango-dev/wallets-shared';
|
|
|
7
7
|
import { useCallback, useEffect } from 'react';
|
|
8
8
|
|
|
9
9
|
import { useAppStore } from '../store/AppStore';
|
|
10
|
+
import { emitWalletDetected } from '../utils/events';
|
|
10
11
|
import { configWalletsToWalletName } from '../utils/providers';
|
|
11
12
|
import {
|
|
12
13
|
hashWalletsState,
|
|
@@ -16,6 +17,26 @@ import {
|
|
|
16
17
|
|
|
17
18
|
import { useStatefulConnect } from './useStatefulConnect/useStatefulConnect';
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Wallet types we've already reported as detected this session. Module-scoped so
|
|
22
|
+
* the `walletDetected` event fires at most once per provider regardless of how
|
|
23
|
+
* often the wallet list re-renders or remounts.
|
|
24
|
+
*/
|
|
25
|
+
const detectedWalletsReported = new Set<string>();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Wallet types that aren't browser-injected extensions (hardware wallets,
|
|
29
|
+
* WalletConnect, TON Connect). Their presence doesn't indicate an installed
|
|
30
|
+
* wallet, so they're excluded from the `walletDetected` signal.
|
|
31
|
+
*/
|
|
32
|
+
const NON_INJECTED_WALLET_TYPES = new Set<string>([
|
|
33
|
+
WalletTypes.LEDGER,
|
|
34
|
+
WalletTypes.TREZOR,
|
|
35
|
+
WalletTypes.WALLET_CONNECT_2,
|
|
36
|
+
WalletTypes.TON_CONNECT,
|
|
37
|
+
WalletTypes.DEFAULT,
|
|
38
|
+
]);
|
|
39
|
+
|
|
19
40
|
interface Params {
|
|
20
41
|
chain?: string;
|
|
21
42
|
}
|
|
@@ -59,6 +80,21 @@ export function useWalletList(params?: Params): API {
|
|
|
59
80
|
|
|
60
81
|
const sortedWallets = sortWalletsBasedOnConnectionState(wallets);
|
|
61
82
|
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
wallets.forEach((wallet) => {
|
|
85
|
+
if (NON_INJECTED_WALLET_TYPES.has(wallet.type)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const isDetected = wallet.state !== WalletState.NOT_INSTALLED;
|
|
89
|
+
if (isDetected && !detectedWalletsReported.has(wallet.type)) {
|
|
90
|
+
detectedWalletsReported.add(wallet.type);
|
|
91
|
+
emitWalletDetected({
|
|
92
|
+
walletName: wallet.title,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}, [hashWalletsState(wallets)]);
|
|
97
|
+
|
|
62
98
|
const terminateConnectingWallets = useCallback(() => {
|
|
63
99
|
const connectingWallets =
|
|
64
100
|
wallets?.filter((wallet) => wallet.state === WalletState.CONNECTING) ||
|
|
@@ -28,7 +28,9 @@ import { useConfirmSwap } from '../hooks/useConfirmSwap';
|
|
|
28
28
|
import { useAppStore } from '../store/AppStore';
|
|
29
29
|
import { useQuoteStore } from '../store/quote';
|
|
30
30
|
import { useUiStore } from '../store/ui';
|
|
31
|
-
import { QuoteErrorType, QuoteWarningType } from '../types';
|
|
31
|
+
import { QuoteErrorType, QuoteWarningType, UiEventTypes } from '../types';
|
|
32
|
+
import { buildSwapEstimatePayload } from '../utils/eventPayloads';
|
|
33
|
+
import { emitUiEvent } from '../utils/events';
|
|
32
34
|
import { isQuoteWarningConfirmationRequired } from '../utils/quote';
|
|
33
35
|
import { joinList } from '../utils/ui';
|
|
34
36
|
|
|
@@ -142,6 +144,15 @@ export function ConfirmSwapPage() {
|
|
|
142
144
|
if (shouldShowWarningModal) {
|
|
143
145
|
setShowQuoteWarningModal(true);
|
|
144
146
|
} else {
|
|
147
|
+
if (selectedQuote) {
|
|
148
|
+
emitUiEvent({
|
|
149
|
+
type: UiEventTypes.SWAP_STARTED,
|
|
150
|
+
payload: {
|
|
151
|
+
...buildSwapEstimatePayload(selectedQuote),
|
|
152
|
+
stepCount: selectedQuote.swaps.length,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
145
156
|
await onConfirm();
|
|
146
157
|
}
|
|
147
158
|
};
|
package/src/pages/Home.tsx
CHANGED
|
@@ -23,9 +23,14 @@ import { useSwapMode } from '../hooks/useSwapMode';
|
|
|
23
23
|
import { useAppStore } from '../store/AppStore';
|
|
24
24
|
import { useQuoteStore } from '../store/quote';
|
|
25
25
|
import { useUiStore } from '../store/ui';
|
|
26
|
-
import { UiEventTypes } from '../types';
|
|
26
|
+
import { QuoteEventTypes, UiEventTypes } from '../types';
|
|
27
27
|
import { isVariantExpandable } from '../utils/configs';
|
|
28
|
-
import {
|
|
28
|
+
import { buildSwapEstimatePayload } from '../utils/eventPayloads';
|
|
29
|
+
import {
|
|
30
|
+
emitPreventableEvent,
|
|
31
|
+
emitQuoteEvent,
|
|
32
|
+
emitUiEvent,
|
|
33
|
+
} from '../utils/events';
|
|
29
34
|
import { getSlippageValidation } from '../utils/settings';
|
|
30
35
|
import { getSwapButtonState, isTokensIdentical } from '../utils/swap';
|
|
31
36
|
|
|
@@ -133,6 +138,18 @@ export function Home() {
|
|
|
133
138
|
};
|
|
134
139
|
const onClickOnQuote = (quote: SelectedQuote) => {
|
|
135
140
|
if (selectedQuote?.requestId !== quote.requestId) {
|
|
141
|
+
if (selectedQuote) {
|
|
142
|
+
emitQuoteEvent({
|
|
143
|
+
type: QuoteEventTypes.ROUTE_CHANGED,
|
|
144
|
+
payload: {
|
|
145
|
+
sourceChain: quote.swaps[0]?.from.blockchain ?? '',
|
|
146
|
+
destinationChain:
|
|
147
|
+
quote.swaps[quote.swaps.length - 1]?.to.blockchain ?? '',
|
|
148
|
+
fromRouteId: selectedQuote.requestId,
|
|
149
|
+
toRouteId: quote.requestId,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
136
153
|
setShowQuoteWarningModal(false);
|
|
137
154
|
setSelectedQuote(quote);
|
|
138
155
|
}
|
|
@@ -171,6 +188,25 @@ export function Home() {
|
|
|
171
188
|
setIsVisibleExpanded(hasInputs);
|
|
172
189
|
}, [hasInputs]);
|
|
173
190
|
|
|
191
|
+
const handleSwapButtonClick = () => {
|
|
192
|
+
if (swapButtonState.action === 'connect-wallet') {
|
|
193
|
+
emitPreventableEvent({ type: UiEventTypes.CLICK_CONNECT_WALLET }, () => {
|
|
194
|
+
onHandleNavigation(navigationRoutes.wallets);
|
|
195
|
+
});
|
|
196
|
+
} else if (swapButtonState.action === 'confirm-warning') {
|
|
197
|
+
setShowQuoteWarningModal(true);
|
|
198
|
+
} else if (selectedQuote) {
|
|
199
|
+
emitUiEvent({
|
|
200
|
+
type: UiEventTypes.SWAP_INITIATED,
|
|
201
|
+
payload: {
|
|
202
|
+
...buildSwapEstimatePayload(selectedQuote),
|
|
203
|
+
walletConnected: connectedWallets.length > 0,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
onHandleNavigation(navigationRoutes.confirmSwap);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
174
210
|
return (
|
|
175
211
|
<MainContainer>
|
|
176
212
|
<Layout
|
|
@@ -186,18 +222,7 @@ export function Home() {
|
|
|
186
222
|
swapButtonState.action === 'confirm-warning' && <WarningIcon />
|
|
187
223
|
}
|
|
188
224
|
fullWidth
|
|
189
|
-
onClick={
|
|
190
|
-
if (swapButtonState.action === 'connect-wallet') {
|
|
191
|
-
emitPreventableEvent(
|
|
192
|
-
{ type: UiEventTypes.CLICK_CONNECT_WALLET },
|
|
193
|
-
() => onHandleNavigation(navigationRoutes.wallets)
|
|
194
|
-
);
|
|
195
|
-
} else if (swapButtonState.action === 'confirm-warning') {
|
|
196
|
-
setShowQuoteWarningModal(true);
|
|
197
|
-
} else {
|
|
198
|
-
onHandleNavigation(navigationRoutes.confirmSwap);
|
|
199
|
-
}
|
|
200
|
-
}}>
|
|
225
|
+
onClick={handleSwapButtonClick}>
|
|
201
226
|
{swapButtonState.title}
|
|
202
227
|
</Button>
|
|
203
228
|
}
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
NotFoundContainer,
|
|
22
22
|
} from '../components/SettingsContainer';
|
|
23
23
|
import { useAppStore } from '../store/AppStore';
|
|
24
|
+
import { type LiquiditySourceType, UiEventTypes } from '../types';
|
|
25
|
+
import { emitUiEvent } from '../utils/events';
|
|
24
26
|
import { containsText } from '../utils/numbers';
|
|
25
27
|
import { replaceSpacesWithDash } from '../utils/sanitizers';
|
|
26
28
|
import { getUniqueSwappersGroups } from '../utils/settings';
|
|
@@ -56,7 +58,24 @@ export function LiquiditySourcePage({ sourceType }: PropTypes) {
|
|
|
56
58
|
liquiditySources.length ===
|
|
57
59
|
liquiditySources.filter((sourceItem) => sourceItem.selected).length;
|
|
58
60
|
|
|
61
|
+
const liquiditySourceType: LiquiditySourceType =
|
|
62
|
+
sourceType === 'Exchanges' ? 'exchanges' : 'bridges';
|
|
63
|
+
|
|
59
64
|
const toggleAllSources = () => {
|
|
65
|
+
// Selecting all enables every source; deselecting all disables them.
|
|
66
|
+
const enabled = !hasSelectAll;
|
|
67
|
+
emitUiEvent({
|
|
68
|
+
type: UiEventTypes.SETTINGS_CHANGED,
|
|
69
|
+
payload: {
|
|
70
|
+
setting: 'liquiditySource',
|
|
71
|
+
sourceType: liquiditySourceType,
|
|
72
|
+
changeType: enabled ? 'selectAll' : 'deselectAll',
|
|
73
|
+
previousSelectedCount: liquiditySources.filter(
|
|
74
|
+
(sourceItem) => sourceItem.selected
|
|
75
|
+
).length,
|
|
76
|
+
totalCount: liquiditySources.length,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
60
79
|
liquiditySources.forEach((sourceItem) => {
|
|
61
80
|
if (hasSelectAll) {
|
|
62
81
|
toggleLiquiditySource(sourceItem.groupTitle);
|
|
@@ -79,6 +98,15 @@ export function LiquiditySourcePage({ sourceType }: PropTypes) {
|
|
|
79
98
|
start: <Image src={logo} size={22} type="circular" />,
|
|
80
99
|
onClick: () => {
|
|
81
100
|
if (!campaignMode) {
|
|
101
|
+
emitUiEvent({
|
|
102
|
+
type: UiEventTypes.SETTINGS_CHANGED,
|
|
103
|
+
payload: {
|
|
104
|
+
setting: 'liquiditySource',
|
|
105
|
+
sourceType: liquiditySourceType,
|
|
106
|
+
changeType: 'individual',
|
|
107
|
+
sources: [{ id: groupTitle, enabled: !selected }],
|
|
108
|
+
},
|
|
109
|
+
});
|
|
82
110
|
toggleLiquiditySource(groupTitle);
|
|
83
111
|
}
|
|
84
112
|
},
|
|
@@ -16,7 +16,9 @@ import { useNavigateBack } from '../hooks/useNavigateBack';
|
|
|
16
16
|
import { useSwapMode } from '../hooks/useSwapMode';
|
|
17
17
|
import { useAppStore } from '../store/AppStore';
|
|
18
18
|
import { useQuoteStore } from '../store/quote';
|
|
19
|
+
import { UiEventTypes } from '../types';
|
|
19
20
|
import { filterBlockchainsWithAtLeastOneToken } from '../utils/common';
|
|
21
|
+
import { emitUiEvent } from '../utils/events';
|
|
20
22
|
|
|
21
23
|
interface PropTypes {
|
|
22
24
|
type: 'source' | 'destination' | 'custom-token';
|
|
@@ -51,6 +53,14 @@ export function SelectBlockchainPage(props: PropTypes) {
|
|
|
51
53
|
if (type === 'custom-token') {
|
|
52
54
|
navigate(`..?blockchain=${blockchain.name}`, { replace: true });
|
|
53
55
|
} else {
|
|
56
|
+
emitUiEvent({
|
|
57
|
+
type: UiEventTypes.CHAIN_FILTER_APPLIED,
|
|
58
|
+
payload: {
|
|
59
|
+
side: type === 'source' ? 'from' : 'to',
|
|
60
|
+
chain: blockchain.name,
|
|
61
|
+
filterSource: 'expanded',
|
|
62
|
+
},
|
|
63
|
+
});
|
|
54
64
|
if (type === 'source') {
|
|
55
65
|
setFromBlockchain(blockchain);
|
|
56
66
|
} else {
|
|
@@ -7,6 +7,15 @@ import {
|
|
|
7
7
|
MIN_SEARCH_LENGTH,
|
|
8
8
|
} from './SelectSwapItemPage.constants';
|
|
9
9
|
|
|
10
|
+
export function getTokenSelectionMethod(
|
|
11
|
+
searchedFor: string
|
|
12
|
+
): 'search' | 'default ' {
|
|
13
|
+
if (searchedFor.trim()) {
|
|
14
|
+
return 'search';
|
|
15
|
+
}
|
|
16
|
+
return 'default ';
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
export function shouldSearchForCustomTokens(
|
|
11
20
|
metaSearchResultTokens: Token[],
|
|
12
21
|
query: string,
|
|
@@ -14,8 +14,11 @@ import { useNavigateBack } from '../../hooks/useNavigateBack';
|
|
|
14
14
|
import { useSearchCustomTokens } from '../../hooks/useSearchCustomTokens';
|
|
15
15
|
import { useAppStore } from '../../store/AppStore';
|
|
16
16
|
import { useQuoteStore } from '../../store/quote';
|
|
17
|
+
import { UiEventTypes } from '../../types';
|
|
18
|
+
import { emitUiEvent } from '../../utils/events';
|
|
17
19
|
|
|
18
20
|
import {
|
|
21
|
+
getTokenSelectionMethod,
|
|
19
22
|
prepareTokensList,
|
|
20
23
|
shouldSearchForCustomTokens,
|
|
21
24
|
} from './SelectSwapItemPage.helpers';
|
|
@@ -82,6 +85,31 @@ export function SelectSwapItemsPage(props: PropTypes) {
|
|
|
82
85
|
setToToken({ token, meta: { blockchains } });
|
|
83
86
|
}
|
|
84
87
|
};
|
|
88
|
+
|
|
89
|
+
const side = type === 'source' ? 'from' : 'to';
|
|
90
|
+
|
|
91
|
+
const emitChainFilterApplied = (blockchain: BlockchainMeta) => {
|
|
92
|
+
emitUiEvent({
|
|
93
|
+
type: UiEventTypes.CHAIN_FILTER_APPLIED,
|
|
94
|
+
payload: { side, chain: blockchain.name, filterSource: 'featured' },
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const emitTokenSelected = (token: Token) => {
|
|
99
|
+
emitUiEvent({
|
|
100
|
+
type: UiEventTypes.TOKEN_SELECTED,
|
|
101
|
+
payload: {
|
|
102
|
+
side,
|
|
103
|
+
tokenName: token.name,
|
|
104
|
+
tokenSymbol: token.symbol,
|
|
105
|
+
tokenAddress: token.address,
|
|
106
|
+
chain: token.blockchain,
|
|
107
|
+
selectionMethod: getTokenSelectionMethod(searchedFor),
|
|
108
|
+
activeChainFilter: selectedBlockchainName || 'all',
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
85
113
|
const types = {
|
|
86
114
|
source: i18n.t('Source'),
|
|
87
115
|
destination: i18n.t('Destination'),
|
|
@@ -111,6 +139,7 @@ export function SelectSwapItemsPage(props: PropTypes) {
|
|
|
111
139
|
blockchain={type === 'source' ? fromBlockchain : toBlockchain}
|
|
112
140
|
onMoreClick={() => navigate(navigationRoutes.blockchains)}
|
|
113
141
|
onChange={(blockchain) => {
|
|
142
|
+
emitChainFilterApplied(blockchain);
|
|
114
143
|
updateBlockchain(blockchain);
|
|
115
144
|
}}
|
|
116
145
|
/>
|
|
@@ -142,6 +171,7 @@ export function SelectSwapItemsPage(props: PropTypes) {
|
|
|
142
171
|
searchedFor={searchedFor}
|
|
143
172
|
type={type}
|
|
144
173
|
onChange={(token) => {
|
|
174
|
+
emitTokenSelected(token);
|
|
145
175
|
updateToken(token);
|
|
146
176
|
|
|
147
177
|
const tokenBlockchain = blockchains.find(
|