@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
@@ -1,6 +1,24 @@
1
1
  import { getConfigItemSets, isFormItemAllowed } from "./item.js";
2
2
  //#region src/utils/token.ts
3
3
  /**
4
+ * Builds per-chain sets of lowercase token addresses from the
5
+ * `tokens.verified` config allowlist.
6
+ */
7
+ const getVerifiedTokensSets = (configTokens) => {
8
+ if (!configTokens?.verified?.length) return;
9
+ const sets = /* @__PURE__ */ new Map();
10
+ for (const token of configTokens.verified) {
11
+ const chainId = Number(token.chainId);
12
+ let addresses = sets.get(chainId);
13
+ if (!addresses) {
14
+ addresses = /* @__PURE__ */ new Set();
15
+ sets.set(chainId, addresses);
16
+ }
17
+ addresses.add(token.address.toLowerCase());
18
+ }
19
+ return sets;
20
+ };
21
+ /**
4
22
  * Merges verified tokens with search tokens.
5
23
  * Verified tokens take priority - search tokens are only added if they don't already exist.
6
24
  */
@@ -41,11 +59,28 @@ const filterAllowedTokens = (dataTokens, configTokens, chainsConfig, formType) =
41
59
  const allChainIds = Array.from(/* @__PURE__ */ new Set([...includedTokens.map((t) => t.chainId), ...Object.keys(dataTokens)])).map((chainId) => Number(chainId));
42
60
  const configChainIdsSet = getConfigItemSets(chainsConfig, (chainIds) => new Set(chainIds), formType);
43
61
  const allowedChainIds = configChainIdsSet ? allChainIds.filter((chainId) => isFormItemAllowed(chainId, configChainIdsSet, formType)) : allChainIds;
62
+ const verifiedTokensSets = getVerifiedTokensSets(configTokens);
44
63
  const allowedTokensByChain = {};
45
64
  for (const chainId of allowedChainIds) {
46
- const chainTokens = [...dataTokens[chainId], ...includedTokens.filter((t) => Number(t.chainId) === chainId)];
65
+ const chainIncludedTokens = includedTokens.filter((t) => Number(t.chainId) === chainId);
66
+ const includedAddresses = new Set(chainIncludedTokens.map((t) => t.address.toLowerCase()));
47
67
  const allowedAddresses = getConfigItemSets(configTokens, (tokens) => new Set(tokens.filter((t) => Number(t.chainId) === chainId).map((t) => t.address.toLowerCase())), formType);
48
- allowedTokensByChain[chainId] = chainTokens.filter((token) => isFormItemAllowed(token, allowedAddresses, formType, (t) => t.address.toLowerCase()));
68
+ const verifiedAddresses = verifiedTokensSets?.get(chainId);
69
+ const chainTokens = [...dataTokens[chainId] ?? [], ...chainIncludedTokens];
70
+ const filtered = [];
71
+ const seenAddresses = /* @__PURE__ */ new Set();
72
+ for (const token of chainTokens) {
73
+ const address = token.address.toLowerCase();
74
+ if (seenAddresses.has(address)) continue;
75
+ seenAddresses.add(address);
76
+ if (!isFormItemAllowed(token, allowedAddresses, formType, (t) => t.address.toLowerCase())) continue;
77
+ if (!token.verified && (includedAddresses.has(address) || verifiedAddresses?.has(address))) filtered.push({
78
+ ...token,
79
+ verified: true
80
+ });
81
+ else filtered.push(token);
82
+ }
83
+ allowedTokensByChain[chainId] = filtered;
49
84
  }
50
85
  return allowedTokensByChain;
51
86
  };
@@ -54,6 +89,6 @@ const getExecutionToToken = (route) => ({
54
89
  amount: BigInt(route.steps.at(-1)?.execution?.toAmount ?? route.steps.at(-1)?.estimate.toAmount ?? route.toAmount)
55
90
  });
56
91
  //#endregion
57
- export { filterAllowedTokens, getExecutionToToken, mergeVerifiedWithSearchTokens, updateTokenInCache };
92
+ export { filterAllowedTokens, getExecutionToToken, getVerifiedTokensSets, mergeVerifiedWithSearchTokens, updateTokenInCache };
58
93
 
