@lifi/widget 4.1.0 → 4.3.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/dist/esm/AppDefault.js +14 -0
- package/dist/esm/AppDefault.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInput.js +2 -1
- package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +4 -2
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js.map +1 -1
- package/dist/esm/components/AmountInput/PriceFormHelperText.js +4 -2
- package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
- package/dist/esm/components/AmountInputCard/AmountInputCard.style.js +98 -0
- package/dist/esm/components/AmountInputCard/AmountInputCard.style.js.map +1 -0
- package/dist/esm/components/AmountInputCard/AmountInputCardPair.js +25 -0
- package/dist/esm/components/AmountInputCard/AmountInputCardPair.js.map +1 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.js +35 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.js.map +1 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js +11 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js.map +1 -0
- package/dist/esm/components/AmountInputCard/FiatValueToggle.js +41 -0
- package/dist/esm/components/AmountInputCard/FiatValueToggle.js.map +1 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.js +80 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.js.map +1 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.style.js +43 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.style.js.map +1 -0
- package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js +137 -0
- package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js.map +1 -0
- package/dist/esm/components/AmountInputCard/SendAmountCard.js +102 -0
- package/dist/esm/components/AmountInputCard/SendAmountCard.js.map +1 -0
- package/dist/esm/components/Avatar/Avatar.js +12 -3
- package/dist/esm/components/Avatar/Avatar.js.map +1 -1
- package/dist/esm/components/Chains/VirtualizedChainList.js +2 -1
- package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
- package/dist/esm/components/Header/HeaderNavigationTabs.js +14 -1
- package/dist/esm/components/Header/HeaderNavigationTabs.js.map +1 -1
- package/dist/esm/components/IconCircle/IconCircle.style.js +1 -1
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js +99 -0
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js.map +1 -0
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js +52 -0
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js.map +1 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.js +115 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.js.map +1 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js +89 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js.map +1 -0
- package/dist/esm/components/LimitPriceCard/LimitPricePresets.js +91 -0
- package/dist/esm/components/LimitPriceCard/LimitPricePresets.js.map +1 -0
- package/dist/esm/components/RouteCard/RouteProviderCard.js +118 -0
- package/dist/esm/components/RouteCard/RouteProviderCard.js.map +1 -0
- package/dist/esm/components/Routes/LimitOrderRoutes.js +34 -0
- package/dist/esm/components/Routes/LimitOrderRoutes.js.map +1 -0
- package/dist/esm/components/Routes/Routes.js +2 -2
- package/dist/esm/components/Routes/Routes.js.map +1 -1
- package/dist/esm/components/SettingsPanel/SettingsPanel.js +51 -0
- package/dist/esm/components/SettingsPanel/SettingsPanel.js.map +1 -0
- package/dist/esm/components/SettingsPanel/SettingsPanel.style.js +33 -0
- package/dist/esm/components/SettingsPanel/SettingsPanel.style.js.map +1 -0
- package/dist/esm/components/SettingsPanel/utils.js +14 -0
- package/dist/esm/components/SettingsPanel/utils.js.map +1 -0
- package/dist/esm/components/SwapButton/SwapButton.js +50 -0
- package/dist/esm/components/SwapButton/SwapButton.js.map +1 -0
- package/dist/esm/components/SwapButton/SwapButton.style.js +24 -0
- package/dist/esm/components/SwapButton/SwapButton.style.js.map +1 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.js +52 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.js.map +1 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.style.js +39 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.style.js.map +1 -0
- package/dist/esm/config/version.d.ts +1 -1
- package/dist/esm/config/version.js +1 -1
- package/dist/esm/config/version.js.map +1 -1
- package/dist/esm/hooks/useChainSearch.js +2 -1
- package/dist/esm/hooks/useChainSearch.js.map +1 -1
- package/dist/esm/hooks/useFilteredByTokenBalances.js +4 -1
- package/dist/esm/hooks/useFilteredByTokenBalances.js.map +1 -1
- package/dist/esm/hooks/useLimitMarketRate.js +24 -0
- package/dist/esm/hooks/useLimitMarketRate.js.map +1 -0
- package/dist/esm/hooks/useLinkedLimitFields.js +49 -0
- package/dist/esm/hooks/useLinkedLimitFields.js.map +1 -0
- package/dist/esm/hooks/useRoutes.js +23 -6
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useTokens.js +16 -14
- package/dist/esm/hooks/useTokens.js.map +1 -1
- package/dist/esm/hooks/useWideVariant.js +5 -2
- package/dist/esm/hooks/useWideVariant.js.map +1 -1
- package/dist/esm/i18n/en.json +22 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/pages/MainPage/LimitPageContent.js +23 -0
- package/dist/esm/pages/MainPage/LimitPageContent.js.map +1 -0
- package/dist/esm/pages/MainPage/MainPage.js +19 -22
- package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
- package/dist/esm/pages/MainPage/MainPageActions.js +28 -0
- package/dist/esm/pages/MainPage/MainPageActions.js.map +1 -0
- package/dist/esm/pages/MainPage/ReviewButton.js +9 -2
- package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
- package/dist/esm/pages/RoutePriorityPage.js +48 -0
- package/dist/esm/pages/RoutePriorityPage.js.map +1 -0
- package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js +2 -2
- package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js.map +1 -1
- package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js +12 -27
- package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js.map +1 -1
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js +133 -0
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js.map +1 -0
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js +14 -72
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js.map +1 -1
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js +26 -14
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js.map +1 -1
- package/dist/esm/pages/SettingsPage/ThemeSettings.js +4 -0
- package/dist/esm/pages/SettingsPage/ThemeSettings.js.map +1 -1
- package/dist/esm/pages/TransactionPage/TransactionReview.js +4 -1
- package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
- package/dist/esm/providers/I18nProvider/I18nProvider.js +1 -1
- package/dist/esm/providers/I18nProvider/enResource.d.ts +1 -1
- package/dist/esm/providers/I18nProvider/enResource.js +1 -1
- package/dist/esm/providers/I18nProvider/types.d.ts +2 -2
- package/dist/esm/providers/SDKClientProvider.js +25 -24
- package/dist/esm/providers/SDKClientProvider.js.map +1 -1
- package/dist/esm/providers/WidgetProvider/WidgetProvider.js +22 -21
- package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
- package/dist/esm/stores/form/URLSearchParamsBuilder.js +14 -3
- package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
- package/dist/esm/stores/form/createFormStore.js +6 -2
- package/dist/esm/stores/form/createFormStore.js.map +1 -1
- package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +3 -0
- package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
- package/dist/esm/stores/form/types.d.ts +9 -1
- package/dist/esm/stores/form/types.d.ts.map +1 -1
- package/dist/esm/stores/form/types.js.map +1 -1
- package/dist/esm/stores/form/useClearAmountFields.js +1 -0
- package/dist/esm/stores/form/useClearAmountFields.js.map +1 -1
- package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js +4 -2
- package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js.map +1 -1
- package/dist/esm/stores/navigationTabs/utils.js +38 -8
- package/dist/esm/stores/navigationTabs/utils.js.map +1 -1
- package/dist/esm/types/events.d.ts +10 -1
- package/dist/esm/types/events.d.ts.map +1 -1
- package/dist/esm/types/events.js +2 -0
- package/dist/esm/types/events.js.map +1 -1
- package/dist/esm/types/widget.d.ts +10 -0
- package/dist/esm/types/widget.d.ts.map +1 -1
- package/dist/esm/types/widget.js.map +1 -1
- package/dist/esm/utils/format.d.ts +1 -1
- package/dist/esm/utils/format.d.ts.map +1 -1
- package/dist/esm/utils/format.js +2 -2
- package/dist/esm/utils/format.js.map +1 -1
- package/dist/esm/utils/limitOrder.js +61 -0
- package/dist/esm/utils/limitOrder.js.map +1 -0
- package/dist/esm/utils/navigationRoutes.d.ts +2 -0
- package/dist/esm/utils/navigationRoutes.d.ts.map +1 -1
- package/dist/esm/utils/navigationRoutes.js +6 -0
- package/dist/esm/utils/navigationRoutes.js.map +1 -1
- package/dist/esm/utils/token.js +38 -3
- package/dist/esm/utils/token.js.map +1 -1
- package/package.json +11 -11
- package/src/AppDefault.tsx +16 -0
- package/src/components/AmountInput/AmountInputEndAdornment.style.ts +1 -1
- package/src/components/AmountInput/AmountInputEndAdornment.tsx +5 -2
- package/src/components/AmountInputCard/AmountInputCard.style.tsx +157 -0
- package/src/components/AmountInputCard/AmountInputCardPair.tsx +25 -0
- package/src/components/AmountInputCard/BalanceDisplay.style.tsx +9 -0
- package/src/components/AmountInputCard/BalanceDisplay.tsx +46 -0
- package/src/components/AmountInputCard/FiatValueToggle.tsx +55 -0
- package/src/components/AmountInputCard/PercentageChips.style.tsx +60 -0
- package/src/components/AmountInputCard/PercentageChips.tsx +100 -0
- package/src/components/AmountInputCard/ReceiveAmountCard.tsx +219 -0
- package/src/components/AmountInputCard/SendAmountCard.tsx +179 -0
- package/src/components/Avatar/Avatar.tsx +8 -3
- package/src/components/Header/HeaderNavigationTabs.tsx +19 -2
- package/src/components/LimitOrderSettings/LimitOrderSettings.style.tsx +69 -0
- package/src/components/LimitOrderSettings/LimitOrderSettings.tsx +124 -0
- package/src/components/LimitPriceCard/LimitPriceCard.style.tsx +103 -0
- package/src/components/LimitPriceCard/LimitPriceCard.tsx +153 -0
- package/src/components/LimitPriceCard/LimitPricePresets.tsx +139 -0
- package/src/components/RouteCard/RouteProviderCard.tsx +113 -0
- package/src/components/Routes/LimitOrderRoutes.tsx +64 -0
- package/src/components/Routes/Routes.tsx +3 -2
- package/src/components/SettingsPanel/SettingsPanel.style.tsx +41 -0
- package/src/components/SettingsPanel/SettingsPanel.tsx +73 -0
- package/src/components/SettingsPanel/utils.ts +27 -0
- package/src/components/SwapButton/SwapButton.style.tsx +28 -0
- package/src/components/SwapButton/SwapButton.tsx +71 -0
- package/src/components/TokenPillButton/TokenPillButton.style.tsx +50 -0
- package/src/components/TokenPillButton/TokenPillButton.tsx +81 -0
- package/src/config/version.ts +1 -1
- package/src/hooks/useFilteredByTokenBalances.ts +7 -2
- package/src/hooks/useLimitMarketRate.ts +39 -0
- package/src/hooks/useLinkedLimitFields.ts +74 -0
- package/src/hooks/useRoutes.ts +45 -4
- package/src/hooks/useWideVariant.ts +8 -2
- package/src/pages/MainPage/LimitPageContent.tsx +18 -0
- package/src/pages/MainPage/MainPage.tsx +43 -30
- package/src/pages/MainPage/MainPageActions.tsx +35 -0
- package/src/pages/MainPage/ReviewButton.tsx +13 -1
- package/src/pages/RoutePriorityPage.tsx +55 -0
- package/src/pages/SettingsPage/RoutePrioritySettings.tsx +14 -38
- package/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx +176 -0
- package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx +32 -25
- package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx +14 -128
- package/src/pages/SettingsPage/ThemeSettings.tsx +4 -0
- package/src/stores/form/URLSearchParamsBuilder.tsx +25 -3
- package/src/stores/form/createFormStore.ts +7 -3
- package/src/stores/form/getDefaultValuesFromQueryString.ts +11 -0
- package/src/stores/form/types.ts +14 -1
- package/src/stores/form/useClearAmountFields.ts +1 -0
- package/src/stores/navigationTabs/types.ts +6 -0
- package/src/stores/navigationTabs/useNavigationTabsStore.tsx +8 -3
- package/src/stores/navigationTabs/utils.ts +67 -5
- package/src/types/events.ts +12 -0
- package/src/types/widget.ts +10 -0
- package/src/utils/format.ts +6 -2
- package/src/utils/limitOrder.ts +88 -0
- package/src/utils/navigationRoutes.ts +6 -0
- package/src/utils/token.ts +71 -20
|
@@ -16,7 +16,7 @@ import { RouteNotFoundCard } from '../RouteCard/RouteNotFoundCard.js'
|
|
|
16
16
|
export const Routes: React.FC<CardProps> = (props) => {
|
|
17
17
|
const { t } = useTranslation()
|
|
18
18
|
const navigate = useNavigate()
|
|
19
|
-
const { mode, modeOptions, showSingleRoute } = useWidgetConfig()
|
|
19
|
+
const { mode, modeOptions, showSingleRoute, defaultUI } = useWidgetConfig()
|
|
20
20
|
const {
|
|
21
21
|
routes,
|
|
22
22
|
isLoading,
|
|
@@ -51,7 +51,8 @@ export const Routes: React.FC<CardProps> = (props) => {
|
|
|
51
51
|
|
|
52
52
|
return (
|
|
53
53
|
<Card {...props}>
|
|
54
|
-
|
|
54
|
+
{/* The cards layout's Receive card already carries this header. */}
|
|
55
|
+
{defaultUI?.layout === 'cards' ? null : <CardTitle>{title}</CardTitle>}
|
|
55
56
|
<ProgressToNextUpdate
|
|
56
57
|
updatedAt={dataUpdatedAt || Date.now()}
|
|
57
58
|
timeToUpdate={refetchTime}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Box, ButtonBase, styled, Typography } from '@mui/material'
|
|
2
|
+
import type React from 'react'
|
|
3
|
+
|
|
4
|
+
export const SettingsPanelContainer: React.FC<
|
|
5
|
+
React.ComponentProps<typeof Box>
|
|
6
|
+
> = styled(Box)(({ theme }) => ({
|
|
7
|
+
display: 'flex',
|
|
8
|
+
flexDirection: 'column',
|
|
9
|
+
gap: theme.spacing(1),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
export const SettingsPanelButton: React.FC<
|
|
13
|
+
React.ComponentProps<typeof ButtonBase>
|
|
14
|
+
> = styled(ButtonBase)(({ theme }) => ({
|
|
15
|
+
display: 'flex',
|
|
16
|
+
width: '100%',
|
|
17
|
+
justifyContent: 'space-between',
|
|
18
|
+
alignItems: 'center',
|
|
19
|
+
gap: theme.spacing(1),
|
|
20
|
+
padding: theme.spacing(2),
|
|
21
|
+
borderRadius: theme.vars.shape.borderRadius,
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
export const SettingsPanelTitle: React.FC<
|
|
25
|
+
React.ComponentProps<typeof Typography>
|
|
26
|
+
> = styled(Typography)(({ theme }) => ({
|
|
27
|
+
fontSize: 14,
|
|
28
|
+
lineHeight: 1,
|
|
29
|
+
fontWeight: 700,
|
|
30
|
+
color: theme.vars.palette.text.primary,
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
export const SettingsPanelValue: React.FC<
|
|
34
|
+
React.ComponentProps<typeof Typography>
|
|
35
|
+
> = styled(Typography)(({ theme }) => ({
|
|
36
|
+
fontSize: 14,
|
|
37
|
+
lineHeight: 1,
|
|
38
|
+
fontWeight: 500,
|
|
39
|
+
color: theme.vars.palette.text.secondary,
|
|
40
|
+
whiteSpace: 'nowrap',
|
|
41
|
+
}))
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { BoxProps } from '@mui/material'
|
|
2
|
+
import { useNavigate } from '@tanstack/react-router'
|
|
3
|
+
import type { JSX } from 'react'
|
|
4
|
+
import { useTranslation } from 'react-i18next'
|
|
5
|
+
import { useSplitMode } from '../../stores/navigationTabs/useNavigationTabsStore.js'
|
|
6
|
+
import { useSettingsStore } from '../../stores/settings/SettingsStore.js'
|
|
7
|
+
import { useSettings } from '../../stores/settings/useSettings.js'
|
|
8
|
+
import { navigationRoutes } from '../../utils/navigationRoutes.js'
|
|
9
|
+
import { Card } from '../Card/Card.js'
|
|
10
|
+
import {
|
|
11
|
+
SettingsPanelButton,
|
|
12
|
+
SettingsPanelContainer,
|
|
13
|
+
SettingsPanelTitle,
|
|
14
|
+
SettingsPanelValue,
|
|
15
|
+
} from './SettingsPanel.style.js'
|
|
16
|
+
import {
|
|
17
|
+
bridgeSettingsPanelKeys,
|
|
18
|
+
type SettingsPanelKey,
|
|
19
|
+
swapSettingsPanelKeys,
|
|
20
|
+
} from './utils.js'
|
|
21
|
+
|
|
22
|
+
export const SettingsPanel: React.FC<BoxProps> = (props): JSX.Element => {
|
|
23
|
+
const { t } = useTranslation()
|
|
24
|
+
const navigate = useNavigate()
|
|
25
|
+
const splitMode = useSplitMode()
|
|
26
|
+
const { slippage, routePriority } = useSettings(['slippage', 'routePriority'])
|
|
27
|
+
const [enabledBridges, totalBridges, enabledExchanges, totalExchanges] =
|
|
28
|
+
useSettingsStore((state) => [
|
|
29
|
+
Object.values(state._enabledBridges).filter(Boolean).length,
|
|
30
|
+
Object.values(state._enabledBridges).length,
|
|
31
|
+
Object.values(state._enabledExchanges).filter(Boolean).length,
|
|
32
|
+
Object.values(state._enabledExchanges).length,
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
const rows =
|
|
36
|
+
splitMode === 'bridge' ? bridgeSettingsPanelKeys : swapSettingsPanelKeys
|
|
37
|
+
|
|
38
|
+
const labelByKey: Record<SettingsPanelKey, string> = {
|
|
39
|
+
routePriority: t('settings.routePriority'),
|
|
40
|
+
exchanges: t('settings.enabledExchanges'),
|
|
41
|
+
bridges: t('settings.enabledBridges'),
|
|
42
|
+
slippage: t('settings.slippage'),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const valueByKey: Record<SettingsPanelKey, string> = {
|
|
46
|
+
routePriority:
|
|
47
|
+
routePriority === 'FASTEST'
|
|
48
|
+
? t('settings.routePriorityOptions.fastest.title')
|
|
49
|
+
: t('settings.routePriorityOptions.cheapest.title'),
|
|
50
|
+
exchanges: `${enabledExchanges}/${totalExchanges}`,
|
|
51
|
+
bridges: `${enabledBridges}/${totalBridges}`,
|
|
52
|
+
slippage: slippage ? `${slippage}%` : t('button.auto'),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleClick = (key: SettingsPanelKey) => {
|
|
56
|
+
navigate({
|
|
57
|
+
to: `/${navigationRoutes.settings}/${navigationRoutes[key]}`,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<SettingsPanelContainer {...props}>
|
|
63
|
+
{rows.map((key) => (
|
|
64
|
+
<Card key={key}>
|
|
65
|
+
<SettingsPanelButton onClick={() => handleClick(key)} disableRipple>
|
|
66
|
+
<SettingsPanelTitle>{labelByKey[key]}</SettingsPanelTitle>
|
|
67
|
+
<SettingsPanelValue>{valueByKey[key]}</SettingsPanelValue>
|
|
68
|
+
</SettingsPanelButton>
|
|
69
|
+
</Card>
|
|
70
|
+
))}
|
|
71
|
+
</SettingsPanelContainer>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { NavigationTabKey } from '../../types/widget.js'
|
|
2
|
+
|
|
3
|
+
export type SettingsPanelKey =
|
|
4
|
+
| 'routePriority'
|
|
5
|
+
| 'exchanges'
|
|
6
|
+
| 'bridges'
|
|
7
|
+
| 'slippage'
|
|
8
|
+
|
|
9
|
+
/** Navigation tabs that surface the settings panel on the main page. */
|
|
10
|
+
export const advancedNavigationTabKeys: NavigationTabKey[] = [
|
|
11
|
+
'swap-advanced',
|
|
12
|
+
'bridge-advanced',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
// Keys are ordered to match the settings page layout.
|
|
16
|
+
// Swap is same-chain, so route priority and bridges are not relevant.
|
|
17
|
+
export const swapSettingsPanelKeys: SettingsPanelKey[] = [
|
|
18
|
+
'slippage',
|
|
19
|
+
'exchanges',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
export const bridgeSettingsPanelKeys: SettingsPanelKey[] = [
|
|
23
|
+
'routePriority',
|
|
24
|
+
'slippage',
|
|
25
|
+
'bridges',
|
|
26
|
+
'exchanges',
|
|
27
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Box, styled } from '@mui/material'
|
|
2
|
+
import type React from 'react'
|
|
3
|
+
import { Card } from '../Card/Card.js'
|
|
4
|
+
|
|
5
|
+
// Sits just above MUI's appBar layer (1100) so the swap icon stays on top of the
|
|
6
|
+
// two overlapping amount cards it bridges.
|
|
7
|
+
const swapButtonZIndex = 1110
|
|
8
|
+
|
|
9
|
+
export const SwapIconCard: React.FC<React.ComponentProps<typeof Card>> = styled(
|
|
10
|
+
Card
|
|
11
|
+
)(({ theme }) => ({
|
|
12
|
+
height: 32,
|
|
13
|
+
width: 32,
|
|
14
|
+
fontSize: 16,
|
|
15
|
+
display: 'flex',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
justifyContent: 'center',
|
|
18
|
+
borderRadius: theme.vars.shape.borderRadiusTertiary,
|
|
19
|
+
zIndex: swapButtonZIndex,
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
export const SwapButtonContainer: React.FC<React.ComponentProps<typeof Box>> =
|
|
23
|
+
styled(Box)(({ theme }) => ({
|
|
24
|
+
display: 'flex',
|
|
25
|
+
justifyContent: 'center',
|
|
26
|
+
alignItems: 'center',
|
|
27
|
+
margin: theme.spacing(-1.5),
|
|
28
|
+
}))
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import ArrowDownward from '@mui/icons-material/ArrowDownward'
|
|
2
|
+
import type { BoxProps } from '@mui/material'
|
|
3
|
+
import type { JSX } from 'react'
|
|
4
|
+
import { useTranslation } from 'react-i18next'
|
|
5
|
+
import { useAvailableChains } from '../../hooks/useAvailableChains.js'
|
|
6
|
+
import { useToAddressAutoPopulate } from '../../hooks/useToAddressAutoPopulate.js'
|
|
7
|
+
import { useToAddressReset } from '../../hooks/useToAddressReset.js'
|
|
8
|
+
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
|
|
9
|
+
import { useFieldActions } from '../../stores/form/useFieldActions.js'
|
|
10
|
+
import { SwapButtonContainer, SwapIconCard } from './SwapButton.style.js'
|
|
11
|
+
|
|
12
|
+
export const SwapButton: React.FC<BoxProps> = ({
|
|
13
|
+
sx,
|
|
14
|
+
...props
|
|
15
|
+
}): JSX.Element => {
|
|
16
|
+
const { t } = useTranslation()
|
|
17
|
+
const { mode } = useWidgetConfig()
|
|
18
|
+
const { setFieldValue, getFieldValues } = useFieldActions()
|
|
19
|
+
const { getChainById } = useAvailableChains()
|
|
20
|
+
const { tryResetToAddress } = useToAddressReset()
|
|
21
|
+
const autoPopulateToAddress = useToAddressAutoPopulate()
|
|
22
|
+
|
|
23
|
+
const handleClick = (): void => {
|
|
24
|
+
const [fromChainId, fromToken, toChainId, toToken, toAddress, toAmount] =
|
|
25
|
+
getFieldValues(
|
|
26
|
+
'fromChain',
|
|
27
|
+
'fromToken',
|
|
28
|
+
'toChain',
|
|
29
|
+
'toToken',
|
|
30
|
+
'toAddress',
|
|
31
|
+
'toAmount'
|
|
32
|
+
)
|
|
33
|
+
const newFromAmount =
|
|
34
|
+
mode === 'limit' && toAmount ? (toAmount as string) : ''
|
|
35
|
+
setFieldValue('fromAmount', newFromAmount, { isTouched: true })
|
|
36
|
+
setFieldValue('fromChain', toChainId, { isTouched: true })
|
|
37
|
+
setFieldValue('fromToken', toToken, { isTouched: true })
|
|
38
|
+
setFieldValue('toChain', fromChainId, { isTouched: true })
|
|
39
|
+
setFieldValue('toToken', fromToken, { isTouched: true })
|
|
40
|
+
|
|
41
|
+
const autoPopulatedToAddress = autoPopulateToAddress({
|
|
42
|
+
formType: 'from',
|
|
43
|
+
selectedToAddress: toAddress,
|
|
44
|
+
selectedChainId: toChainId,
|
|
45
|
+
selectedOppositeChainId: fromChainId,
|
|
46
|
+
selectedOppositeTokenAddress: fromToken,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (autoPopulatedToAddress) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const toChain = getChainById(fromChainId)
|
|
54
|
+
if (toChain) {
|
|
55
|
+
tryResetToAddress(toChain)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<SwapButtonContainer sx={sx} {...props}>
|
|
61
|
+
<SwapIconCard
|
|
62
|
+
component="button"
|
|
63
|
+
onClick={handleClick}
|
|
64
|
+
aria-label={t('button.swap')}
|
|
65
|
+
data-testid="widget-swap-tokens-button"
|
|
66
|
+
>
|
|
67
|
+
<ArrowDownward fontSize="inherit" />
|
|
68
|
+
</SwapIconCard>
|
|
69
|
+
</SwapButtonContainer>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Button, styled, type Theme, Typography } from '@mui/material'
|
|
2
|
+
import type React from 'react'
|
|
3
|
+
|
|
4
|
+
const pillLayout = (theme: Theme) => ({
|
|
5
|
+
height: 36,
|
|
6
|
+
borderRadius: theme.vars.shape.borderRadiusSecondary,
|
|
7
|
+
padding: theme.spacing(0.5, 1),
|
|
8
|
+
display: 'flex',
|
|
9
|
+
alignItems: 'center',
|
|
10
|
+
gap: theme.spacing(1),
|
|
11
|
+
whiteSpace: 'nowrap',
|
|
12
|
+
flexShrink: 0,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export const TokenPill: React.FC<React.ComponentProps<typeof Button>> = styled(
|
|
16
|
+
Button
|
|
17
|
+
)(({ theme }) => ({
|
|
18
|
+
...pillLayout(theme),
|
|
19
|
+
backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,
|
|
20
|
+
border: '1px solid transparent',
|
|
21
|
+
transition: theme.transitions.create(['background-color'], {
|
|
22
|
+
duration: theme.transitions.duration.short,
|
|
23
|
+
}),
|
|
24
|
+
'&:hover': {
|
|
25
|
+
backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)`,
|
|
26
|
+
},
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
export const TokenSelectPill: React.FC<React.ComponentProps<typeof Button>> =
|
|
30
|
+
styled(TokenPill)({ minWidth: 0 })
|
|
31
|
+
|
|
32
|
+
export const TokenPillSymbol: React.FC<
|
|
33
|
+
React.ComponentProps<typeof Typography>
|
|
34
|
+
> = styled(Typography)(({ theme }) => ({
|
|
35
|
+
fontSize: 14,
|
|
36
|
+
fontWeight: 700,
|
|
37
|
+
lineHeight: '18px',
|
|
38
|
+
overflow: 'hidden',
|
|
39
|
+
textOverflow: 'ellipsis',
|
|
40
|
+
whiteSpace: 'nowrap',
|
|
41
|
+
color: theme.vars.palette.text.primary,
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
export const TokenPillLabel: React.FC<React.ComponentProps<typeof Typography>> =
|
|
45
|
+
styled(Typography)(({ theme }) => ({
|
|
46
|
+
fontSize: 16,
|
|
47
|
+
fontWeight: 700,
|
|
48
|
+
lineHeight: '20px',
|
|
49
|
+
color: theme.vars.palette.text.secondary,
|
|
50
|
+
}))
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useNavigate } from '@tanstack/react-router'
|
|
2
|
+
import type { JSX } from 'react'
|
|
3
|
+
import { useTranslation } from 'react-i18next'
|
|
4
|
+
import { useChain } from '../../hooks/useChain.js'
|
|
5
|
+
import { useToken } from '../../hooks/useToken.js'
|
|
6
|
+
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
|
|
7
|
+
import type { FormTypeProps } from '../../stores/form/types.js'
|
|
8
|
+
import { FormKeyHelper } from '../../stores/form/types.js'
|
|
9
|
+
import { useFieldValues } from '../../stores/form/useFieldValues.js'
|
|
10
|
+
import type { DisabledUIConfig } from '../../types/widget.js'
|
|
11
|
+
import { navigationRoutes } from '../../utils/navigationRoutes.js'
|
|
12
|
+
import { AvatarBadgedDefault } from '../Avatar/Avatar.js'
|
|
13
|
+
import { TokenAvatar } from '../Avatar/TokenAvatar.js'
|
|
14
|
+
import {
|
|
15
|
+
TokenPill,
|
|
16
|
+
TokenPillLabel,
|
|
17
|
+
TokenPillSymbol,
|
|
18
|
+
TokenSelectPill,
|
|
19
|
+
} from './TokenPillButton.style.js'
|
|
20
|
+
|
|
21
|
+
export const TokenPillButton: React.FC<FormTypeProps> = ({
|
|
22
|
+
formType,
|
|
23
|
+
}): JSX.Element => {
|
|
24
|
+
const { t } = useTranslation()
|
|
25
|
+
const navigate = useNavigate()
|
|
26
|
+
const { disabledUI, mode } = useWidgetConfig()
|
|
27
|
+
|
|
28
|
+
const tokenKey = FormKeyHelper.getTokenKey(formType)
|
|
29
|
+
const [chainId, tokenAddress] = useFieldValues(
|
|
30
|
+
FormKeyHelper.getChainKey(formType),
|
|
31
|
+
tokenKey
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const { chain } = useChain(chainId)
|
|
35
|
+
const { token } = useToken(chainId, tokenAddress)
|
|
36
|
+
|
|
37
|
+
const isSelected = !!(chain && token)
|
|
38
|
+
|
|
39
|
+
const handleClick = (): void => {
|
|
40
|
+
navigate({
|
|
41
|
+
to:
|
|
42
|
+
formType === 'from'
|
|
43
|
+
? navigationRoutes.fromToken
|
|
44
|
+
: mode === 'refuel'
|
|
45
|
+
? navigationRoutes.toTokenNative
|
|
46
|
+
: navigationRoutes.toToken,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const isDisabled = !!disabledUI?.[tokenKey as keyof DisabledUIConfig]
|
|
51
|
+
|
|
52
|
+
const testId = `widget-${formType}-token-button`
|
|
53
|
+
|
|
54
|
+
if (isSelected) {
|
|
55
|
+
return (
|
|
56
|
+
<TokenPill
|
|
57
|
+
onClick={isDisabled ? undefined : handleClick}
|
|
58
|
+
disabled={isDisabled}
|
|
59
|
+
data-testid={testId}
|
|
60
|
+
>
|
|
61
|
+
<TokenAvatar
|
|
62
|
+
token={token}
|
|
63
|
+
chain={chain}
|
|
64
|
+
tokenAvatarSize={24}
|
|
65
|
+
chainAvatarSize={12}
|
|
66
|
+
/>
|
|
67
|
+
<TokenPillSymbol>{token.symbol}</TokenPillSymbol>
|
|
68
|
+
</TokenPill>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<TokenSelectPill
|
|
74
|
+
onClick={isDisabled ? undefined : handleClick}
|
|
75
|
+
data-testid={testId}
|
|
76
|
+
>
|
|
77
|
+
<AvatarBadgedDefault chain={chain} avatarSize={24} badgeSize={12} />
|
|
78
|
+
<TokenPillLabel>{t('main.select')}</TokenPillLabel>
|
|
79
|
+
</TokenSelectPill>
|
|
80
|
+
)
|
|
81
|
+
}
|
package/src/config/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = '@lifi/widget'
|
|
2
|
-
export const version = '4.
|
|
2
|
+
export const version = '4.3.0'
|
|
@@ -11,6 +11,7 @@ import { useSDKClient } from '../providers/SDKClientProvider.js'
|
|
|
11
11
|
import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'
|
|
12
12
|
import type { FormType } from '../stores/form/types.js'
|
|
13
13
|
import { getConfigItemSets, isFormItemAllowed } from '../utils/item.js'
|
|
14
|
+
import { getVerifiedTokensSets } from '../utils/token.js'
|
|
14
15
|
import { isSupportedToken } from '../utils/tokenList.js'
|
|
15
16
|
|
|
16
17
|
export const useFilteredTokensByBalance = (
|
|
@@ -56,11 +57,14 @@ export const useFilteredTokensByBalance = (
|
|
|
56
57
|
return result
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
const verifiedTokensSets = getVerifiedTokensSets(configTokens)
|
|
61
|
+
|
|
59
62
|
for (const [address, { tokens }] of Object.entries(accountsWithTokens)) {
|
|
60
63
|
result[address] = {}
|
|
61
64
|
|
|
62
65
|
for (const [chainIdStr, chainTokens] of Object.entries(tokens)) {
|
|
63
66
|
const chainId = Number(chainIdStr)
|
|
67
|
+
const verifiedAddresses = verifiedTokensSets?.get(chainId)
|
|
64
68
|
// Get balances for this specific chain
|
|
65
69
|
const balances = existingBalances?.[chainId]
|
|
66
70
|
// If no balances, RPC all tokens of the chain
|
|
@@ -115,10 +119,11 @@ export const useFilteredTokensByBalance = (
|
|
|
115
119
|
)
|
|
116
120
|
)
|
|
117
121
|
})
|
|
118
|
-
// Mark tokens from wallet balances as unverified
|
|
122
|
+
// Mark tokens from wallet balances as unverified unless allowlisted
|
|
119
123
|
.map((token: WalletTokenExtended) => ({
|
|
120
124
|
...token,
|
|
121
|
-
verified:
|
|
125
|
+
verified:
|
|
126
|
+
verifiedAddresses?.has(token.address.toLowerCase()) ?? false,
|
|
122
127
|
})) as TokenExtended[]
|
|
123
128
|
|
|
124
129
|
// Combine both sets of tokens - convert WalletTokenExtended to TokenAmount
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FormKeyHelper } from '../stores/form/types.js'
|
|
2
|
+
import { useFieldValues } from '../stores/form/useFieldValues.js'
|
|
3
|
+
import { useToken } from './useToken.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Canonical limit-order market rate (receive token per send token), derived
|
|
7
|
+
* from the tokens' USD prices: 1 send token ≈ sendPrice / receivePrice receive
|
|
8
|
+
* tokens. This is the spot ratio — what the price card's "Market" reference and
|
|
9
|
+
* proximity indicator compare against. Returns `undefined` until both token
|
|
10
|
+
* prices are known.
|
|
11
|
+
*/
|
|
12
|
+
export const useLimitMarketRate = (): number | undefined => {
|
|
13
|
+
const [fromChainId, fromTokenAddress, toChainId, toTokenAddress] =
|
|
14
|
+
useFieldValues(
|
|
15
|
+
FormKeyHelper.getChainKey('from'),
|
|
16
|
+
FormKeyHelper.getTokenKey('from'),
|
|
17
|
+
FormKeyHelper.getChainKey('to'),
|
|
18
|
+
FormKeyHelper.getTokenKey('to')
|
|
19
|
+
)
|
|
20
|
+
const { token: fromToken } = useToken(fromChainId, fromTokenAddress)
|
|
21
|
+
const { token: toToken } = useToken(toChainId, toTokenAddress)
|
|
22
|
+
|
|
23
|
+
const fromPrice = Number(fromToken?.priceUSD)
|
|
24
|
+
const toPrice = Number(toToken?.priceUSD)
|
|
25
|
+
|
|
26
|
+
// A `priceUSD` of `"0"` (or missing) means the price is unknown — there's no
|
|
27
|
+
// meaningful spot ratio to derive, and dividing by a zero `toPrice` would be
|
|
28
|
+
// `Infinity`, so the falsy guard intentionally suppresses the rate here.
|
|
29
|
+
if (
|
|
30
|
+
!fromPrice ||
|
|
31
|
+
!toPrice ||
|
|
32
|
+
Number.isNaN(fromPrice) ||
|
|
33
|
+
Number.isNaN(toPrice)
|
|
34
|
+
) {
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return fromPrice / toPrice
|
|
39
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { FormKeyHelper } from '../stores/form/types.js'
|
|
2
|
+
import { useFieldActions } from '../stores/form/useFieldActions.js'
|
|
3
|
+
import { useFieldValues } from '../stores/form/useFieldValues.js'
|
|
4
|
+
import { deriveLimitPrice, deriveReceiveAmount } from '../utils/limitOrder.js'
|
|
5
|
+
import { useToken } from './useToken.js'
|
|
6
|
+
|
|
7
|
+
export interface LinkedLimitFields {
|
|
8
|
+
limitPrice: string
|
|
9
|
+
/** Set the send amount (explicit user edit); derives receiveAmount when a price is set. */
|
|
10
|
+
setSendAmount: (value: string) => void
|
|
11
|
+
/** Set the limit price (canonical: receive per send); derives receiveAmount when a send amount is set. */
|
|
12
|
+
setLimitPrice: (value: string) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Implements the linked-field derivation for limit orders:
|
|
17
|
+
* - `limitPrice = receiveAmount / sendAmount`
|
|
18
|
+
* - editing sendAmount recomputes receiveAmount, holding the current price
|
|
19
|
+
* - editing limitPrice (with a send amount set) recomputes receiveAmount
|
|
20
|
+
* - sendAmount never changes without an explicit user edit
|
|
21
|
+
*
|
|
22
|
+
* The receive amount (`toAmount`) is read-only in the UI — it is derived here
|
|
23
|
+
* from the send amount and limit price, then sent to the routes request; the
|
|
24
|
+
* receive card only displays the resulting quote.
|
|
25
|
+
*
|
|
26
|
+
* The limit price is NOT stored: it is purely a function of the `fromAmount`
|
|
27
|
+
* and `toAmount` form fields (canonical receive-per-send). The UI layer handles
|
|
28
|
+
* base/quote display inversion. Because it's derived, editing the send amount
|
|
29
|
+
* only has to preserve the pre-edit price.
|
|
30
|
+
*/
|
|
31
|
+
export const useLinkedLimitFields = (): LinkedLimitFields => {
|
|
32
|
+
const { setFieldValue } = useFieldActions()
|
|
33
|
+
const [toChainId, toTokenAddress, sendAmount, receiveAmount] = useFieldValues(
|
|
34
|
+
FormKeyHelper.getChainKey('to'),
|
|
35
|
+
FormKeyHelper.getTokenKey('to'),
|
|
36
|
+
FormKeyHelper.getAmountKey('from'),
|
|
37
|
+
FormKeyHelper.getAmountKey('to')
|
|
38
|
+
)
|
|
39
|
+
const { token: toToken } = useToken(toChainId, toTokenAddress)
|
|
40
|
+
|
|
41
|
+
const limitPrice = deriveLimitPrice(sendAmount, receiveAmount)
|
|
42
|
+
|
|
43
|
+
const setSendAmount = (value: string): void => {
|
|
44
|
+
// Hold the pre-edit price (derived from the current fields) so changing the
|
|
45
|
+
// send amount rescales the receive amount instead of moving the price.
|
|
46
|
+
setFieldValue('fromAmount', value, { isDirty: true, isTouched: true })
|
|
47
|
+
const nextReceive = deriveReceiveAmount(
|
|
48
|
+
value,
|
|
49
|
+
limitPrice,
|
|
50
|
+
toToken?.decimals
|
|
51
|
+
)
|
|
52
|
+
if (nextReceive) {
|
|
53
|
+
setFieldValue('toAmount', nextReceive, { isDirty: true })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const setLimitPrice = (value: string): void => {
|
|
58
|
+
// Write unconditionally (including '') so the receive amount tracks the
|
|
59
|
+
// price both ways — clearing the price clears the derived receive amount
|
|
60
|
+
// instead of leaving a stale value that re-derives the old price.
|
|
61
|
+
const nextReceive = deriveReceiveAmount(
|
|
62
|
+
sendAmount,
|
|
63
|
+
value,
|
|
64
|
+
toToken?.decimals
|
|
65
|
+
)
|
|
66
|
+
setFieldValue('toAmount', nextReceive, { isDirty: true })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
limitPrice,
|
|
71
|
+
setSendAmount,
|
|
72
|
+
setLimitPrice,
|
|
73
|
+
}
|
|
74
|
+
}
|