@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.
Files changed (210) hide show
  1. package/dist/esm/AppDefault.js +14 -0
  2. package/dist/esm/AppDefault.js.map +1 -1
  3. package/dist/esm/components/AmountInput/AmountInput.js +2 -1
  4. package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
  5. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +4 -2
  6. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
  7. package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js +1 -1
  8. package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js.map +1 -1
  9. package/dist/esm/components/AmountInput/PriceFormHelperText.js +4 -2
  10. package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
  11. package/dist/esm/components/AmountInputCard/AmountInputCard.style.js +98 -0
  12. package/dist/esm/components/AmountInputCard/AmountInputCard.style.js.map +1 -0
  13. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js +25 -0
  14. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js.map +1 -0
  15. package/dist/esm/components/AmountInputCard/BalanceDisplay.js +35 -0
  16. package/dist/esm/components/AmountInputCard/BalanceDisplay.js.map +1 -0
  17. package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js +11 -0
  18. package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js.map +1 -0
  19. package/dist/esm/components/AmountInputCard/FiatValueToggle.js +41 -0
  20. package/dist/esm/components/AmountInputCard/FiatValueToggle.js.map +1 -0
  21. package/dist/esm/components/AmountInputCard/PercentageChips.js +80 -0
  22. package/dist/esm/components/AmountInputCard/PercentageChips.js.map +1 -0
  23. package/dist/esm/components/AmountInputCard/PercentageChips.style.js +43 -0
  24. package/dist/esm/components/AmountInputCard/PercentageChips.style.js.map +1 -0
  25. package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js +137 -0
  26. package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js.map +1 -0
  27. package/dist/esm/components/AmountInputCard/SendAmountCard.js +102 -0
  28. package/dist/esm/components/AmountInputCard/SendAmountCard.js.map +1 -0
  29. package/dist/esm/components/Avatar/Avatar.js +12 -3
  30. package/dist/esm/components/Avatar/Avatar.js.map +1 -1
  31. package/dist/esm/components/Chains/VirtualizedChainList.js +2 -1
  32. package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
  33. package/dist/esm/components/Header/HeaderNavigationTabs.js +14 -1
  34. package/dist/esm/components/Header/HeaderNavigationTabs.js.map +1 -1
  35. package/dist/esm/components/IconCircle/IconCircle.style.js +1 -1
  36. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js +99 -0
  37. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js.map +1 -0
  38. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js +52 -0
  39. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js.map +1 -0
  40. package/dist/esm/components/LimitPriceCard/LimitPriceCard.js +115 -0
  41. package/dist/esm/components/LimitPriceCard/LimitPriceCard.js.map +1 -0
  42. package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js +89 -0
  43. package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js.map +1 -0
  44. package/dist/esm/components/LimitPriceCard/LimitPricePresets.js +91 -0
  45. package/dist/esm/components/LimitPriceCard/LimitPricePresets.js.map +1 -0
  46. package/dist/esm/components/RouteCard/RouteProviderCard.js +118 -0
  47. package/dist/esm/components/RouteCard/RouteProviderCard.js.map +1 -0
  48. package/dist/esm/components/Routes/LimitOrderRoutes.js +34 -0
  49. package/dist/esm/components/Routes/LimitOrderRoutes.js.map +1 -0
  50. package/dist/esm/components/Routes/Routes.js +2 -2
  51. package/dist/esm/components/Routes/Routes.js.map +1 -1
  52. package/dist/esm/components/SettingsPanel/SettingsPanel.js +51 -0
  53. package/dist/esm/components/SettingsPanel/SettingsPanel.js.map +1 -0
  54. package/dist/esm/components/SettingsPanel/SettingsPanel.style.js +33 -0
  55. package/dist/esm/components/SettingsPanel/SettingsPanel.style.js.map +1 -0
  56. package/dist/esm/components/SettingsPanel/utils.js +14 -0
  57. package/dist/esm/components/SettingsPanel/utils.js.map +1 -0
  58. package/dist/esm/components/SwapButton/SwapButton.js +50 -0
  59. package/dist/esm/components/SwapButton/SwapButton.js.map +1 -0
  60. package/dist/esm/components/SwapButton/SwapButton.style.js +24 -0
  61. package/dist/esm/components/SwapButton/SwapButton.style.js.map +1 -0
  62. package/dist/esm/components/TokenPillButton/TokenPillButton.js +52 -0
  63. package/dist/esm/components/TokenPillButton/TokenPillButton.js.map +1 -0
  64. package/dist/esm/components/TokenPillButton/TokenPillButton.style.js +39 -0
  65. package/dist/esm/components/TokenPillButton/TokenPillButton.style.js.map +1 -0
  66. package/dist/esm/config/version.d.ts +1 -1
  67. package/dist/esm/config/version.js +1 -1
  68. package/dist/esm/config/version.js.map +1 -1
  69. package/dist/esm/hooks/useChainSearch.js +2 -1
  70. package/dist/esm/hooks/useChainSearch.js.map +1 -1
  71. package/dist/esm/hooks/useFilteredByTokenBalances.js +4 -1
  72. package/dist/esm/hooks/useFilteredByTokenBalances.js.map +1 -1
  73. package/dist/esm/hooks/useLimitMarketRate.js +24 -0
  74. package/dist/esm/hooks/useLimitMarketRate.js.map +1 -0
  75. package/dist/esm/hooks/useLinkedLimitFields.js +49 -0
  76. package/dist/esm/hooks/useLinkedLimitFields.js.map +1 -0
  77. package/dist/esm/hooks/useRoutes.js +23 -6
  78. package/dist/esm/hooks/useRoutes.js.map +1 -1
  79. package/dist/esm/hooks/useTokens.js +16 -14
  80. package/dist/esm/hooks/useTokens.js.map +1 -1
  81. package/dist/esm/hooks/useWideVariant.js +5 -2
  82. package/dist/esm/hooks/useWideVariant.js.map +1 -1
  83. package/dist/esm/i18n/en.json +22 -0
  84. package/dist/esm/index.d.ts +2 -2
  85. package/dist/esm/pages/MainPage/LimitPageContent.js +23 -0
  86. package/dist/esm/pages/MainPage/LimitPageContent.js.map +1 -0
  87. package/dist/esm/pages/MainPage/MainPage.js +19 -22
  88. package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
  89. package/dist/esm/pages/MainPage/MainPageActions.js +28 -0
  90. package/dist/esm/pages/MainPage/MainPageActions.js.map +1 -0
  91. package/dist/esm/pages/MainPage/ReviewButton.js +9 -2
  92. package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
  93. package/dist/esm/pages/RoutePriorityPage.js +48 -0
  94. package/dist/esm/pages/RoutePriorityPage.js.map +1 -0
  95. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js +2 -2
  96. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js.map +1 -1
  97. package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js +12 -27
  98. package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js.map +1 -1
  99. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js +133 -0
  100. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js.map +1 -0
  101. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js +14 -72
  102. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js.map +1 -1
  103. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js +26 -14
  104. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js.map +1 -1
  105. package/dist/esm/pages/SettingsPage/ThemeSettings.js +4 -0
  106. package/dist/esm/pages/SettingsPage/ThemeSettings.js.map +1 -1
  107. package/dist/esm/pages/TransactionPage/TransactionReview.js +4 -1
  108. package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
  109. package/dist/esm/providers/I18nProvider/I18nProvider.js +1 -1
  110. package/dist/esm/providers/I18nProvider/enResource.d.ts +1 -1
  111. package/dist/esm/providers/I18nProvider/enResource.js +1 -1
  112. package/dist/esm/providers/I18nProvider/types.d.ts +2 -2
  113. package/dist/esm/providers/SDKClientProvider.js +25 -24
  114. package/dist/esm/providers/SDKClientProvider.js.map +1 -1
  115. package/dist/esm/providers/WidgetProvider/WidgetProvider.js +22 -21
  116. package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
  117. package/dist/esm/stores/form/URLSearchParamsBuilder.js +14 -3
  118. package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
  119. package/dist/esm/stores/form/createFormStore.js +6 -2
  120. package/dist/esm/stores/form/createFormStore.js.map +1 -1
  121. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +3 -0
  122. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
  123. package/dist/esm/stores/form/types.d.ts +9 -1
  124. package/dist/esm/stores/form/types.d.ts.map +1 -1
  125. package/dist/esm/stores/form/types.js.map +1 -1
  126. package/dist/esm/stores/form/useClearAmountFields.js +1 -0
  127. package/dist/esm/stores/form/useClearAmountFields.js.map +1 -1
  128. package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js +4 -2
  129. package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js.map +1 -1
  130. package/dist/esm/stores/navigationTabs/utils.js +38 -8
  131. package/dist/esm/stores/navigationTabs/utils.js.map +1 -1
  132. package/dist/esm/types/events.d.ts +10 -1
  133. package/dist/esm/types/events.d.ts.map +1 -1
  134. package/dist/esm/types/events.js +2 -0
  135. package/dist/esm/types/events.js.map +1 -1
  136. package/dist/esm/types/widget.d.ts +10 -0
  137. package/dist/esm/types/widget.d.ts.map +1 -1
  138. package/dist/esm/types/widget.js.map +1 -1
  139. package/dist/esm/utils/format.d.ts +1 -1
  140. package/dist/esm/utils/format.d.ts.map +1 -1
  141. package/dist/esm/utils/format.js +2 -2
  142. package/dist/esm/utils/format.js.map +1 -1
  143. package/dist/esm/utils/limitOrder.js +61 -0
  144. package/dist/esm/utils/limitOrder.js.map +1 -0
  145. package/dist/esm/utils/navigationRoutes.d.ts +2 -0
  146. package/dist/esm/utils/navigationRoutes.d.ts.map +1 -1
  147. package/dist/esm/utils/navigationRoutes.js +6 -0
  148. package/dist/esm/utils/navigationRoutes.js.map +1 -1
  149. package/dist/esm/utils/token.js +38 -3
  150. package/dist/esm/utils/token.js.map +1 -1
  151. package/package.json +11 -11
  152. package/src/AppDefault.tsx +16 -0
  153. package/src/components/AmountInput/AmountInputEndAdornment.style.ts +1 -1
  154. package/src/components/AmountInput/AmountInputEndAdornment.tsx +5 -2
  155. package/src/components/AmountInputCard/AmountInputCard.style.tsx +157 -0
  156. package/src/components/AmountInputCard/AmountInputCardPair.tsx +25 -0
  157. package/src/components/AmountInputCard/BalanceDisplay.style.tsx +9 -0
  158. package/src/components/AmountInputCard/BalanceDisplay.tsx +46 -0
  159. package/src/components/AmountInputCard/FiatValueToggle.tsx +55 -0
  160. package/src/components/AmountInputCard/PercentageChips.style.tsx +60 -0
  161. package/src/components/AmountInputCard/PercentageChips.tsx +100 -0
  162. package/src/components/AmountInputCard/ReceiveAmountCard.tsx +219 -0
  163. package/src/components/AmountInputCard/SendAmountCard.tsx +179 -0
  164. package/src/components/Avatar/Avatar.tsx +8 -3
  165. package/src/components/Header/HeaderNavigationTabs.tsx +19 -2
  166. package/src/components/LimitOrderSettings/LimitOrderSettings.style.tsx +69 -0
  167. package/src/components/LimitOrderSettings/LimitOrderSettings.tsx +124 -0
  168. package/src/components/LimitPriceCard/LimitPriceCard.style.tsx +103 -0
  169. package/src/components/LimitPriceCard/LimitPriceCard.tsx +153 -0
  170. package/src/components/LimitPriceCard/LimitPricePresets.tsx +139 -0
  171. package/src/components/RouteCard/RouteProviderCard.tsx +113 -0
  172. package/src/components/Routes/LimitOrderRoutes.tsx +64 -0
  173. package/src/components/Routes/Routes.tsx +3 -2
  174. package/src/components/SettingsPanel/SettingsPanel.style.tsx +41 -0
  175. package/src/components/SettingsPanel/SettingsPanel.tsx +73 -0
  176. package/src/components/SettingsPanel/utils.ts +27 -0
  177. package/src/components/SwapButton/SwapButton.style.tsx +28 -0
  178. package/src/components/SwapButton/SwapButton.tsx +71 -0
  179. package/src/components/TokenPillButton/TokenPillButton.style.tsx +50 -0
  180. package/src/components/TokenPillButton/TokenPillButton.tsx +81 -0
  181. package/src/config/version.ts +1 -1
  182. package/src/hooks/useFilteredByTokenBalances.ts +7 -2
  183. package/src/hooks/useLimitMarketRate.ts +39 -0
  184. package/src/hooks/useLinkedLimitFields.ts +74 -0
  185. package/src/hooks/useRoutes.ts +45 -4
  186. package/src/hooks/useWideVariant.ts +8 -2
  187. package/src/pages/MainPage/LimitPageContent.tsx +18 -0
  188. package/src/pages/MainPage/MainPage.tsx +43 -30
  189. package/src/pages/MainPage/MainPageActions.tsx +35 -0
  190. package/src/pages/MainPage/ReviewButton.tsx +13 -1
  191. package/src/pages/RoutePriorityPage.tsx +55 -0
  192. package/src/pages/SettingsPage/RoutePrioritySettings.tsx +14 -38
  193. package/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx +176 -0
  194. package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx +32 -25
  195. package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx +14 -128
  196. package/src/pages/SettingsPage/ThemeSettings.tsx +4 -0
  197. package/src/stores/form/URLSearchParamsBuilder.tsx +25 -3
  198. package/src/stores/form/createFormStore.ts +7 -3
  199. package/src/stores/form/getDefaultValuesFromQueryString.ts +11 -0
  200. package/src/stores/form/types.ts +14 -1
  201. package/src/stores/form/useClearAmountFields.ts +1 -0
  202. package/src/stores/navigationTabs/types.ts +6 -0
  203. package/src/stores/navigationTabs/useNavigationTabsStore.tsx +8 -3
  204. package/src/stores/navigationTabs/utils.ts +67 -5
  205. package/src/types/events.ts +12 -0
  206. package/src/types/widget.ts +10 -0
  207. package/src/utils/format.ts +6 -2
  208. package/src/utils/limitOrder.ts +88 -0
  209. package/src/utils/navigationRoutes.ts +6 -0
  210. 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
- <CardTitle>{title}</CardTitle>
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
+ }
@@ -1,2 +1,2 @@
1
1
  export const name = '@lifi/widget'
2
- export const version = '4.1.0'
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: false,
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
+ }