59
94
  //# sourceMappingURL=token.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","names":[],"sources":["../../../src/utils/token.ts"],"sourcesContent":["import type {\n BaseToken,\n RouteExtended,\n Token,\n TokenAmount,\n TokenExtended,\n} from '@lifi/sdk'\nimport type { FormType } from '../stores/form/types.js'\nimport type { TokensByChain } from '../types/token.js'\nimport type { WidgetChains, WidgetTokens } from '../types/widget.js'\nimport { getConfigItemSets, isFormItemAllowed } from './item.js'\n\n/**\n * Merges verified tokens with search tokens.\n * Verified tokens take priority - search tokens are only added if they don't already exist.\n */\nexport const mergeVerifiedWithSearchTokens = (\n verifiedTokens?: TokensByChain,\n searchTokens?: TokensByChain\n): TokensByChain | undefined => {\n if (!verifiedTokens) {\n return searchTokens\n }\n if (!searchTokens) {\n return verifiedTokens\n }\n\n const result = { ...verifiedTokens }\n\n for (const [chainId, tokens] of Object.entries(searchTokens)) {\n const chainIdNum = Number(chainId)\n const existingTokens = result[chainIdNum] || []\n const existingAddresses = new Set(\n existingTokens.map((t) => t.address.toLowerCase())\n )\n\n const newTokens = tokens.filter(\n (t) => !existingAddresses.has(t.address.toLowerCase())\n )\n\n if (newTokens.length) {\n result[chainIdNum] = [...existingTokens, ...newTokens]\n }\n }\n\n return result\n}\n\n/**\n * Updates a token in the cache by chainId and address.\n * Returns a new cache object with the token updated, or the original if not found.\n */\nexport const updateTokenInCache = (\n data: TokensByChain | undefined,\n token: Token\n): TokensByChain | undefined => {\n if (!data) {\n return data\n }\n const chainTokens = data[token.chainId]\n if (!chainTokens) {\n return data\n }\n const index = chainTokens.findIndex((t) => t.address === token.address)\n if (index < 0) {\n return data\n }\n return {\n ...data,\n [token.chainId]: chainTokens.map((t, i) =>\n i === index ? { ...t, ...token } : t\n ),\n }\n}\n\nexport const filterAllowedTokens = (\n dataTokens: { [chainId: number]: TokenExtended[] } | undefined,\n configTokens?: WidgetTokens,\n chainsConfig?: WidgetChains,\n formType?: FormType\n): { [chainId: number]: TokenExtended[] } | undefined => {\n if (!dataTokens) {\n return\n }\n\n const includedTokens = configTokens?.include || []\n const allChainIds = Array.from(\n new Set([\n ...includedTokens.map((t) => t.chainId),\n ...Object.keys(dataTokens),\n ])\n ).map((chainId) => Number(chainId))\n\n const configChainIdsSet = getConfigItemSets(\n chainsConfig,\n (chainIds: number[]) => new Set(chainIds),\n formType\n )\n\n const allowedChainIds = configChainIdsSet\n ? allChainIds.filter((chainId) =>\n isFormItemAllowed(chainId, configChainIdsSet, formType)\n )\n : allChainIds\n\n const allowedTokensByChain: { [chainId: number]: TokenExtended[] } = {}\n for (const chainId of allowedChainIds) {\n const chainTokens = [\n ...dataTokens[chainId],\n ...includedTokens.filter((t) => Number(t.chainId) === chainId),\n ]\n\n const allowedAddresses = getConfigItemSets(\n configTokens,\n (tokens: BaseToken[]) =>\n new Set(\n tokens\n .filter((t) => Number(t.chainId) === chainId)\n .map((t) => t.address.toLowerCase())\n ),\n formType\n )\n\n const filtered = chainTokens.filter((token) =>\n isFormItemAllowed(token, allowedAddresses, formType, (t) =>\n t.address.toLowerCase()\n )\n )\n\n allowedTokensByChain[chainId] = filtered\n }\n\n return allowedTokensByChain\n}\n\nexport const getExecutionToToken = (route: RouteExtended): TokenAmount => ({\n ...(route.steps.at(-1)?.execution?.toToken ?? route.toToken),\n amount: BigInt(\n route.steps.at(-1)?.execution?.toAmount ??\n route.steps.at(-1)?.estimate.toAmount ??\n route.toAmount\n ),\n})\n"],"mappings":";;;;;;AAgBA,MAAa,iCACX,gBACA,iBAC8B;CAC9B,IAAI,CAAC,gBACH,OAAO;CAET,IAAI,CAAC,cACH,OAAO;CAGT,MAAM,SAAS,EAAE,GAAG,eAAe;CAEnC,KAAK,MAAM,CAAC,SAAS,WAAW,OAAO,QAAQ,YAAY,GAAG;EAC5D,MAAM,aAAa,OAAO,OAAO;EACjC,MAAM,iBAAiB,OAAO,eAAe,CAAC;EAC9C,MAAM,oBAAoB,IAAI,IAC5B,eAAe,KAAK,MAAM,EAAE,QAAQ,YAAY,CAAC,CACnD;EAEA,MAAM,YAAY,OAAO,QACtB,MAAM,CAAC,kBAAkB,IAAI,EAAE,QAAQ,YAAY,CAAC,CACvD;EAEA,IAAI,UAAU,QACZ,OAAO,cAAc,CAAC,GAAG,gBAAgB,GAAG,SAAS;CAEzD;CAEA,OAAO;AACT;;;;;AAMA,MAAa,sBACX,MACA,UAC8B;CAC9B,IAAI,CAAC,MACH,OAAO;CAET,MAAM,cAAc,KAAK,MAAM;CAC/B,IAAI,CAAC,aACH,OAAO;CAET,MAAM,QAAQ,YAAY,WAAW,MAAM,EAAE,YAAY,MAAM,OAAO;CACtE,IAAI,QAAQ,GACV,OAAO;CAET,OAAO;EACL,GAAG;GACF,MAAM,UAAU,YAAY,KAAK,GAAG,MACnC,MAAM,QAAQ;GAAE,GAAG;GAAG,GAAG;EAAM,IAAI,CACrC;CACF;AACF;AAEA,MAAa,uBACX,YACA,cACA,cACA,aACuD;CACvD,IAAI,CAAC,YACH;CAGF,MAAM,iBAAiB,cAAc,WAAW,CAAC;CACjD,MAAM,cAAc,MAAM,qBACxB,IAAI,IAAI,CACN,GAAG,eAAe,KAAK,MAAM,EAAE,OAAO,GACtC,GAAG,OAAO,KAAK,UAAU,CAC3B,CAAC,CACH,CAAC,CAAC,KAAK,YAAY,OAAO,OAAO,CAAC;CAElC,MAAM,oBAAoB,kBACxB,eACC,aAAuB,IAAI,IAAI,QAAQ,GACxC,QACF;CAEA,MAAM,kBAAkB,oBACpB,YAAY,QAAQ,YAClB,kBAAkB,SAAS,mBAAmB,QAAQ,CACxD,IACA;CAEJ,MAAM,uBAA+D,CAAC;CACtE,KAAK,MAAM,WAAW,iBAAiB;EACrC,MAAM,cAAc,CAClB,GAAG,WAAW,UACd,GAAG,eAAe,QAAQ,MAAM,OAAO,EAAE,OAAO,MAAM,OAAO,CAC/D;EAEA,MAAM,mBAAmB,kBACvB,eACC,WACC,IAAI,IACF,OACG,QAAQ,MAAM,OAAO,EAAE,OAAO,MAAM,OAAO,CAAC,CAC5C,KAAK,MAAM,EAAE,QAAQ,YAAY,CAAC,CACvC,GACF,QACF;EAQA,qBAAqB,WANJ,YAAY,QAAQ,UACnC,kBAAkB,OAAO,kBAAkB,WAAW,MACpD,EAAE,QAAQ,YAAY,CACxB,CAGqC;CACzC;CAEA,OAAO;AACT;AAEA,MAAa,uBAAuB,WAAuC;CACzE,GAAI,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,WAAW,WAAW,MAAM;CACpD,QAAQ,OACN,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,WAAW,YAC7B,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,SAAS,YAC7B,MAAM,QACV;AACF"}
1
+ {"version":3,"file":"token.js","names":[],"sources":["../../../src/utils/token.ts"],"sourcesContent":["import type { BaseToken, RouteExtended, Token, TokenAmount } from '@lifi/sdk'\nimport type { FormType } from '../stores/form/types.js'\nimport type { TokensByChain, TokenWithVerified } from '../types/token.js'\nimport type { WidgetChains, WidgetTokens } from '../types/widget.js'\nimport { getConfigItemSets, isFormItemAllowed } from './item.js'\n\n/**\n * Builds per-chain sets of lowercase token addresses from the\n * `tokens.verified` config allowlist.\n */\nexport const getVerifiedTokensSets = (\n configTokens?: WidgetTokens\n): Map<number, Set<string>> | undefined => {\n if (!configTokens?.verified?.length) {\n return undefined\n }\n const sets = new Map<number, Set<string>>()\n for (const token of configTokens.verified) {\n const chainId = Number(token.chainId)\n let addresses = sets.get(chainId)\n if (!addresses) {\n addresses = new Set()\n sets.set(chainId, addresses)\n }\n addresses.add(token.address.toLowerCase())\n }\n return sets\n}\n\n/**\n * Merges verified tokens with search tokens.\n * Verified tokens take priority - search tokens are only added if they don't already exist.\n */\nexport const mergeVerifiedWithSearchTokens = (\n verifiedTokens?: TokensByChain,\n searchTokens?: TokensByChain\n): TokensByChain | undefined => {\n if (!verifiedTokens) {\n return searchTokens\n }\n if (!searchTokens) {\n return verifiedTokens\n }\n\n const result = { ...verifiedTokens }\n\n for (const [chainId, tokens] of Object.entries(searchTokens)) {\n const chainIdNum = Number(chainId)\n const existingTokens = result[chainIdNum] || []\n const existingAddresses = new Set(\n existingTokens.map((t) => t.address.toLowerCase())\n )\n\n const newTokens = tokens.filter(\n (t) => !existingAddresses.has(t.address.toLowerCase())\n )\n\n if (newTokens.length) {\n result[chainIdNum] = [...existingTokens, ...newTokens]\n }\n }\n\n return result\n}\n\n/**\n * Updates a token in the cache by chainId and address.\n * Returns a new cache object with the token updated, or the original if not found.\n */\nexport const updateTokenInCache = (\n data: TokensByChain | undefined,\n token: Token\n): TokensByChain | undefined => {\n if (!data) {\n return data\n }\n const chainTokens = data[token.chainId]\n if (!chainTokens) {\n return data\n }\n const index = chainTokens.findIndex((t) => t.address === token.address)\n if (index < 0) {\n return data\n }\n return {\n ...data,\n [token.chainId]: chainTokens.map((t, i) =>\n i === index ? { ...t, ...token } : t\n ),\n }\n}\n\nexport const filterAllowedTokens = (\n dataTokens: TokensByChain | undefined,\n configTokens?: WidgetTokens,\n chainsConfig?: WidgetChains,\n formType?: FormType\n): TokensByChain | undefined => {\n if (!dataTokens) {\n return\n }\n\n const includedTokens = configTokens?.include || []\n const allChainIds = Array.from(\n new Set([\n ...includedTokens.map((t) => t.chainId),\n ...Object.keys(dataTokens),\n ])\n ).map((chainId) => Number(chainId))\n\n const configChainIdsSet = getConfigItemSets(\n chainsConfig,\n (chainIds: number[]) => new Set(chainIds),\n formType\n )\n\n const allowedChainIds = configChainIdsSet\n ? allChainIds.filter((chainId) =>\n isFormItemAllowed(chainId, configChainIdsSet, formType)\n )\n : allChainIds\n\n const verifiedTokensSets = getVerifiedTokensSets(configTokens)\n\n const allowedTokensByChain: TokensByChain = {}\n for (const chainId of allowedChainIds) {\n const chainIncludedTokens = includedTokens.filter(\n (t) => Number(t.chainId) === chainId\n )\n const includedAddresses = new Set(\n chainIncludedTokens.map((t) => t.address.toLowerCase())\n )\n\n const allowedAddresses = getConfigItemSets(\n configTokens,\n (tokens: BaseToken[]) =>\n new Set(\n tokens\n .filter((t) => Number(t.chainId) === chainId)\n .map((t) => t.address.toLowerCase())\n ),\n formType\n )\n\n const verifiedAddresses = verifiedTokensSets?.get(chainId)\n\n const chainTokens: TokenWithVerified[] = [\n ...(dataTokens[chainId] ?? []),\n ...chainIncludedTokens,\n ]\n\n const filtered: TokenWithVerified[] = []\n const seenAddresses = new Set<string>()\n for (const token of chainTokens) {\n const address = token.address.toLowerCase()\n // Data tokens come first and win over include duplicates,\n // keeping their extended data\n if (seenAddresses.has(address)) {\n continue\n }\n seenAddresses.add(address)\n if (\n !isFormItemAllowed(token, allowedAddresses, formType, (t) =>\n t.address.toLowerCase()\n )\n ) {\n continue\n }\n // Include and verified-allowlist tokens are explicitly set by the\n // integrator, mark them as verified\n if (\n !token.verified &&\n (includedAddresses.has(address) || verifiedAddresses?.has(address))\n ) {\n filtered.push({ ...token, verified: true })\n } else {\n filtered.push(token)\n }\n }\n\n allowedTokensByChain[chainId] = filtered\n }\n\n return allowedTokensByChain\n}\n\nexport const getExecutionToToken = (route: RouteExtended): TokenAmount => ({\n ...(route.steps.at(-1)?.execution?.toToken ?? route.toToken),\n amount: BigInt(\n route.steps.at(-1)?.execution?.toAmount ??\n route.steps.at(-1)?.estimate.toAmount ??\n route.toAmount\n ),\n})\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/widget",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/index.js",
@@ -34,22 +34,22 @@
34
34
  "dependencies": {
35
35
  "@emotion/react": "^11.14.0",
36
36
  "@emotion/styled": "^11.14.1",
37
- "@lifi/sdk": "^4.0.0",
38
- "@mui/icons-material": "^9.1.1",
39
- "@mui/material": "^9.1.1",
40
- "@mui/system": "^9.1.1",
41
- "@tanstack/react-router": "^1.170.16",
42
- "@tanstack/react-virtual": "^3.14.3",
37
+ "@lifi/sdk": "^4.1.0",
38
+ "@mui/icons-material": "^9.2.0",
39
+ "@mui/material": "^9.2.0",
40
+ "@mui/system": "^9.2.0",
41
+ "@tanstack/react-router": "^1.170.17",
42
+ "@tanstack/react-virtual": "^3.14.5",
43
43
  "eventemitter3": "^5.0.4",
44
- "i18next": "^26.3.1",
44
+ "i18next": "^26.3.4",
45
45
  "microdiff": "^1.5.0",
46
- "motion": "^12.38.0",
46
+ "motion": "^12.42.2",
47
47
  "react-i18next": "^17.0.8",
48
48
  "react-intersection-observer": "^10.0.3",
49
49
  "react-transition-group": "^4.4.5",
50
50
  "zustand": "^5.0.14",
51
- "@lifi/widget-provider": "4.1.0",
52
- "@lifi/wallet-management": "4.1.0"
51
+ "@lifi/wallet-management": "4.1.1",
52
+ "@lifi/widget-provider": "4.2.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@tanstack/react-query": ">=5.90.0",
@@ -12,6 +12,7 @@ import { NotFound } from './components/NotFound.js'
12
12
  import { ActivitiesPage } from './pages/ActivitiesPage/ActivitiesPage.js'
13
13
  import { LanguagesPage } from './pages/LanguagesPage.js'
14
14
  import { MainPage } from './pages/MainPage/MainPage.js'
15
+ import { RoutePriorityPage } from './pages/RoutePriorityPage.js'
15
16
  import { RoutesPage } from './pages/RoutesPage/RoutesPage.js'
16
17
  import { SelectChainPage } from './pages/SelectChainPage/SelectChainPage.js'
17
18
  import { SelectEnabledToolsPage } from './pages/SelectEnabledToolsPage.js'
@@ -22,6 +23,7 @@ import { RecentWalletsPage } from './pages/SendToWallet/RecentWalletsPage.js'
22
23
  import { SendToConfiguredWalletPage } from './pages/SendToWallet/SendToConfiguredWalletPage.js'
23
24
  import { SendToWalletPage } from './pages/SendToWallet/SendToWalletPage.js'
24
25
  import { SettingsPage } from './pages/SettingsPage/SettingsPage.js'
26
+ import { SlippagePage } from './pages/SettingsPage/SlippageSettings/SlippagePage.js'
25
27
  import { TransactionDetailsPage } from './pages/TransactionDetailsPage/TransactionDetailsPage.js'
26
28
  import { TransactionPage } from './pages/TransactionPage/TransactionPage.js'
27
29
  import { navigationRoutes } from './utils/navigationRoutes.js'
@@ -66,6 +68,18 @@ const settingsLanguagesRoute = createRoute({
66
68
  component: LanguagesPage,
67
69
  })
68
70
 
71
+ const settingsRoutePriorityRoute = createRoute({
72
+ getParentRoute: () => settingsLayoutRoute,
73
+ path: navigationRoutes.routePriority,
74
+ component: RoutePriorityPage,
75
+ })
76
+
77
+ const settingsSlippageRoute = createRoute({
78
+ getParentRoute: () => settingsLayoutRoute,
79
+ path: navigationRoutes.slippage,
80
+ component: SlippagePage,
81
+ })
82
+
69
83
  const fromTokenLayoutRoute = createRoute({
70
84
  getParentRoute: () => rootRoute,
71
85
  path: navigationRoutes.fromToken,
@@ -214,6 +228,8 @@ const routeTree = rootRoute.addChildren([
214
228
  settingsLayoutRoute.addChildren([
215
229
  settingsIndexRoute,
216
230
  settingsLanguagesRoute,
231
+ settingsRoutePriorityRoute,
232
+ settingsSlippageRoute,
217
233
  settingsBridgesRoute,
218
234
  settingsExchangesRoute,
219
235
  ]),
@@ -18,5 +18,5 @@ export const AmountInputButton: React.FC<
18
18
  fontWeight: 700,
19
19
  lineHeight: 1.3334,
20
20
  height: 'auto',
21
- borderRadius: theme.shape.borderRadiusSecondary,
21
+ borderRadius: theme.vars.shape.borderRadiusSecondary,
22
22
  }))
@@ -1,4 +1,5 @@
1
1
  import { formatUnits } from '@lifi/sdk'
2
+ import { useAccount } from '@lifi/wallet-management'
2
3
  import type React from 'react'
3
4
  import { type JSX, memo } from 'react'
4
5
  import { useTranslation } from 'react-i18next'
@@ -25,6 +26,9 @@ export const AmountInputEndAdornment: React.NamedExoticComponent<FormTypeProps>
25
26
  FormKeyHelper.getTokenKey(formType)
26
27
  )
27
28
 
29
+ const chain = getChainById(chainId)
30
+ const { account } = useAccount({ chainType: chain?.chainType })
31
+
28
32
  // We get gas recommendations for the source chain to make sure that after pressing the Max button
29
33
  // the user will have enough funds remaining to cover gas costs
30
34
  const { data } = useGasRecommendation(chainId)
@@ -35,7 +39,6 @@ export const AmountInputEndAdornment: React.NamedExoticComponent<FormTypeProps>
35
39
  if (!token?.amount) {
36
40
  return 0n
37
41
  }
38
- const chain = getChainById(chainId)
39
42
  let maxAmount = token.amount
40
43
  if (chain?.nativeToken.address === tokenAddress && data?.recommended) {
41
44
  const recommendedAmount = BigInt(data.recommended.amount)
@@ -73,7 +76,7 @@ export const AmountInputEndAdornment: React.NamedExoticComponent<FormTypeProps>
73
76
  }
74
77
  }
75
78
 
76
- if (formType !== 'from' || !token) {
79
+ if (formType !== 'from' || !token || !account?.isConnected) {
77
80
  return null
78
81
  }
79
82
 
@@ -0,0 +1,157 @@
1
+ import {
2
+ Box,
3
+ ButtonBase,
4
+ InputBase,
5
+ inputBaseClasses,
6
+ styled,
7
+ Typography,
8
+ } from '@mui/material'
9
+ import type React from 'react'
10
+ import type { FormType } from '../../stores/form/types.js'
11
+ import { InputCard } from '../Card/InputCard.js'
12
+
13
+ export const maxInputFontSize = 30
14
+ export const minInputFontSize = 20
15
+ export const amountHeight = 32
16
+ export const footerFontSize = 12
17
+
18
+ export const AmountCard: React.FC<
19
+ React.ComponentProps<typeof InputCard> & {
20
+ formType?: FormType
21
+ mask?: boolean
22
+ }
23
+ > = styled(InputCard, {
24
+ shouldForwardProp: (prop) => !['formType', 'mask'].includes(prop as string),
25
+ })<{ formType?: FormType; mask?: boolean }>(
26
+ ({ theme, formType, mask = true }) => {
27
+ const cardVariant = theme.components?.MuiCard?.defaultProps?.variant
28
+ // Notch faces the swap button between the stacked cards: bottom edge for the
29
+ // top (send) card, top edge for the bottom (receive) card.
30
+ const vertical = formType === 'to' ? '-4px' : 'calc(100% + 4px)'
31
+ return {
32
+ display: 'flex',
33
+ flexDirection: 'column',
34
+ gap: theme.spacing(1.5),
35
+ padding: theme.spacing(2),
36
+ ...(cardVariant !== 'outlined' &&
37
+ mask && {
38
+ mask: `radial-gradient(circle 22px at 50% ${vertical}, #fff0 96%, #fff) 100% 100% / 100% 100% no-repeat`,
39
+ }),
40
+ }
41
+ }
42
+ )
43
+
44
+ export const CardHeaderRow: React.FC<React.ComponentProps<typeof Box>> = styled(
45
+ Box
46
+ )(({ theme }) => ({
47
+ display: 'flex',
48
+ justifyContent: 'space-between',
49
+ alignItems: 'center',
50
+ padding: 0,
51
+ height: theme.spacing(2),
52
+ }))
53
+
54
+ export const CardBodyRow: React.FC<React.ComponentProps<typeof Box>> = styled(
55
+ Box
56
+ )(({ theme }) => ({
57
+ display: 'flex',
58
+ alignItems: 'center',
59
+ gap: theme.spacing(2),
60
+ }))
61
+
62
+ export const CardFooterRow: React.FC<React.ComponentProps<typeof Box>> = styled(
63
+ Box
64
+ )(({ theme }) => ({
65
+ display: 'flex',
66
+ justifyContent: 'space-between',
67
+ alignItems: 'center',
68
+ height: theme.spacing(2),
69
+ }))
70
+
71
+ export const LargeInput: React.FC<React.ComponentProps<typeof InputBase>> =
72
+ styled(InputBase)(({ theme }) => ({
73
+ fontSize: maxInputFontSize,
74
+ fontWeight: 700,
75
+ lineHeight: 1.4,
76
+ height: amountHeight,
77
+ flex: 1,
78
+ minWidth: 0,
79
+ boxShadow: 'none',
80
+ [`.${inputBaseClasses.input}`]: {
81
+ height: amountHeight,
82
+ padding: 0,
83
+ },
84
+ [`.${inputBaseClasses.input}::placeholder`]: {
85
+ color: theme.vars.palette.text.primary,
86
+ opacity: 0.5,
87
+ },
88
+ '& input[type="number"]::-webkit-outer-spin-button, & input[type="number"]::-webkit-inner-spin-button':
89
+ {
90
+ WebkitAppearance: 'none',
91
+ margin: 0,
92
+ },
93
+ '& input[type="number"]': {
94
+ MozAppearance: 'textfield',
95
+ },
96
+ [`&.${inputBaseClasses.disabled}`]: {
97
+ color: 'inherit',
98
+ },
99
+ [`.${inputBaseClasses.input}.${inputBaseClasses.disabled}`]: {
100
+ WebkitTextFillColor: 'unset',
101
+ },
102
+ }))
103
+
104
+ export const AmountDisplay: React.FC<React.ComponentProps<typeof Typography>> =
105
+ styled(Typography)(() => ({
106
+ fontSize: maxInputFontSize,
107
+ fontWeight: 700,
108
+ lineHeight: 1.4,
109
+ height: amountHeight,
110
+ display: 'flex',
111
+ alignItems: 'center',
112
+ flex: 1,
113
+ minWidth: 0,
114
+ overflow: 'visible',
115
+ whiteSpace: 'nowrap',
116
+ }))
117
+
118
+ export const FooterText: React.FC<React.ComponentProps<typeof Typography>> =
119
+ styled(Typography)(({ theme }) => ({
120
+ fontSize: footerFontSize,
121
+ fontWeight: 500,
122
+ lineHeight: 1,
123
+ color: theme.vars.palette.text.secondary,
124
+ }))
125
+
126
+ export const CardContainer: React.FC<React.ComponentProps<typeof Box>> = styled(
127
+ Box
128
+ )(() => ({
129
+ display: 'flex',
130
+ flexDirection: 'column',
131
+ position: 'relative',
132
+ }))
133
+
134
+ export const ToggleButton: React.FC<
135
+ React.ComponentProps<typeof ButtonBase> & { clickable?: boolean }
136
+ > = styled(ButtonBase, {
137
+ shouldForwardProp: (prop) => prop !== 'clickable',
138
+ })<{ clickable?: boolean }>(({ theme, clickable }) => ({
139
+ display: 'flex',
140
+ alignItems: 'center',
141
+ gap: theme.spacing(0.25),
142
+ borderRadius: theme.vars.shape.borderRadiusSecondary,
143
+ padding: theme.spacing(0.25, 0.5, 0.25, 0.75),
144
+ marginLeft: theme.spacing(-0.75),
145
+ backgroundColor: 'transparent',
146
+ ...(clickable
147
+ ? {
148
+ cursor: 'pointer',
149
+ '&:hover': {
150
+ backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,
151
+ },
152
+ }
153
+ : {
154
+ cursor: 'default',
155
+ pointerEvents: 'none',
156
+ }),
157
+ }))
@@ -0,0 +1,25 @@
1
+ import type { BoxProps } from '@mui/material'
2
+ import type React from 'react'
3
+ import type { JSX } from 'react'
4
+ import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
5
+ import { LimitPriceCard } from '../LimitPriceCard/LimitPriceCard.js'
6
+ import { SwapButton } from '../SwapButton/SwapButton.js'
7
+ import { CardContainer } from './AmountInputCard.style.js'
8
+ import { ReceiveAmountCard } from './ReceiveAmountCard.js'
9
+ import { SendAmountCard } from './SendAmountCard.js'
10
+
11
+ export const AmountInputCardPair: React.FC<BoxProps> = (props): JSX.Element => {
12
+ // Refuel's destination is fixed to native gas, so reversing from/to is
13
+ // meaningless — hide the swap button (and the notch mask it sits in).
14
+ const { mode } = useWidgetConfig()
15
+ const showSwapButton = mode !== 'refuel'
16
+
17
+ return (
18
+ <CardContainer {...props}>
19
+ {mode === 'limit' ? <LimitPriceCard sx={{ marginBottom: 1 }} /> : null}
20
+ <SendAmountCard mask={showSwapButton} />
21
+ <SwapButton sx={{ visibility: showSwapButton ? 'visible' : 'hidden' }} />
22
+ <ReceiveAmountCard mask={showSwapButton} />
23
+ </CardContainer>
24
+ )
25
+ }
@@ -0,0 +1,9 @@
1
+ import { Box, styled } from '@mui/material'
2
+ import type React from 'react'
3
+
4
+ export const BalanceContainer: React.FC<React.ComponentProps<typeof Box>> =
5
+ styled(Box)(({ theme }) => ({
6
+ display: 'flex',
7
+ alignItems: 'center',
8
+ gap: theme.spacing(0.5),
9
+ }))
@@ -0,0 +1,46 @@
1
+ import AccountBalanceWalletOutlinedIcon from '@mui/icons-material/AccountBalanceWalletOutlined'
2
+ import { Skeleton } from '@mui/material'
3
+ import { type JSX, memo } from 'react'
4
+ import { useTranslation } from 'react-i18next'
5
+ import { useTokenAddressBalance } from '../../hooks/useTokenAddressBalance.js'
6
+ import type { FormTypeProps } from '../../stores/form/types.js'
7
+ import { FormKeyHelper } from '../../stores/form/types.js'
8
+ import { useFieldValues } from '../../stores/form/useFieldValues.js'
9
+ import { formatTokenAmount } from '../../utils/format.js'
10
+ import { FooterText, footerFontSize } from './AmountInputCard.style.js'
11
+ import { BalanceContainer } from './BalanceDisplay.style.js'
12
+
13
+ export const BalanceDisplay: React.NamedExoticComponent<FormTypeProps> = memo(
14
+ ({ formType }: FormTypeProps): JSX.Element | null => {
15
+ const { t } = useTranslation()
16
+ const [chainId, tokenAddress] = useFieldValues(
17
+ FormKeyHelper.getChainKey(formType),
18
+ FormKeyHelper.getTokenKey(formType)
19
+ )
20
+
21
+ const { token, isLoading } = useTokenAddressBalance(chainId, tokenAddress)
22
+
23
+ if (isLoading && tokenAddress) {
24
+ return (
25
+ <Skeleton variant="text" width={72} sx={{ fontSize: footerFontSize }} />
26
+ )
27
+ }
28
+
29
+ if (!token?.amount) {
30
+ return null
31
+ }
32
+
33
+ const tokenAmount = formatTokenAmount(token.amount, token.decimals)
34
+
35
+ return (
36
+ <BalanceContainer>
37
+ <FooterText title={tokenAmount}>
38
+ {t('format.tokenAmount', { value: tokenAmount })}
39
+ </FooterText>
40
+ <AccountBalanceWalletOutlinedIcon
41
+ sx={{ fontSize: 16, color: 'text.secondary' }}
42
+ />
43
+ </BalanceContainer>
44
+ )
45
+ }
46
+ )
@@ -0,0 +1,55 @@
1
+ import SwapVertIcon from '@mui/icons-material/SwapVert'
2
+ import { type JSX, memo } from 'react'
3
+ import { useTranslation } from 'react-i18next'
4
+ import { useToken } from '../../hooks/useToken.js'
5
+ import type { FormTypeProps } from '../../stores/form/types.js'
6
+ import { FormKeyHelper } from '../../stores/form/types.js'
7
+ import { useFieldValues } from '../../stores/form/useFieldValues.js'
8
+ import { useInputModeStore } from '../../stores/inputMode/useInputModeStore.js'
9
+ import { formatTokenPrice } from '../../utils/format.js'
10
+ import { FooterText, ToggleButton } from './AmountInputCard.style.js'
11
+
12
+ export const FiatValueToggle: React.NamedExoticComponent<FormTypeProps> = memo(
13
+ ({ formType }: FormTypeProps): JSX.Element => {
14
+ const { t } = useTranslation()
15
+ const [chainId, tokenAddress, amount] = useFieldValues(
16
+ FormKeyHelper.getChainKey(formType),
17
+ FormKeyHelper.getTokenKey(formType),
18
+ FormKeyHelper.getAmountKey(formType)
19
+ )
20
+
21
+ const { token } = useToken(chainId, tokenAddress)
22
+ const { inputMode, toggleInputMode } = useInputModeStore()
23
+ const currentInputMode = inputMode[formType]
24
+
25
+ const handleToggle = (e: React.MouseEvent): void => {
26
+ e.stopPropagation()
27
+ toggleInputMode(formType)
28
+ }
29
+
30
+ const canToggle = !!token?.priceUSD
31
+
32
+ const displayValue =
33
+ currentInputMode === 'amount'
34
+ ? t('format.currency', {
35
+ value: formatTokenPrice(amount, token?.priceUSD, token?.decimals),
36
+ })
37
+ : t('format.tokenAmount', { value: amount || '0' })
38
+
39
+ return (
40
+ <ToggleButton
41
+ clickable={canToggle}
42
+ onClick={canToggle ? handleToggle : undefined}
43
+ aria-label={canToggle ? 'Toggle fiat value' : undefined}
44
+ >
45
+ <FooterText>{displayValue}</FooterText>
46
+ {currentInputMode === 'price' && token?.symbol ? (
47
+ <FooterText>{token.symbol}</FooterText>
48
+ ) : null}
49
+ {canToggle ? (
50
+ <SwapVertIcon sx={{ fontSize: 14, color: 'text.secondary' }} />
51
+ ) : null}
52
+ </ToggleButton>
53
+ )
54
+ }
55
+ )
@@ -0,0 +1,60 @@
1
+ import { Box, Button, styled } from '@mui/material'
2
+ import { cardClasses } from '@mui/material/Card'
3
+ import type React from 'react'
4
+
5
+ const chipCount = 4
6
+ const staggerStepMs = 25
7
+
8
+ const revealStagger = Object.fromEntries(
9
+ Array.from({ length: chipCount }, (_, index) => [
10
+ `&[data-delay="${index}"]`,
11
+ {
12
+ [`.${cardClasses.root}:hover &`]: {
13
+ opacity: 1,
14
+ transform: 'scale(1) translateX(0)',
15
+ pointerEvents: 'auto',
16
+ transitionDelay: `${(chipCount - 1 - index) * staggerStepMs}ms`,
17
+ },
18
+ [`.${cardClasses.root}:not(:hover) &`]: {
19
+ transitionDelay: `${index * staggerStepMs}ms`,
20
+ },
21
+ },
22
+ ])
23
+ )
24
+
25
+ export const ChipContainer: React.FC<React.ComponentProps<typeof Box>> = styled(
26
+ Box
27
+ )(({ theme }) => ({
28
+ display: 'flex',
29
+ gap: theme.spacing(0.5),
30
+ }))
31
+
32
+ export const Chip: React.FC<React.ComponentProps<typeof Button>> = styled(
33
+ Button
34
+ )(({ theme }) => ({
35
+ minWidth: 0,
36
+ padding: theme.spacing(0.5, 1.5),
37
+ fontSize: 12,
38
+ fontWeight: 700,
39
+ lineHeight: 1.3334,
40
+ backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,
41
+ color: theme.vars.palette.text.primary,
42
+ transition:
43
+ 'opacity 200ms cubic-bezier(0.4, 0, 0.2, 1), transform 200ms cubic-bezier(0.4, 0, 0.2, 1), background-color 200ms ease',
44
+ '&:hover': {
45
+ backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)`,
46
+ },
47
+ // Reveal-on-hover — only on devices that support hover. On touch there is no
48
+ // hover, so the chips stay permanently visible (the default above) instead of
49
+ // becoming an unreachable dead row.
50
+ '@media (hover: hover)': {
51
+ opacity: 0,
52
+ transform: 'scale(0.85) translateX(10px)',
53
+ pointerEvents: 'none',
54
+ ...revealStagger,
55
+ '@media (prefers-reduced-motion: reduce)': {
56
+ transform: 'none !important',
57
+ transitionDelay: '0ms !important',
58
+ },
59
+ },
60
+ }))