@lifi/widget 4.5.0 → 4.6.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 (199) hide show
  1. package/dist/esm/AppDrawer.js +2 -1
  2. package/dist/esm/AppDrawer.js.map +1 -1
  3. package/dist/esm/components/AmountInput/AmountInput.js +13 -7
  4. package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
  5. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +2 -13
  6. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
  7. package/dist/esm/components/AmountInputCard/PercentageChips.js +2 -13
  8. package/dist/esm/components/AmountInputCard/PercentageChips.js.map +1 -1
  9. package/dist/esm/components/AmountInputCard/PercentageChips.style.js +1 -1
  10. package/dist/esm/components/AmountInputCard/PercentageChips.style.js.map +1 -1
  11. package/dist/esm/components/AmountInputCard/SendAmountCard.js +13 -6
  12. package/dist/esm/components/AmountInputCard/SendAmountCard.js.map +1 -1
  13. package/dist/esm/components/Chains/AllChainsAvatar.js +7 -1
  14. package/dist/esm/components/Chains/AllChainsAvatar.js.map +1 -1
  15. package/dist/esm/components/Dialog/Dialog.js +2 -1
  16. package/dist/esm/components/Dialog/Dialog.js.map +1 -1
  17. package/dist/esm/components/Expansion/Expansion.js +4 -3
  18. package/dist/esm/components/Expansion/Expansion.js.map +1 -1
  19. package/dist/esm/components/Header/SettingsButton.js +2 -1
  20. package/dist/esm/components/Header/SettingsButton.js.map +1 -1
  21. package/dist/esm/components/IconCircle/IconCircle.js +2 -1
  22. package/dist/esm/components/IconCircle/IconCircle.js.map +1 -1
  23. package/dist/esm/components/Messages/UnsupportedToAddressMessage.js +27 -0
  24. package/dist/esm/components/Messages/UnsupportedToAddressMessage.js.map +1 -0
  25. package/dist/esm/components/Messages/WarningMessages.d.ts.map +1 -1
  26. package/dist/esm/components/Messages/WarningMessages.js +2 -0
  27. package/dist/esm/components/Messages/WarningMessages.js.map +1 -1
  28. package/dist/esm/components/Messages/useMessageQueue.js +6 -1
  29. package/dist/esm/components/Messages/useMessageQueue.js.map +1 -1
  30. package/dist/esm/components/RouteCard/RouteDetails.js +2 -1
  31. package/dist/esm/components/RouteCard/RouteDetails.js.map +1 -1
  32. package/dist/esm/components/Routes/LimitOrderRoutes.js +2 -2
  33. package/dist/esm/components/Routes/LimitOrderRoutes.js.map +1 -1
  34. package/dist/esm/components/Routes/RoutesContent.js +2 -2
  35. package/dist/esm/components/Routes/RoutesContent.js.map +1 -1
  36. package/dist/esm/components/SendToWallet/SendToWalletExpandButton.js +2 -2
  37. package/dist/esm/components/SendToWallet/SendToWalletExpandButton.js.map +1 -1
  38. package/dist/esm/components/Skeleton/WidgetSkeleton.js +2 -1
  39. package/dist/esm/components/Skeleton/WidgetSkeleton.js.map +1 -1
  40. package/dist/esm/components/Step/StepActions.js +18 -16
  41. package/dist/esm/components/Step/StepActions.js.map +1 -1
  42. package/dist/esm/components/StepActions/StepActionRow.js +2 -1
  43. package/dist/esm/components/StepActions/StepActionRow.js.map +1 -1
  44. package/dist/esm/components/Switch.js +1 -1
  45. package/dist/esm/components/Switch.js.map +1 -1
  46. package/dist/esm/components/Tabs/Tabs.style.js +4 -2
  47. package/dist/esm/components/Tabs/Tabs.style.js.map +1 -1
  48. package/dist/esm/components/TokenList/TokenListItem.js +46 -8
  49. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  50. package/dist/esm/components/TokenList/VirtualizedTokenList.js +22 -9
  51. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  52. package/dist/esm/components/TokenRate/TokenRate.js +2 -1
  53. package/dist/esm/components/TokenRate/TokenRate.js.map +1 -1
  54. package/dist/esm/components/TransactionCard/ActiveTransactionCard.js +22 -21
  55. package/dist/esm/components/TransactionCard/ActiveTransactionCard.js.map +1 -1
  56. package/dist/esm/config/version.d.ts +1 -1
  57. package/dist/esm/config/version.js +1 -1
  58. package/dist/esm/config/version.js.map +1 -1
  59. package/dist/esm/hooks/timer/time.js +2 -2
  60. package/dist/esm/hooks/timer/time.js.map +1 -1
  61. package/dist/esm/hooks/useAvailableChains.d.ts.map +1 -1
  62. package/dist/esm/hooks/useAvailableChains.js +2 -1
  63. package/dist/esm/hooks/useAvailableChains.js.map +1 -1
  64. package/dist/esm/hooks/useExplorer.d.ts.map +1 -1
  65. package/dist/esm/hooks/useExplorer.js +4 -0
  66. package/dist/esm/hooks/useExplorer.js.map +1 -1
  67. package/dist/esm/hooks/useFlaggedTokenGuard.js +28 -0
  68. package/dist/esm/hooks/useFlaggedTokenGuard.js.map +1 -0
  69. package/dist/esm/hooks/useGasSufficiency.js +3 -1
  70. package/dist/esm/hooks/useGasSufficiency.js.map +1 -1
  71. package/dist/esm/hooks/useMaxSendAmount.js +15 -0
  72. package/dist/esm/hooks/useMaxSendAmount.js.map +1 -0
  73. package/dist/esm/hooks/usePacedRouteExecution.js +3 -2
  74. package/dist/esm/hooks/usePacedRouteExecution.js.map +1 -1
  75. package/dist/esm/hooks/useRouteExecutionMessage.js +3 -2
  76. package/dist/esm/hooks/useRouteExecutionMessage.js.map +1 -1
  77. package/dist/esm/hooks/useRoutes.d.ts.map +1 -1
  78. package/dist/esm/hooks/useRoutes.js +3 -1
  79. package/dist/esm/hooks/useRoutes.js.map +1 -1
  80. package/dist/esm/hooks/useToAddressRequirements.d.ts +2 -0
  81. package/dist/esm/hooks/useToAddressRequirements.d.ts.map +1 -1
  82. package/dist/esm/hooks/useToAddressRequirements.js +7 -1
  83. package/dist/esm/hooks/useToAddressRequirements.js.map +1 -1
  84. package/dist/esm/hooks/useTokenBalances.d.ts.map +1 -1
  85. package/dist/esm/hooks/useTokenBalances.js +7 -2
  86. package/dist/esm/hooks/useTokenBalances.js.map +1 -1
  87. package/dist/esm/hooks/useTokenBalancesQueries.js +29 -28
  88. package/dist/esm/hooks/useTokenBalancesQueries.js.map +1 -1
  89. package/dist/esm/hooks/useTokenSearch.js +3 -5
  90. package/dist/esm/hooks/useTokenSearch.js.map +1 -1
  91. package/dist/esm/hooks/useTokens.js +14 -10
  92. package/dist/esm/hooks/useTokens.js.map +1 -1
  93. package/dist/esm/hooks/useWideVariant.js +2 -1
  94. package/dist/esm/hooks/useWideVariant.js.map +1 -1
  95. package/dist/esm/i18n/bn.json +3 -1
  96. package/dist/esm/i18n/de.json +3 -1
  97. package/dist/esm/i18n/en.json +12 -3
  98. package/dist/esm/i18n/es.json +3 -1
  99. package/dist/esm/i18n/fr.json +3 -1
  100. package/dist/esm/i18n/hi.json +3 -1
  101. package/dist/esm/i18n/id.json +3 -1
  102. package/dist/esm/i18n/it.json +3 -1
  103. package/dist/esm/i18n/ja.json +3 -1
  104. package/dist/esm/i18n/ko.json +3 -1
  105. package/dist/esm/i18n/pl.json +3 -1
  106. package/dist/esm/i18n/pt.json +3 -1
  107. package/dist/esm/i18n/th.json +3 -1
  108. package/dist/esm/i18n/tr.json +3 -1
  109. package/dist/esm/i18n/uk.json +3 -1
  110. package/dist/esm/i18n/vi.json +3 -1
  111. package/dist/esm/i18n/zh.json +3 -1
  112. package/dist/esm/pages/MainPage/MainPage.js +2 -1
  113. package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
  114. package/dist/esm/pages/MainPage/ReviewButton.js +2 -2
  115. package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
  116. package/dist/esm/pages/RoutesPage/RoutesPage.js +2 -2
  117. package/dist/esm/pages/RoutesPage/RoutesPage.js.map +1 -1
  118. package/dist/esm/pages/SelectChainPage/SelectChainPage.js +13 -12
  119. package/dist/esm/pages/SelectChainPage/SelectChainPage.js.map +1 -1
  120. package/dist/esm/pages/SelectEnabledToolsPage.js +8 -5
  121. package/dist/esm/pages/SelectEnabledToolsPage.js.map +1 -1
  122. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js +2 -1
  123. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js.map +1 -1
  124. package/dist/esm/pages/SettingsPage/SmallBalanceFilterSettings.js +2 -1
  125. package/dist/esm/pages/SettingsPage/SmallBalanceFilterSettings.js.map +1 -1
  126. package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.js +2 -1
  127. package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.js.map +1 -1
  128. package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js +2 -1
  129. package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js.map +1 -1
  130. package/dist/esm/pages/TransactionPage/StartTransactionButton.js +4 -3
  131. package/dist/esm/pages/TransactionPage/StartTransactionButton.js.map +1 -1
  132. package/dist/esm/pages/TransactionPage/TokenVerificationBottomSheet.js +64 -0
  133. package/dist/esm/pages/TransactionPage/TokenVerificationBottomSheet.js.map +1 -0
  134. package/dist/esm/pages/TransactionPage/TransactionExecutionContent.js +19 -16
  135. package/dist/esm/pages/TransactionPage/TransactionExecutionContent.js.map +1 -1
  136. package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js +3 -1
  137. package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js.map +1 -1
  138. package/dist/esm/pages/TransactionPage/TransactionReview.js +24 -2
  139. package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
  140. package/dist/esm/providers/I18nProvider/I18nProvider.js +37 -36
  141. package/dist/esm/providers/I18nProvider/I18nProvider.js.map +1 -1
  142. package/dist/esm/providers/ThemeProvider/ThemeProvider.js +2 -1
  143. package/dist/esm/providers/ThemeProvider/ThemeProvider.js.map +1 -1
  144. package/dist/esm/providers/WalletProvider/WalletProvider.js +10 -9
  145. package/dist/esm/providers/WalletProvider/WalletProvider.js.map +1 -1
  146. package/dist/esm/providers/WalletProvider/useExternalWalletProvider.js +7 -3
  147. package/dist/esm/providers/WalletProvider/useExternalWalletProvider.js.map +1 -1
  148. package/dist/esm/stores/routes/useRouteExecutionIndicator.js +4 -3
  149. package/dist/esm/stores/routes/useRouteExecutionIndicator.js.map +1 -1
  150. package/dist/esm/types/token.d.ts +4 -1
  151. package/dist/esm/types/token.d.ts.map +1 -1
  152. package/dist/esm/utils/compactNumberFormatter.js +1 -3
  153. package/dist/esm/utils/compactNumberFormatter.js.map +1 -1
  154. package/dist/esm/utils/customReceiver.js +7 -0
  155. package/dist/esm/utils/customReceiver.js.map +1 -0
  156. package/dist/esm/utils/gas.js +24 -0
  157. package/dist/esm/utils/gas.js.map +1 -0
  158. package/dist/esm/utils/getErrorMessage.js +0 -1
  159. package/dist/esm/utils/getErrorMessage.js.map +1 -1
  160. package/dist/esm/utils/limitOrder.js +1 -1
  161. package/dist/esm/utils/limitOrder.js.map +1 -1
  162. package/dist/esm/utils/token.js +21 -4
  163. package/dist/esm/utils/token.js.map +1 -1
  164. package/dist/esm/utils/tokenList.js +38 -21
  165. package/dist/esm/utils/tokenList.js.map +1 -1
  166. package/package.json +15 -15
  167. package/src/components/AmountInput/AmountInputEndAdornment.tsx +2 -21
  168. package/src/components/AmountInputCard/PercentageChips.tsx +2 -18
  169. package/src/components/Chains/AllChainsAvatar.tsx +7 -1
  170. package/src/components/Messages/UnsupportedToAddressMessage.tsx +24 -0
  171. package/src/components/Messages/WarningMessages.tsx +3 -0
  172. package/src/components/Messages/useMessageQueue.ts +9 -0
  173. package/src/components/Routes/LimitOrderRoutes.tsx +5 -2
  174. package/src/components/Routes/RoutesContent.tsx +5 -2
  175. package/src/components/SendToWallet/SendToWalletExpandButton.tsx +2 -1
  176. package/src/components/TokenList/TokenListItem.tsx +68 -9
  177. package/src/components/TokenList/VirtualizedTokenList.tsx +33 -9
  178. package/src/components/TokenList/types.ts +4 -0
  179. package/src/config/version.ts +1 -1
  180. package/src/hooks/useAvailableChains.ts +1 -0
  181. package/src/hooks/useExplorer.ts +2 -0
  182. package/src/hooks/useFlaggedTokenGuard.ts +33 -0
  183. package/src/hooks/useGasSufficiency.ts +9 -1
  184. package/src/hooks/useMaxSendAmount.ts +21 -0
  185. package/src/hooks/useRoutes.ts +7 -0
  186. package/src/hooks/useToAddressRequirements.ts +17 -1
  187. package/src/hooks/useTokenBalances.ts +18 -2
  188. package/src/hooks/useTokenSearch.ts +1 -5
  189. package/src/hooks/useTokens.ts +28 -13
  190. package/src/pages/MainPage/ReviewButton.tsx +6 -2
  191. package/src/pages/RoutesPage/RoutesPage.tsx +5 -2
  192. package/src/pages/TransactionPage/TokenVerificationBottomSheet.tsx +93 -0
  193. package/src/pages/TransactionPage/TransactionReview.tsx +28 -1
  194. package/src/providers/WalletProvider/useExternalWalletProvider.ts +9 -1
  195. package/src/types/token.ts +6 -3
  196. package/src/utils/customReceiver.ts +7 -0
  197. package/src/utils/gas.ts +41 -0
  198. package/src/utils/token.ts +53 -9
  199. package/src/utils/tokenList.ts +28 -0
@@ -1,4 +1,5 @@
1
1
  import { formatTokenAmount, formatTokenPrice } from "../../utils/format.js";
2
+ import { getTokenVerificationProvider } from "../../utils/token.js";
2
3
  import { TokenAvatar } from "../Avatar/TokenAvatar.js";
3
4
  import { shortenAddress } from "../../utils/wallet.js";
4
5
  import { IconButton as IconButton$1, ListItem as ListItem$1 } from "./TokenList.style.js";
@@ -11,9 +12,10 @@ import { Avatar, Box, ListItemAvatar, ListItemText, Skeleton, Slide, Tooltip, Ty
11
12
  import { jsx, jsxs } from "react/jsx-runtime";
12
13
  import { useTranslation } from "react-i18next";
13
14
  import InfoOutlined from "@mui/icons-material/InfoOutlined";
14
- import WarningRounded from "@mui/icons-material/WarningRounded";
15
+ import ReportRoundedIcon from "@mui/icons-material/ReportRounded";
16
+ import VerifiedIcon from "@mui/icons-material/Verified";
15
17
  //#region src/components/TokenList/TokenListItem.tsx
16
- const TokenListItem = memo(({ onClick, size, start, token, chain, isBalanceLoading, startAdornment, endAdornment, selected, onShowTokenDetails }) => {
18
+ const TokenListItem = memo(({ onClick, size, start, token, chain, chainName, isBalanceLoading, startAdornment, endAdornment, selected, onShowTokenDetails }) => {
17
19
  return /* @__PURE__ */ jsxs(ListItem$1, {
18
20
  style: {
19
21
  height: `${size}px`,
@@ -27,6 +29,7 @@ const TokenListItem = memo(({ onClick, size, start, token, chain, isBalanceLoadi
27
29
  isBalanceLoading,
28
30
  onClick,
29
31
  chain,
32
+ chainName,
30
33
  selected,
31
34
  onShowTokenDetails
32
35
  }),
@@ -56,7 +59,7 @@ const OpenTokenDetailsButton = ({ tokenAddress, withoutContractAddress, chainId,
56
59
  children: /* @__PURE__ */ jsx(InfoOutlined, {})
57
60
  });
58
61
  };
59
- const TokenListItemButton = memo(({ onClick, token, chain, isBalanceLoading, selected, onShowTokenDetails }) => {
62
+ const TokenListItemButton = memo(({ onClick, token, chain, chainName, isBalanceLoading, selected, onShowTokenDetails }) => {
60
63
  const { t } = useTranslation();
61
64
  const container = useRef(null);
62
65
  const timeoutId = useRef(void 0);
@@ -81,11 +84,46 @@ const TokenListItemButton = memo(({ onClick, token, chain, isBalanceLoading, sel
81
84
  token.priceUSD,
82
85
  token.decimals
83
86
  ]);
87
+ const longPressEvents = useLongPress(() => onShowTokenDetails(token.address, withoutContractAddress, token.chainId));
88
+ let verificationBadge;
89
+ if (token.native) verificationBadge = {
90
+ Icon: VerifiedIcon,
91
+ color: "info.main",
92
+ title: chainName ? t("tooltip.tokenNativeOnChain", {
93
+ tokenSymbol: token.symbol,
94
+ chainName
95
+ }) : t("tooltip.tokenNative", { tokenSymbol: token.symbol })
96
+ };
97
+ else if (token.verificationStatus === "flagged") verificationBadge = {
98
+ Icon: ReportRoundedIcon,
99
+ color: "error.main",
100
+ title: t("warning.message.tokenFlagged", { tokenSymbol: token.symbol })
101
+ };
102
+ else if (token.verificationStatus === "verified") {
103
+ const provider = getTokenVerificationProvider(token);
104
+ verificationBadge = {
105
+ Icon: VerifiedIcon,
106
+ color: "success.main",
107
+ title: provider ? t("tooltip.tokenVerifiedByProvider", {
108
+ tokenSymbol: token.symbol,
109
+ provider
110
+ }) : t("tooltip.tokenVerified", { tokenSymbol: token.symbol })
111
+ };
112
+ } else if (token.verificationStatus === "unverified" && !token.listed && !token.verified) verificationBadge = {
113
+ Icon: ReportRoundedIcon,
114
+ color: "warning.main",
115
+ title: t("warning.message.tokenUnverifiedByProvider", { tokenSymbol: token.symbol })
116
+ };
117
+ else if (!token.verificationStatus && !token.listed && !token.verified) verificationBadge = {
118
+ Icon: ReportRoundedIcon,
119
+ color: "warning.main",
120
+ title: t("warning.message.tokenUnverified")
121
+ };
84
122
  return /* @__PURE__ */ jsxs(ListItemButton$1, {
85
123
  onClick: handleClick,
86
124
  onMouseEnter,
87
125
  onMouseLeave,
88
- ...useLongPress(() => onShowTokenDetails(token.address, withoutContractAddress, token.chainId)),
126
+ ...longPressEvents,
89
127
  dense: true,
90
128
  selected,
91
129
  sx: {
@@ -106,14 +144,14 @@ const TokenListItemButton = memo(({ onClick, token, chain, isBalanceLoading, sel
106
144
  alignItems: "center",
107
145
  gap: .5
108
146
  },
109
- children: [token.symbol, !token.verified && /* @__PURE__ */ jsx(Tooltip, {
110
- title: t("warning.message.unverifiedToken"),
147
+ children: [token.symbol, verificationBadge && /* @__PURE__ */ jsx(Tooltip, {
148
+ title: verificationBadge.title,
111
149
  placement: "top",
112
150
  arrow: true,
113
- children: /* @__PURE__ */ jsx(WarningRounded, { sx: {
151
+ children: /* @__PURE__ */ jsx(verificationBadge.Icon, { sx: {
114
152
  display: "flex",
115
153
  fontSize: 16,
116
- color: "warning.main",
154
+ color: verificationBadge.color,
117
155
  cursor: "help"
118
156
  } })
119
157
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"TokenListItem.js","names":["ListItem","IconButton","InfoOutlinedIcon","ListItemButton"],"sources":["../../../../src/components/TokenList/TokenListItem.tsx"],"sourcesContent":["import type { StaticToken } from '@lifi/sdk'\nimport { ChainType } from '@lifi/sdk'\nimport InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'\nimport WarningRounded from '@mui/icons-material/WarningRounded'\nimport {\n Avatar,\n Box,\n ListItemAvatar,\n ListItemText,\n listItemSecondaryActionClasses,\n Skeleton,\n Slide,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport type { JSX, MouseEventHandler } from 'react'\nimport { memo, useMemo, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useLongPress } from '../../hooks/useLongPress.js'\nimport { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'\nimport { shortenAddress } from '../../utils/wallet.js'\nimport { TokenAvatar } from '../Avatar/TokenAvatar.js'\nimport { ListItemButton } from '../ListItem/ListItemButton.js'\nimport { PinTokenButton } from './PinTokenButton.js'\nimport { IconButton, ListItem } from './TokenList.style.js'\nimport type {\n TokenListItemAvatarProps,\n TokenListItemButtonProps,\n TokenListItemProps,\n} from './types.js'\n\nexport const TokenListItem: React.FC<TokenListItemProps> = memo(\n ({\n onClick,\n size,\n start,\n token,\n chain,\n isBalanceLoading,\n startAdornment,\n endAdornment,\n selected,\n onShowTokenDetails,\n }) => {\n return (\n <ListItem\n style={{\n height: `${size}px`,\n transform: `translateY(${start}px)`,\n padding: 0,\n }}\n >\n {startAdornment}\n <TokenListItemButton\n token={token}\n isBalanceLoading={isBalanceLoading}\n onClick={onClick}\n chain={chain}\n selected={selected}\n onShowTokenDetails={onShowTokenDetails}\n />\n {endAdornment}\n </ListItem>\n )\n }\n)\n\nconst TokenListItemAvatar = memo(({ token }: TokenListItemAvatarProps) => {\n const [isImageLoading, setIsImageLoading] = useState(true)\n\n return (\n <Avatar\n src={token.logoURI}\n alt={token.symbol}\n sx={(theme) =>\n isImageLoading ? { bgcolor: theme.vars.palette.grey[300] } : null\n }\n onLoad={() => setIsImageLoading(false)}\n >\n {token.symbol?.[0]}\n </Avatar>\n )\n})\n\ninterface OpenTokenDetailsButtonProps {\n tokenAddress: string | undefined\n withoutContractAddress: boolean\n chainId: number\n onClick: (\n tokenAddress: string,\n withoutContractAddress: boolean,\n chainId: number\n ) => void\n}\n\nconst OpenTokenDetailsButton = ({\n tokenAddress,\n withoutContractAddress,\n chainId,\n onClick,\n}: OpenTokenDetailsButtonProps) => {\n if (!tokenAddress) {\n return null\n }\n return (\n <IconButton\n size=\"small\"\n onClick={(e) => {\n e.stopPropagation()\n e.currentTarget.blur() // Remove focus to prevent accessibility issues when opening drawer\n onClick(tokenAddress, withoutContractAddress, chainId)\n }}\n >\n <InfoOutlinedIcon />\n </IconButton>\n )\n}\n\nconst TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(\n ({\n onClick,\n token,\n chain,\n isBalanceLoading,\n selected,\n onShowTokenDetails,\n }) => {\n const { t } = useTranslation()\n const container = useRef(null)\n const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined)\n const [showAddress, setShowAddress] = useState(false)\n\n const withoutContractAddress = chain?.chainType === ChainType.UTXO\n\n const onMouseEnter = () => {\n timeoutId.current = setTimeout(() => {\n if (token.address) {\n setShowAddress(true)\n }\n }, 350)\n }\n\n const onMouseLeave = () => {\n clearTimeout(timeoutId.current)\n if (showAddress) {\n setShowAddress(false)\n }\n }\n\n const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {\n e.stopPropagation()\n onClick?.(token.address, token.chainId)\n }\n\n const tokenAmount = useMemo(\n () => formatTokenAmount(token.amount, token.decimals),\n [token.amount, token.decimals]\n )\n\n const tokenPrice = useMemo(\n () => formatTokenPrice(token.amount, token.priceUSD, token.decimals),\n [token.amount, token.priceUSD, token.decimals]\n )\n\n const longPressEvents = useLongPress(() =>\n onShowTokenDetails(token.address, withoutContractAddress, token.chainId)\n )\n\n return (\n <ListItemButton\n onClick={handleClick}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n {...longPressEvents}\n dense\n selected={selected}\n sx={{\n height: 60,\n marginBottom: '4px',\n }}\n >\n <ListItemAvatar>\n {chain ? (\n <TokenAvatar\n token={token as StaticToken}\n chain={chain}\n tokenAvatarSize={40}\n chainAvatarSize={16}\n />\n ) : (\n <TokenListItemAvatar token={token} />\n )}\n </ListItemAvatar>\n <ListItemText\n primary={\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {token.symbol}\n {!token.verified && (\n <Tooltip\n title={t('warning.message.unverifiedToken')}\n placement=\"top\"\n arrow\n >\n <WarningRounded\n sx={{\n display: 'flex',\n fontSize: 16,\n color: 'warning.main',\n cursor: 'help',\n }}\n />\n </Tooltip>\n )}\n </Box>\n }\n slotProps={{\n secondary: {\n component: 'div',\n },\n }}\n secondary={\n withoutContractAddress ? (\n <Box\n ref={container}\n sx={{\n height: 20,\n display: 'flex',\n }}\n >\n <Box\n sx={{\n pt: 0.25,\n }}\n >\n {token.name}\n </Box>\n <Box\n sx={{\n position: 'relative',\n }}\n >\n <Slide\n direction=\"up\"\n in={showAddress}\n container={container.current}\n style={{\n position: 'absolute',\n }}\n appear={false}\n mountOnEnter\n >\n <Box>\n <OpenTokenDetailsButton\n tokenAddress={token.address}\n withoutContractAddress={withoutContractAddress}\n chainId={token.chainId}\n onClick={onShowTokenDetails}\n />\n <PinTokenButton\n chainId={token.chainId}\n tokenAddress={token.address}\n />\n </Box>\n </Slide>\n </Box>\n </Box>\n ) : (\n <Box\n ref={container}\n sx={{\n position: 'relative',\n height: 20,\n }}\n >\n <Slide\n direction=\"down\"\n in={!showAddress}\n container={container.current}\n style={{\n position: 'absolute',\n }}\n appear={false}\n >\n <Box\n sx={{\n pt: 0.25,\n }}\n >\n {token.name}\n </Box>\n </Slide>\n <Slide\n direction=\"up\"\n in={showAddress}\n container={container.current}\n style={{\n position: 'absolute',\n }}\n appear={false}\n mountOnEnter\n >\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n gap: 0.5,\n }}\n >\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n pt: 0.125,\n }}\n >\n {shortenAddress(token.address)}\n </Box>\n <Box>\n <OpenTokenDetailsButton\n tokenAddress={token.address}\n withoutContractAddress={withoutContractAddress}\n chainId={token.chainId}\n onClick={onShowTokenDetails}\n />\n <PinTokenButton\n chainId={token.chainId}\n tokenAddress={token.address}\n />\n </Box>\n </Box>\n </Slide>\n </Box>\n )\n }\n />\n {isBalanceLoading ? (\n <TokenAmountSkeleton />\n ) : (\n <Box sx={{ textAlign: 'right' }}>\n {token.amount ? (\n <Typography\n noWrap\n sx={{\n fontWeight: 600,\n }}\n title={tokenAmount}\n >\n {t('format.tokenAmount', {\n value: tokenAmount,\n })}\n </Typography>\n ) : null}\n {token.amount && token.priceUSD ? (\n <Typography\n data-price={token.priceUSD}\n sx={{\n fontWeight: 500,\n fontSize: 12,\n color: 'text.secondary',\n }}\n >\n {t('format.currency', {\n value: tokenPrice,\n })}\n </Typography>\n ) : null}\n </Box>\n )}\n </ListItemButton>\n )\n }\n)\n\nexport const TokenListItemSkeleton = (): JSX.Element => {\n return (\n <ListItem\n secondaryAction={<TokenAmountSkeleton />}\n disablePadding\n sx={(theme) => ({\n position: 'relative',\n flexDirection: 'row',\n alignItems: 'center',\n padding: 0,\n [`& .${listItemSecondaryActionClasses.root}`]: {\n right: theme.spacing(1.5),\n },\n })}\n >\n <ListItemAvatar>\n <Skeleton\n variant=\"circular\"\n width={40}\n height={40}\n sx={{ marginLeft: 1.5, marginRight: 2 }}\n />\n </ListItemAvatar>\n <ListItemText\n primary={<Skeleton variant=\"text\" width={56} height={24} />}\n secondary={<Skeleton variant=\"text\" width={96} height={16} />}\n />\n </ListItem>\n )\n}\n\nconst TokenAmountSkeleton: React.FC = () => {\n return (\n <Box\n sx={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-end',\n }}\n >\n <Skeleton variant=\"text\" width={56} height={24} />\n <Skeleton variant=\"text\" width={48} height={16} />\n </Box>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;AA+BA,MAAa,gBAA8C,MACxD,EACC,SACA,MACA,OACA,OACA,OACA,kBACA,gBACA,cACA,UACA,yBACI;CACJ,OACE,qBAACA,YAAD;EACE,OAAO;GACL,QAAQ,GAAG,KAAK;GAChB,WAAW,cAAc,MAAM;GAC/B,SAAS;EACX;EALF,UAAA;GAOG;GACD,oBAAC,qBAAD;IACS;IACW;IACT;IACF;IACG;IACU;GACrB,CAAA;GACA;EACO;;AAEd,CACF;AAEA,MAAM,sBAAsB,MAAM,EAAE,YAAsC;CACxE,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,IAAI;CAEzD,OACE,oBAAC,QAAD;EACE,KAAK,MAAM;EACX,KAAK,MAAM;EACX,KAAK,UACH,iBAAiB,EAAE,SAAS,MAAM,KAAK,QAAQ,KAAK,KAAK,IAAI;EAE/D,cAAc,kBAAkB,KAAK;EAEpC,UAAA,MAAM,SAAS;CACV,CAAA;AAEZ,CAAC;AAaD,MAAM,0BAA0B,EAC9B,cACA,wBACA,SACA,cACiC;CACjC,IAAI,CAAC,cACH,OAAO;CAET,OACE,oBAACC,cAAD;EACE,MAAK;EACL,UAAU,MAAM;GACd,EAAE,gBAAgB;GAClB,EAAE,cAAc,KAAK;GACrB,QAAQ,cAAc,wBAAwB,OAAO;EACvD;EAEA,UAAA,oBAACC,cAAD,CAAmB,CAAA;CACT,CAAA;AAEhB;AAEA,MAAM,sBAA0D,MAC7D,EACC,SACA,OACA,OACA,kBACA,UACA,yBACI;CACJ,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,YAAY,OAAO,IAAI;CAC7B,MAAM,YAAY,OAAsC,KAAA,CAAS;CACjE,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CAEpD,MAAM,yBAAyB,OAAO,cAAc,UAAU;CAE9D,MAAM,qBAAqB;EACzB,UAAU,UAAU,iBAAiB;GACnC,IAAI,MAAM,SACR,eAAe,IAAI;EAEvB,GAAG,GAAG;CACR;CAEA,MAAM,qBAAqB;EACzB,aAAa,UAAU,OAAO;EAC9B,IAAI,aACF,eAAe,KAAK;CAExB;CAEA,MAAM,eAAkD,MAAM;EAC5D,EAAE,gBAAgB;EAClB,UAAU,MAAM,SAAS,MAAM,OAAO;CACxC;CAEA,MAAM,cAAc,cACZ,kBAAkB,MAAM,QAAQ,MAAM,QAAQ,GACpD,CAAC,MAAM,QAAQ,MAAM,QAAQ,CAC/B;CAEA,MAAM,aAAa,cACX,iBAAiB,MAAM,QAAQ,MAAM,UAAU,MAAM,QAAQ,GACnE;EAAC,MAAM;EAAQ,MAAM;EAAU,MAAM;CAAQ,CAC/C;CAMA,OACE,qBAACC,kBAAD;EACE,SAAS;EACK;EACA;EACd,GAToB,mBACtB,mBAAmB,MAAM,SAAS,wBAAwB,MAAM,OAAO,CAQnD;EAClB,OAAA;EACU;EACV,IAAI;GACF,QAAQ;GACR,cAAc;EAChB;EAVF,UAAA;GAYE,oBAAC,gBAAD,EAAA,UACG,QACC,oBAAC,aAAD;IACS;IACA;IACP,iBAAiB;IACjB,iBAAiB;GAClB,CAAA,IAED,oBAAC,qBAAD,EAA4B,MAAQ,CAAA,EAExB,CAAA;GAChB,oBAAC,cAAD;IACE,SACE,qBAAC,KAAD;KAAK,IAAI;MAAE,SAAS;MAAQ,YAAY;MAAU,KAAK;KAAI;KAA3D,UAAA,CACG,MAAM,QACN,CAAC,MAAM,YACN,oBAAC,SAAD;MACE,OAAO,EAAE,iCAAiC;MAC1C,WAAU;MACV,OAAA;MAEA,UAAA,oBAAC,gBAAD,EACE,IAAI;OACF,SAAS;OACT,UAAU;OACV,OAAO;OACP,QAAQ;MACV,EACD,CAAA;KACM,CAAA,CAER;;IAEP,WAAW,EACT,WAAW,EACT,WAAW,MACb,EACF;IACA,WACE,yBACE,qBAAC,KAAD;KACE,KAAK;KACL,IAAI;MACF,QAAQ;MACR,SAAS;KACX;KALF,UAAA,CAOE,oBAAC,KAAD;MACE,IAAI,EACF,IAAI,IACN;MAEC,UAAA,MAAM;KACJ,CAAA,GACL,oBAAC,KAAD;MACE,IAAI,EACF,UAAU,WACZ;MAEA,UAAA,oBAAC,OAAD;OACE,WAAU;OACV,IAAI;OACJ,WAAW,UAAU;OACrB,OAAO,EACL,UAAU,WACZ;OACA,QAAQ;OACR,cAAA;OAEA,UAAA,qBAAC,KAAD,EAAA,UAAA,CACE,oBAAC,wBAAD;QACE,cAAc,MAAM;QACI;QACxB,SAAS,MAAM;QACf,SAAS;OACV,CAAA,GACD,oBAAC,gBAAD;QACE,SAAS,MAAM;QACf,cAAc,MAAM;OACrB,CAAA,CACE,EAAA,CAAA;MACA,CAAA;KACJ,CAAA,CACF;IAEL,CAAA,IAAA,qBAAC,KAAD;KACE,KAAK;KACL,IAAI;MACF,UAAU;MACV,QAAQ;KACV;KALF,UAAA,CAOE,oBAAC,OAAD;MACE,WAAU;MACV,IAAI,CAAC;MACL,WAAW,UAAU;MACrB,OAAO,EACL,UAAU,WACZ;MACA,QAAQ;MAER,UAAA,oBAAC,KAAD;OACE,IAAI,EACF,IAAI,IACN;OAEC,UAAA,MAAM;MACJ,CAAA;KACA,CAAA,GACP,oBAAC,OAAD;MACE,WAAU;MACV,IAAI;MACJ,WAAW,UAAU;MACrB,OAAO,EACL,UAAU,WACZ;MACA,QAAQ;MACR,cAAA;MAEA,UAAA,qBAAC,KAAD;OACE,IAAI;QACF,SAAS;QACT,YAAY;QACZ,KAAK;OACP;OALF,UAAA,CAOE,oBAAC,KAAD;QACE,IAAI;SACF,SAAS;SACT,YAAY;SACZ,IAAI;QACN;QAEC,UAAA,eAAe,MAAM,OAAO;OAC1B,CAAA,GACL,qBAAC,KAAD,EAAA,UAAA,CACE,oBAAC,wBAAD;QACE,cAAc,MAAM;QACI;QACxB,SAAS,MAAM;QACf,SAAS;OACV,CAAA,GACD,oBAAC,gBAAD;QACE,SAAS,MAAM;QACf,cAAc,MAAM;OACrB,CAAA,CACE,EAAA,CAAA,CACF;;KACA,CAAA,CACJ;;GAGV,CAAA;GACA,mBACC,oBAAC,qBAAD,CAAsB,CAAA,IAEtB,qBAAC,KAAD;IAAK,IAAI,EAAE,WAAW,QAAQ;IAA9B,UAAA,CACG,MAAM,SACL,oBAAC,YAAD;KACE,QAAA;KACA,IAAI,EACF,YAAY,IACd;KACA,OAAO;KAEN,UAAA,EAAE,sBAAsB,EACvB,OAAO,YACT,CAAC;IACS,CAAA,IACV,MACH,MAAM,UAAU,MAAM,WACrB,oBAAC,YAAD;KACE,cAAY,MAAM;KAClB,IAAI;MACF,YAAY;MACZ,UAAU;MACV,OAAO;KACT;KAEC,UAAA,EAAE,mBAAmB,EACpB,OAAO,WACT,CAAC;IACS,CAAA,IACV,IACD;;EAEO;;AAEpB,CACF;AAEA,MAAa,8BAA2C;CACtD,OACE,qBAACH,YAAD;EACE,iBAAiB,oBAAC,qBAAD,CAAsB,CAAA;EACvC,gBAAA;EACA,KAAK,WAAW;GACd,UAAU;GACV,eAAe;GACf,YAAY;GACZ,SAAS;IACR,MAAM,+BAA+B,SAAS,EAC7C,OAAO,MAAM,QAAQ,GAAG,EAC1B;EACF;EAXF,UAAA,CAaE,oBAAC,gBAAD,EAAA,UACE,oBAAC,UAAD;GACE,SAAQ;GACR,OAAO;GACP,QAAQ;GACR,IAAI;IAAE,YAAY;IAAK,aAAa;GAAE;EACvC,CAAA,EACa,CAAA,GAChB,oBAAC,cAAD;GACE,SAAS,oBAAC,UAAD;IAAU,SAAQ;IAAO,OAAO;IAAI,QAAQ;GAAK,CAAA;GAC1D,WAAW,oBAAC,UAAD;IAAU,SAAQ;IAAO,OAAO;IAAI,QAAQ;GAAK,CAAA;EAC7D,CAAA,CACO;;AAEd;AAEA,MAAM,4BAAsC;CAC1C,OACE,qBAAC,KAAD;EACE,IAAI;GACF,SAAS;GACT,eAAe;GACf,YAAY;EACd;EALF,UAAA,CAOE,oBAAC,UAAD;GAAU,SAAQ;GAAO,OAAO;GAAI,QAAQ;EAAK,CAAA,GACjD,oBAAC,UAAD;GAAU,SAAQ;GAAO,OAAO;GAAI,QAAQ;EAAK,CAAA,CAC9C;;AAET"}
1
+ {"version":3,"file":"TokenListItem.js","names":["ListItem","IconButton","InfoOutlinedIcon","ListItemButton"],"sources":["../../../../src/components/TokenList/TokenListItem.tsx"],"sourcesContent":["import type { StaticToken } from '@lifi/sdk'\nimport { ChainType } from '@lifi/sdk'\nimport InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'\nimport ReportRoundedIcon from '@mui/icons-material/ReportRounded'\nimport VerifiedIcon from '@mui/icons-material/Verified'\nimport {\n Avatar,\n Box,\n ListItemAvatar,\n ListItemText,\n listItemSecondaryActionClasses,\n Skeleton,\n Slide,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport type { JSX, MouseEventHandler } from 'react'\nimport { memo, useMemo, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useLongPress } from '../../hooks/useLongPress.js'\nimport { formatTokenAmount, formatTokenPrice } from '../../utils/format.js'\nimport { getTokenVerificationProvider } from '../../utils/token.js'\nimport { shortenAddress } from '../../utils/wallet.js'\nimport { TokenAvatar } from '../Avatar/TokenAvatar.js'\nimport { ListItemButton } from '../ListItem/ListItemButton.js'\nimport { PinTokenButton } from './PinTokenButton.js'\nimport { IconButton, ListItem } from './TokenList.style.js'\nimport type {\n TokenListItemAvatarProps,\n TokenListItemButtonProps,\n TokenListItemProps,\n} from './types.js'\n\nexport const TokenListItem: React.FC<TokenListItemProps> = memo(\n ({\n onClick,\n size,\n start,\n token,\n chain,\n chainName,\n isBalanceLoading,\n startAdornment,\n endAdornment,\n selected,\n onShowTokenDetails,\n }) => {\n return (\n <ListItem\n style={{\n height: `${size}px`,\n transform: `translateY(${start}px)`,\n padding: 0,\n }}\n >\n {startAdornment}\n <TokenListItemButton\n token={token}\n isBalanceLoading={isBalanceLoading}\n onClick={onClick}\n chain={chain}\n chainName={chainName}\n selected={selected}\n onShowTokenDetails={onShowTokenDetails}\n />\n {endAdornment}\n </ListItem>\n )\n }\n)\n\nconst TokenListItemAvatar = memo(({ token }: TokenListItemAvatarProps) => {\n const [isImageLoading, setIsImageLoading] = useState(true)\n\n return (\n <Avatar\n src={token.logoURI}\n alt={token.symbol}\n sx={(theme) =>\n isImageLoading ? { bgcolor: theme.vars.palette.grey[300] } : null\n }\n onLoad={() => setIsImageLoading(false)}\n >\n {token.symbol?.[0]}\n </Avatar>\n )\n})\n\ninterface OpenTokenDetailsButtonProps {\n tokenAddress: string | undefined\n withoutContractAddress: boolean\n chainId: number\n onClick: (\n tokenAddress: string,\n withoutContractAddress: boolean,\n chainId: number\n ) => void\n}\n\nconst OpenTokenDetailsButton = ({\n tokenAddress,\n withoutContractAddress,\n chainId,\n onClick,\n}: OpenTokenDetailsButtonProps) => {\n if (!tokenAddress) {\n return null\n }\n return (\n <IconButton\n size=\"small\"\n onClick={(e) => {\n e.stopPropagation()\n e.currentTarget.blur() // Remove focus to prevent accessibility issues when opening drawer\n onClick(tokenAddress, withoutContractAddress, chainId)\n }}\n >\n <InfoOutlinedIcon />\n </IconButton>\n )\n}\n\nconst TokenListItemButton: React.FC<TokenListItemButtonProps> = memo(\n ({\n onClick,\n token,\n chain,\n chainName,\n isBalanceLoading,\n selected,\n onShowTokenDetails,\n }) => {\n const { t } = useTranslation()\n const container = useRef(null)\n const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined)\n const [showAddress, setShowAddress] = useState(false)\n\n const withoutContractAddress = chain?.chainType === ChainType.UTXO\n\n const onMouseEnter = () => {\n timeoutId.current = setTimeout(() => {\n if (token.address) {\n setShowAddress(true)\n }\n }, 350)\n }\n\n const onMouseLeave = () => {\n clearTimeout(timeoutId.current)\n if (showAddress) {\n setShowAddress(false)\n }\n }\n\n const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {\n e.stopPropagation()\n onClick?.(token.address, token.chainId)\n }\n\n const tokenAmount = useMemo(\n () => formatTokenAmount(token.amount, token.decimals),\n [token.amount, token.decimals]\n )\n\n const tokenPrice = useMemo(\n () => formatTokenPrice(token.amount, token.priceUSD, token.decimals),\n [token.amount, token.priceUSD, token.decimals]\n )\n\n const longPressEvents = useLongPress(() =>\n onShowTokenDetails(token.address, withoutContractAddress, token.chainId)\n )\n\n // The native token is badged ahead of any verdict: the provider screens\n // the native-address convention and calls it a scam on some chains.\n // A token the main list or the integrator vouches for needs no warning;\n // anything else without a clean verdict keeps the amber one.\n let verificationBadge:\n | { Icon: typeof VerifiedIcon; color: string; title: string }\n | undefined\n if (token.native) {\n verificationBadge = {\n Icon: VerifiedIcon,\n color: 'info.main',\n title: chainName\n ? t('tooltip.tokenNativeOnChain', {\n tokenSymbol: token.symbol,\n chainName,\n })\n : t('tooltip.tokenNative', { tokenSymbol: token.symbol }),\n }\n } else if (token.verificationStatus === 'flagged') {\n verificationBadge = {\n Icon: ReportRoundedIcon,\n color: 'error.main',\n title: t('warning.message.tokenFlagged', {\n tokenSymbol: token.symbol,\n }),\n }\n } else if (token.verificationStatus === 'verified') {\n const provider = getTokenVerificationProvider(token)\n verificationBadge = {\n Icon: VerifiedIcon,\n color: 'success.main',\n title: provider\n ? t('tooltip.tokenVerifiedByProvider', {\n tokenSymbol: token.symbol,\n provider,\n })\n : t('tooltip.tokenVerified', { tokenSymbol: token.symbol }),\n }\n } else if (\n token.verificationStatus === 'unverified' &&\n !token.listed &&\n !token.verified\n ) {\n verificationBadge = {\n Icon: ReportRoundedIcon,\n color: 'warning.main',\n title: t('warning.message.tokenUnverifiedByProvider', {\n tokenSymbol: token.symbol,\n }),\n }\n } else if (!token.verificationStatus && !token.listed && !token.verified) {\n verificationBadge = {\n Icon: ReportRoundedIcon,\n color: 'warning.main',\n title: t('warning.message.tokenUnverified'),\n }\n }\n\n return (\n <ListItemButton\n onClick={handleClick}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n {...longPressEvents}\n dense\n selected={selected}\n sx={{\n height: 60,\n marginBottom: '4px',\n }}\n >\n <ListItemAvatar>\n {chain ? (\n <TokenAvatar\n token={token as StaticToken}\n chain={chain}\n tokenAvatarSize={40}\n chainAvatarSize={16}\n />\n ) : (\n <TokenListItemAvatar token={token} />\n )}\n </ListItemAvatar>\n <ListItemText\n primary={\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {token.symbol}\n {verificationBadge && (\n <Tooltip title={verificationBadge.title} placement=\"top\" arrow>\n <verificationBadge.Icon\n sx={{\n display: 'flex',\n fontSize: 16,\n color: verificationBadge.color,\n cursor: 'help',\n }}\n />\n </Tooltip>\n )}\n </Box>\n }\n slotProps={{\n secondary: {\n component: 'div',\n },\n }}\n secondary={\n withoutContractAddress ? (\n <Box\n ref={container}\n sx={{\n height: 20,\n display: 'flex',\n }}\n >\n <Box\n sx={{\n pt: 0.25,\n }}\n >\n {token.name}\n </Box>\n <Box\n sx={{\n position: 'relative',\n }}\n >\n <Slide\n direction=\"up\"\n in={showAddress}\n container={container.current}\n style={{\n position: 'absolute',\n }}\n appear={false}\n mountOnEnter\n >\n <Box>\n <OpenTokenDetailsButton\n tokenAddress={token.address}\n withoutContractAddress={withoutContractAddress}\n chainId={token.chainId}\n onClick={onShowTokenDetails}\n />\n <PinTokenButton\n chainId={token.chainId}\n tokenAddress={token.address}\n />\n </Box>\n </Slide>\n </Box>\n </Box>\n ) : (\n <Box\n ref={container}\n sx={{\n position: 'relative',\n height: 20,\n }}\n >\n <Slide\n direction=\"down\"\n in={!showAddress}\n container={container.current}\n style={{\n position: 'absolute',\n }}\n appear={false}\n >\n <Box\n sx={{\n pt: 0.25,\n }}\n >\n {token.name}\n </Box>\n </Slide>\n <Slide\n direction=\"up\"\n in={showAddress}\n container={container.current}\n style={{\n position: 'absolute',\n }}\n appear={false}\n mountOnEnter\n >\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n gap: 0.5,\n }}\n >\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n pt: 0.125,\n }}\n >\n {shortenAddress(token.address)}\n </Box>\n <Box>\n <OpenTokenDetailsButton\n tokenAddress={token.address}\n withoutContractAddress={withoutContractAddress}\n chainId={token.chainId}\n onClick={onShowTokenDetails}\n />\n <PinTokenButton\n chainId={token.chainId}\n tokenAddress={token.address}\n />\n </Box>\n </Box>\n </Slide>\n </Box>\n )\n }\n />\n {isBalanceLoading ? (\n <TokenAmountSkeleton />\n ) : (\n <Box sx={{ textAlign: 'right' }}>\n {token.amount ? (\n <Typography\n noWrap\n sx={{\n fontWeight: 600,\n }}\n title={tokenAmount}\n >\n {t('format.tokenAmount', {\n value: tokenAmount,\n })}\n </Typography>\n ) : null}\n {token.amount && token.priceUSD ? (\n <Typography\n data-price={token.priceUSD}\n sx={{\n fontWeight: 500,\n fontSize: 12,\n color: 'text.secondary',\n }}\n >\n {t('format.currency', {\n value: tokenPrice,\n })}\n </Typography>\n ) : null}\n </Box>\n )}\n </ListItemButton>\n )\n }\n)\n\nexport const TokenListItemSkeleton = (): JSX.Element => {\n return (\n <ListItem\n secondaryAction={<TokenAmountSkeleton />}\n disablePadding\n sx={(theme) => ({\n position: 'relative',\n flexDirection: 'row',\n alignItems: 'center',\n padding: 0,\n [`& .${listItemSecondaryActionClasses.root}`]: {\n right: theme.spacing(1.5),\n },\n })}\n >\n <ListItemAvatar>\n <Skeleton\n variant=\"circular\"\n width={40}\n height={40}\n sx={{ marginLeft: 1.5, marginRight: 2 }}\n />\n </ListItemAvatar>\n <ListItemText\n primary={<Skeleton variant=\"text\" width={56} height={24} />}\n secondary={<Skeleton variant=\"text\" width={96} height={16} />}\n />\n </ListItem>\n )\n}\n\nconst TokenAmountSkeleton: React.FC = () => {\n return (\n <Box\n sx={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-end',\n }}\n >\n <Skeleton variant=\"text\" width={56} height={24} />\n <Skeleton variant=\"text\" width={48} height={16} />\n </Box>\n )\n}\n"],"mappings":"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"}
@@ -24,18 +24,28 @@ const VirtualizedTokenList = ({ tokens, scrollElementRef, chainId, selectedToken
24
24
  const token = tokens[index];
25
25
  return `${token.chainId}-${token.address}-${index}`;
26
26
  }, [tokens]);
27
+ const nativeTokenHoisted = useMemo(() => {
28
+ const first = tokens[0];
29
+ return !!first?.native && !first.pinned && !first.featured && !first.popular && !first.verified;
30
+ }, [tokens]);
31
+ const isListStartIndex = useCallback((index) => index === 0 || index === 1 && nativeTokenHoisted, [nativeTokenHoisted]);
27
32
  const estimateSize = useCallback((index) => {
28
33
  const currentToken = tokens[index];
29
34
  const previousToken = tokens[index - 1];
30
35
  let size = tokenItemHeight;
31
- if (currentToken.pinned && index === 0) size += 24;
36
+ if (currentToken.pinned && isListStartIndex(index)) size += 24;
32
37
  if (previousToken?.pinned && !currentToken.pinned) size += 32;
33
38
  if (!showCategories) return size;
34
- if (currentToken.featured && !currentToken.pinned && index === 0) size += 24;
35
- if (!currentToken.pinned && !previousToken?.pinned && (previousToken?.amount && !currentToken.amount || previousToken?.featured && !currentToken.featured || previousToken?.popular && !currentToken.popular)) size += 32;
39
+ if (currentToken.featured && !currentToken.pinned && isListStartIndex(index)) size += 24;
40
+ const isNotPinned = !currentToken.pinned && !previousToken?.pinned;
41
+ if (!isListStartIndex(index) && isNotPinned && (previousToken?.amount && !currentToken.amount || previousToken?.featured && !currentToken.featured || previousToken?.popular && !currentToken.popular)) size += 32;
36
42
  return size;
37
- }, [tokens, showCategories]);
38
- const { getVirtualItems, getTotalSize, scrollToIndex, measure } = useVirtualizer(useMemo(() => ({
43
+ }, [
44
+ tokens,
45
+ showCategories,
46
+ isListStartIndex
47
+ ]);
48
+ const virtualizerConfig = useMemo(() => ({
39
49
  count: tokens.length,
40
50
  overscan: 5,
41
51
  getScrollElement: () => scrollElementRef.current,
@@ -47,7 +57,8 @@ const VirtualizedTokenList = ({ tokens, scrollElementRef, chainId, selectedToken
47
57
  estimateSize,
48
58
  getItemKey,
49
59
  scrollElementRef
50
- ]));
60
+ ]);
61
+ const { getVirtualItems, getTotalSize, scrollToIndex, measure } = useVirtualizer(virtualizerConfig);
51
62
  useEffect(() => {
52
63
  if (scrollElementRef.current) measure();
53
64
  }, [measure, scrollElementRef.current]);
@@ -69,8 +80,9 @@ const VirtualizedTokenList = ({ tokens, scrollElementRef, chainId, selectedToken
69
80
  const currentToken = tokens[item.index];
70
81
  const previousToken = tokens[item.index - 1];
71
82
  const chain = chainsSet?.get(currentToken.chainId);
83
+ const isListStart = isListStartIndex(item.index);
72
84
  const isNotPinned = !currentToken.pinned;
73
- const isFirstPinnedToken = currentToken.pinned && item.index === 0;
85
+ const isFirstPinnedToken = currentToken.pinned && isListStart;
74
86
  const isTransitionFromPinned = previousToken?.pinned && isNotPinned;
75
87
  const isTransitionFromFeatured = previousToken?.featured && !currentToken.featured && isNotPinned;
76
88
  const isTransitionFromMyTokens = previousToken?.amount && !currentToken.amount && isNotPinned;
@@ -79,7 +91,7 @@ const VirtualizedTokenList = ({ tokens, scrollElementRef, chainId, selectedToken
79
91
  if (showPinnedTokens && isFirstPinnedToken) return t("main.pinnedTokens");
80
92
  if (showPinnedTokens && !showCategories && isTransitionFromPinned) return t("main.allTokens");
81
93
  if (!showCategories) return null;
82
- if (isTransitionFromPinned && currentToken.featured || currentToken.featured && isNotPinned && item.index === 0) return t("main.featuredTokens");
94
+ if (isTransitionFromPinned && currentToken.featured || currentToken.featured && isNotPinned && isListStart) return t("main.featuredTokens");
83
95
  if ((isTransitionFromFeatured || isTransitionFromPinned) && currentToken.amount && isNotPinned) return t("main.myTokens");
84
96
  if ((isTransitionFromFeatured || isTransitionFromMyTokens || isTransitionFromPinned) && currentToken.popular && isNotPinned) return t("main.popularTokens");
85
97
  if (isTransitionFromMyTokens || isTransitionFromFeatured || isTransitionFromPinned || isTransitionFromPopular) return t("main.allTokens");
@@ -92,6 +104,7 @@ const VirtualizedTokenList = ({ tokens, scrollElementRef, chainId, selectedToken
92
104
  start: item.start,
93
105
  token: currentToken,
94
106
  chain: isAllNetworks ? chain : void 0,
107
+ chainName: chain?.name,
95
108
  selected: isSelected,
96
109
  onShowTokenDetails,
97
110
  isBalanceLoading,
@@ -101,7 +114,7 @@ const VirtualizedTokenList = ({ tokens, scrollElementRef, chainId, selectedToken
101
114
  fontWeight: 600,
102
115
  lineHeight: "16px",
103
116
  px: 1.5,
104
- pt: isFirstPinnedToken || currentToken.featured && isNotPinned && item.index === 0 ? 0 : 1,
117
+ pt: isFirstPinnedToken || currentToken.featured && isNotPinned && isListStart ? 0 : 1,
105
118
  pb: 1
106
119
  },
107
120
  children: startAdornmentLabel
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualizedTokenList.js","names":["List"],"sources":["../../../../src/components/TokenList/VirtualizedTokenList.tsx"],"sourcesContent":["import { Typography } from '@mui/material'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport type { FC } from 'react'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useAvailableChains } from '../../hooks/useAvailableChains.js'\nimport type { TokenAmount } from '../../types/token.js'\nimport { TokenDetailsSheet } from './TokenDetailsSheet.js'\nimport { List } from './TokenList.style.js'\nimport { TokenListItem, TokenListItemSkeleton } from './TokenListItem.js'\nimport type {\n TokenDetailsSheetBase,\n VirtualizedTokenListProps,\n} from './types.js'\n\nconst tokenItemHeight = 64 // 60 + 4px margin-bottom\n\nexport const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({\n tokens,\n scrollElementRef,\n chainId,\n selectedTokenAddress,\n isLoading,\n isBalanceLoading,\n showCategories,\n showPinnedTokens,\n onClick,\n isAllNetworks,\n}) => {\n const { t } = useTranslation()\n\n const { chains } = useAvailableChains()\n\n // Create Set for O(1) chain lookup instead of O(n) find\n const chainsSet = useMemo(() => {\n if (!chains) {\n return undefined\n }\n return new Map(chains.map((chain) => [chain.id, chain]))\n }, [chains])\n\n const tokenDetailsSheetRef = useRef<TokenDetailsSheetBase>(null)\n\n const onShowTokenDetails = useCallback(\n (tokenAddress: string, noContractAddress: boolean, chainId: number) => {\n tokenDetailsSheetRef.current?.open(\n tokenAddress,\n noContractAddress,\n chainId\n )\n },\n []\n )\n\n const getItemKey = useCallback(\n (index: number) => {\n const token = tokens[index]\n return `${token.chainId}-${token.address}-${index}`\n },\n [tokens]\n )\n\n const estimateSize = useCallback(\n (index: number) => {\n const currentToken = tokens[index]\n const previousToken = tokens[index - 1]\n let size = tokenItemHeight\n\n // Pinned tokens (always shown, even in all networks mode)\n if (currentToken.pinned && index === 0) {\n size += 24\n }\n if (previousToken?.pinned && !currentToken.pinned) {\n size += 32\n }\n\n if (!showCategories) {\n return size\n }\n\n if (currentToken.featured && !currentToken.pinned && index === 0) {\n size += 24\n }\n\n // Category transition (excluding pinned tokens)\n const isNotPinned = !currentToken.pinned && !previousToken?.pinned\n if (\n isNotPinned &&\n ((previousToken?.amount && !currentToken.amount) ||\n (previousToken?.featured && !currentToken.featured) ||\n (previousToken?.popular && !currentToken.popular))\n ) {\n size += 32\n }\n\n return size\n },\n [tokens, showCategories]\n )\n\n const virtualizerConfig = useMemo(\n () => ({\n count: tokens.length,\n overscan: 5,\n getScrollElement: () => scrollElementRef.current,\n estimateSize,\n getItemKey,\n paddingEnd: 8,\n }),\n [tokens.length, estimateSize, getItemKey, scrollElementRef]\n )\n\n const { getVirtualItems, getTotalSize, scrollToIndex, measure } =\n useVirtualizer(virtualizerConfig)\n\n // Address the issue of disappearing tokens on rerender\n useEffect(() => {\n if (scrollElementRef.current) {\n measure()\n }\n }, [measure, scrollElementRef.current])\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: run only when chainId changes\n useEffect(() => {\n // Scroll to the top of the list when switching the chains\n if (getVirtualItems().length) {\n scrollToIndex(0, { align: 'start' })\n }\n // Close the token details sheet when switching the chains\n tokenDetailsSheetRef.current?.close()\n }, [scrollToIndex, isAllNetworks, chainId, getVirtualItems])\n\n return (\n <>\n <List\n className=\"long-list\"\n style={{ height: getTotalSize() }}\n disablePadding\n >\n {getVirtualItems().map((item) => {\n const currentToken = tokens[item.index]\n const previousToken: TokenAmount | undefined = tokens[item.index - 1]\n const chain = chainsSet?.get(currentToken.chainId)\n\n const isNotPinned = !currentToken.pinned\n const isFirstPinnedToken = currentToken.pinned && item.index === 0\n const isTransitionFromPinned = previousToken?.pinned && isNotPinned\n\n // Category transitions (excluding pinned)\n const isTransitionFromFeatured =\n previousToken?.featured && !currentToken.featured && isNotPinned\n const isTransitionFromMyTokens =\n previousToken?.amount && !currentToken.amount && isNotPinned\n const isTransitionFromPopular =\n previousToken?.popular && !currentToken.popular && isNotPinned\n\n // Determine which category label to show\n const startAdornmentLabel = (() => {\n if (showPinnedTokens && isFirstPinnedToken) {\n return t('main.pinnedTokens')\n }\n if (showPinnedTokens && !showCategories && isTransitionFromPinned) {\n return t('main.allTokens')\n }\n if (!showCategories) {\n return null\n }\n\n if (\n (isTransitionFromPinned && currentToken.featured) ||\n (currentToken.featured && isNotPinned && item.index === 0)\n ) {\n return t('main.featuredTokens')\n }\n if (\n (isTransitionFromFeatured || isTransitionFromPinned) &&\n currentToken.amount &&\n isNotPinned\n ) {\n return t('main.myTokens')\n }\n if (\n (isTransitionFromFeatured ||\n isTransitionFromMyTokens ||\n isTransitionFromPinned) &&\n currentToken.popular &&\n isNotPinned\n ) {\n return t('main.popularTokens')\n }\n if (\n isTransitionFromMyTokens ||\n isTransitionFromFeatured ||\n isTransitionFromPinned ||\n isTransitionFromPopular\n ) {\n return t('main.allTokens')\n }\n return null\n })()\n\n const isSelected =\n selectedTokenAddress === currentToken.address &&\n chainId === currentToken.chainId\n\n return (\n <TokenListItem\n key={item.key}\n onClick={onClick}\n size={item.size}\n start={item.start}\n token={currentToken}\n chain={isAllNetworks ? chain : undefined}\n selected={isSelected}\n onShowTokenDetails={onShowTokenDetails}\n isBalanceLoading={isBalanceLoading}\n startAdornment={\n startAdornmentLabel ? (\n <Typography\n sx={{\n fontSize: 14,\n fontWeight: 600,\n lineHeight: '16px',\n px: 1.5,\n pt:\n isFirstPinnedToken ||\n (currentToken.featured &&\n isNotPinned &&\n item.index === 0)\n ? 0\n : 1,\n pb: 1,\n }}\n >\n {startAdornmentLabel}\n </Typography>\n ) : null\n }\n />\n )\n })}\n </List>\n <TokenDetailsSheet ref={tokenDetailsSheetRef} />\n {isLoading && (\n <List disablePadding sx={{ cursor: 'default' }}>\n {Array.from({ length: 3 }).map((_, index) => (\n <TokenListItemSkeleton key={index} />\n ))}\n </List>\n )}\n </>\n )\n}\n"],"mappings":";;;;;;;;;;AAeA,MAAM,kBAAkB;AAExB,MAAa,wBAAuD,EAClE,QACA,kBACA,SACA,sBACA,WACA,kBACA,gBACA,kBACA,SACA,oBACI;CACJ,MAAM,EAAE,MAAM,eAAe;CAE7B,MAAM,EAAE,WAAW,mBAAmB;CAGtC,MAAM,YAAY,cAAc;EAC9B,IAAI,CAAC,QACH;EAEF,OAAO,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;CACzD,GAAG,CAAC,MAAM,CAAC;CAEX,MAAM,uBAAuB,OAA8B,IAAI;CAE/D,MAAM,qBAAqB,aACxB,cAAsB,mBAA4B,YAAoB;EACrE,qBAAqB,SAAS,KAC5B,cACA,mBACA,OACF;CACF,GACA,CAAC,CACH;CAEA,MAAM,aAAa,aAChB,UAAkB;EACjB,MAAM,QAAQ,OAAO;EACrB,OAAO,GAAG,MAAM,QAAQ,GAAG,MAAM,QAAQ,GAAG;CAC9C,GACA,CAAC,MAAM,CACT;CAEA,MAAM,eAAe,aAClB,UAAkB;EACjB,MAAM,eAAe,OAAO;EAC5B,MAAM,gBAAgB,OAAO,QAAQ;EACrC,IAAI,OAAO;EAGX,IAAI,aAAa,UAAU,UAAU,GACnC,QAAQ;EAEV,IAAI,eAAe,UAAU,CAAC,aAAa,QACzC,QAAQ;EAGV,IAAI,CAAC,gBACH,OAAO;EAGT,IAAI,aAAa,YAAY,CAAC,aAAa,UAAU,UAAU,GAC7D,QAAQ;EAKV,IADoB,CAAC,aAAa,UAAU,CAAC,eAAe,WAGxD,eAAe,UAAU,CAAC,aAAa,UACtC,eAAe,YAAY,CAAC,aAAa,YACzC,eAAe,WAAW,CAAC,aAAa,UAE3C,QAAQ;EAGV,OAAO;CACT,GACA,CAAC,QAAQ,cAAc,CACzB;CAcA,MAAM,EAAE,iBAAiB,cAAc,eAAe,YACpD,eAbwB,eACjB;EACL,OAAO,OAAO;EACd,UAAU;EACV,wBAAwB,iBAAiB;EACzC;EACA;EACA,YAAY;CACd,IACA;EAAC,OAAO;EAAQ;EAAc;EAAY;CAAgB,CAI3B,CAAC;CAGlC,gBAAgB;EACd,IAAI,iBAAiB,SACnB,QAAQ;CAEZ,GAAG,CAAC,SAAS,iBAAiB,OAAO,CAAC;CAGtC,gBAAgB;EAEd,IAAI,gBAAgB,CAAC,CAAC,QACpB,cAAc,GAAG,EAAE,OAAO,QAAQ,CAAC;EAGrC,qBAAqB,SAAS,MAAM;CACtC,GAAG;EAAC;EAAe;EAAe;EAAS;CAAe,CAAC;CAE3D,OACE,qBAAA,YAAA,EAAA,UAAA;EACE,oBAACA,QAAD;GACE,WAAU;GACV,OAAO,EAAE,QAAQ,aAAa,EAAE;GAChC,gBAAA;GAEC,UAAA,gBAAgB,CAAC,CAAC,KAAK,SAAS;IAC/B,MAAM,eAAe,OAAO,KAAK;IACjC,MAAM,gBAAyC,OAAO,KAAK,QAAQ;IACnE,MAAM,QAAQ,WAAW,IAAI,aAAa,OAAO;IAEjD,MAAM,cAAc,CAAC,aAAa;IAClC,MAAM,qBAAqB,aAAa,UAAU,KAAK,UAAU;IACjE,MAAM,yBAAyB,eAAe,UAAU;IAGxD,MAAM,2BACJ,eAAe,YAAY,CAAC,aAAa,YAAY;IACvD,MAAM,2BACJ,eAAe,UAAU,CAAC,aAAa,UAAU;IACnD,MAAM,0BACJ,eAAe,WAAW,CAAC,aAAa,WAAW;IAGrD,MAAM,6BAA6B;KACjC,IAAI,oBAAoB,oBACtB,OAAO,EAAE,mBAAmB;KAE9B,IAAI,oBAAoB,CAAC,kBAAkB,wBACzC,OAAO,EAAE,gBAAgB;KAE3B,IAAI,CAAC,gBACH,OAAO;KAGT,IACG,0BAA0B,aAAa,YACvC,aAAa,YAAY,eAAe,KAAK,UAAU,GAExD,OAAO,EAAE,qBAAqB;KAEhC,KACG,4BAA4B,2BAC7B,aAAa,UACb,aAEA,OAAO,EAAE,eAAe;KAE1B,KACG,4BACC,4BACA,2BACF,aAAa,WACb,aAEA,OAAO,EAAE,oBAAoB;KAE/B,IACE,4BACA,4BACA,0BACA,yBAEA,OAAO,EAAE,gBAAgB;KAE3B,OAAO;IACT,EAAA,CAAG;IAEH,MAAM,aACJ,yBAAyB,aAAa,WACtC,YAAY,aAAa;IAE3B,OACE,oBAAC,eAAD;KAEW;KACT,MAAM,KAAK;KACX,OAAO,KAAK;KACZ,OAAO;KACP,OAAO,gBAAgB,QAAQ,KAAA;KAC/B,UAAU;KACU;KACF;KAClB,gBACE,sBACE,oBAAC,YAAD;MACE,IAAI;OACF,UAAU;OACV,YAAY;OACZ,YAAY;OACZ,IAAI;OACJ,IACE,sBACC,aAAa,YACZ,eACA,KAAK,UAAU,IACb,IACA;OACN,IAAI;MACN;MAEC,UAAA;KACS,CAAA,IACV;IAEP,GA/BM,KAAK,GA+BX;GAEL,CAAC;EACG,CAAA;EACN,oBAAC,mBAAD,EAAmB,KAAK,qBAAuB,CAAA;EAC9C,aACC,oBAACA,QAAD;GAAM,gBAAA;GAAe,IAAI,EAAE,QAAQ,UAAU;GAC1C,UAAA,MAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,UACjC,oBAAC,uBAAD,CAAoC,GAAR,KAAQ,CACrC;EACG,CAAA;CAER,EAAA,CAAA;AAEN"}
1
+ {"version":3,"file":"VirtualizedTokenList.js","names":["List"],"sources":["../../../../src/components/TokenList/VirtualizedTokenList.tsx"],"sourcesContent":["import { Typography } from '@mui/material'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport type { FC } from 'react'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useAvailableChains } from '../../hooks/useAvailableChains.js'\nimport type { TokenAmount } from '../../types/token.js'\nimport { TokenDetailsSheet } from './TokenDetailsSheet.js'\nimport { List } from './TokenList.style.js'\nimport { TokenListItem, TokenListItemSkeleton } from './TokenListItem.js'\nimport type {\n TokenDetailsSheetBase,\n VirtualizedTokenListProps,\n} from './types.js'\n\nconst tokenItemHeight = 64 // 60 + 4px margin-bottom\n\nexport const VirtualizedTokenList: FC<VirtualizedTokenListProps> = ({\n tokens,\n scrollElementRef,\n chainId,\n selectedTokenAddress,\n isLoading,\n isBalanceLoading,\n showCategories,\n showPinnedTokens,\n onClick,\n isAllNetworks,\n}) => {\n const { t } = useTranslation()\n\n const { chains } = useAvailableChains()\n\n // Create Set for O(1) chain lookup instead of O(n) find\n const chainsSet = useMemo(() => {\n if (!chains) {\n return undefined\n }\n return new Map(chains.map((chain) => [chain.id, chain]))\n }, [chains])\n\n const tokenDetailsSheetRef = useRef<TokenDetailsSheetBase>(null)\n\n const onShowTokenDetails = useCallback(\n (tokenAddress: string, noContractAddress: boolean, chainId: number) => {\n tokenDetailsSheetRef.current?.open(\n tokenAddress,\n noContractAddress,\n chainId\n )\n },\n []\n )\n\n const getItemKey = useCallback(\n (index: number) => {\n const token = tokens[index]\n return `${token.chainId}-${token.address}-${index}`\n },\n [tokens]\n )\n\n // The hoisted native token sits above every category, so the row after it\n // opens the first one. Only a row the hoist could have moved counts.\n const nativeTokenHoisted = useMemo(() => {\n const first = tokens[0]\n return (\n !!first?.native &&\n !first.pinned &&\n !first.featured &&\n !first.popular &&\n !first.verified\n )\n }, [tokens])\n\n const isListStartIndex = useCallback(\n (index: number) => index === 0 || (index === 1 && nativeTokenHoisted),\n [nativeTokenHoisted]\n )\n\n const estimateSize = useCallback(\n (index: number) => {\n const currentToken = tokens[index]\n const previousToken = tokens[index - 1]\n let size = tokenItemHeight\n\n // Pinned tokens (always shown, even in all networks mode)\n if (currentToken.pinned && isListStartIndex(index)) {\n size += 24\n }\n if (previousToken?.pinned && !currentToken.pinned) {\n size += 32\n }\n\n if (!showCategories) {\n return size\n }\n\n if (\n currentToken.featured &&\n !currentToken.pinned &&\n isListStartIndex(index)\n ) {\n size += 24\n }\n\n // Category transition (excluding pinned tokens). The hoisted native row\n // sits above every band, so the row after it ends nothing.\n const isNotPinned = !currentToken.pinned && !previousToken?.pinned\n if (\n !isListStartIndex(index) &&\n isNotPinned &&\n ((previousToken?.amount && !currentToken.amount) ||\n (previousToken?.featured && !currentToken.featured) ||\n (previousToken?.popular && !currentToken.popular))\n ) {\n size += 32\n }\n\n return size\n },\n [tokens, showCategories, isListStartIndex]\n )\n\n const virtualizerConfig = useMemo(\n () => ({\n count: tokens.length,\n overscan: 5,\n getScrollElement: () => scrollElementRef.current,\n estimateSize,\n getItemKey,\n paddingEnd: 8,\n }),\n [tokens.length, estimateSize, getItemKey, scrollElementRef]\n )\n\n const { getVirtualItems, getTotalSize, scrollToIndex, measure } =\n useVirtualizer(virtualizerConfig)\n\n // Address the issue of disappearing tokens on rerender\n useEffect(() => {\n if (scrollElementRef.current) {\n measure()\n }\n }, [measure, scrollElementRef.current])\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: run only when chainId changes\n useEffect(() => {\n // Scroll to the top of the list when switching the chains\n if (getVirtualItems().length) {\n scrollToIndex(0, { align: 'start' })\n }\n // Close the token details sheet when switching the chains\n tokenDetailsSheetRef.current?.close()\n }, [scrollToIndex, isAllNetworks, chainId, getVirtualItems])\n\n return (\n <>\n <List\n className=\"long-list\"\n style={{ height: getTotalSize() }}\n disablePadding\n >\n {getVirtualItems().map((item) => {\n const currentToken = tokens[item.index]\n const previousToken: TokenAmount | undefined = tokens[item.index - 1]\n const chain = chainsSet?.get(currentToken.chainId)\n\n const isListStart = isListStartIndex(item.index)\n const isNotPinned = !currentToken.pinned\n const isFirstPinnedToken = currentToken.pinned && isListStart\n const isTransitionFromPinned = previousToken?.pinned && isNotPinned\n\n // Category transitions (excluding pinned)\n const isTransitionFromFeatured =\n previousToken?.featured && !currentToken.featured && isNotPinned\n const isTransitionFromMyTokens =\n previousToken?.amount && !currentToken.amount && isNotPinned\n const isTransitionFromPopular =\n previousToken?.popular && !currentToken.popular && isNotPinned\n\n // Determine which category label to show\n const startAdornmentLabel = (() => {\n if (showPinnedTokens && isFirstPinnedToken) {\n return t('main.pinnedTokens')\n }\n if (showPinnedTokens && !showCategories && isTransitionFromPinned) {\n return t('main.allTokens')\n }\n if (!showCategories) {\n return null\n }\n\n if (\n (isTransitionFromPinned && currentToken.featured) ||\n (currentToken.featured && isNotPinned && isListStart)\n ) {\n return t('main.featuredTokens')\n }\n if (\n (isTransitionFromFeatured || isTransitionFromPinned) &&\n currentToken.amount &&\n isNotPinned\n ) {\n return t('main.myTokens')\n }\n if (\n (isTransitionFromFeatured ||\n isTransitionFromMyTokens ||\n isTransitionFromPinned) &&\n currentToken.popular &&\n isNotPinned\n ) {\n return t('main.popularTokens')\n }\n if (\n isTransitionFromMyTokens ||\n isTransitionFromFeatured ||\n isTransitionFromPinned ||\n isTransitionFromPopular\n ) {\n return t('main.allTokens')\n }\n return null\n })()\n\n const isSelected =\n selectedTokenAddress === currentToken.address &&\n chainId === currentToken.chainId\n\n return (\n <TokenListItem\n key={item.key}\n onClick={onClick}\n size={item.size}\n start={item.start}\n token={currentToken}\n chain={isAllNetworks ? chain : undefined}\n chainName={chain?.name}\n selected={isSelected}\n onShowTokenDetails={onShowTokenDetails}\n isBalanceLoading={isBalanceLoading}\n startAdornment={\n startAdornmentLabel ? (\n <Typography\n sx={{\n fontSize: 14,\n fontWeight: 600,\n lineHeight: '16px',\n px: 1.5,\n pt:\n isFirstPinnedToken ||\n (currentToken.featured && isNotPinned && isListStart)\n ? 0\n : 1,\n pb: 1,\n }}\n >\n {startAdornmentLabel}\n </Typography>\n ) : null\n }\n />\n )\n })}\n </List>\n <TokenDetailsSheet ref={tokenDetailsSheetRef} />\n {isLoading && (\n <List disablePadding sx={{ cursor: 'default' }}>\n {Array.from({ length: 3 }).map((_, index) => (\n <TokenListItemSkeleton key={index} />\n ))}\n </List>\n )}\n </>\n )\n}\n"],"mappings":"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"}
@@ -26,10 +26,11 @@ const TokenRate = ({ route }) => {
26
26
  };
27
27
  const fromToRate = Number.parseFloat(formatUnits(toToken.amount, toToken.decimals)) / Number.parseFloat(formatUnits(fromToken.amount, fromToken.decimals));
28
28
  const toFromRate = Number.parseFloat(formatUnits(fromToken.amount, fromToken.decimals)) / Number.parseFloat(formatUnits(toToken.amount, toToken.decimals));
29
+ const rateText = isForward ? `1 ${fromToken.symbol} ≈ ${t("format.tokenAmount", { value: fromToRate })} ${toToken.symbol}` : `1 ${toToken.symbol} ≈ ${t("format.tokenAmount", { value: toFromRate })} ${fromToken.symbol}`;
29
30
  return /* @__PURE__ */ jsx(TokenRateTypography, {
30
31
  onClick: toggleRate,
31
32
  role: "button",
32
- children: isForward ? `1 ${fromToken.symbol} ≈ ${t("format.tokenAmount", { value: fromToRate })} ${toToken.symbol}` : `1 ${toToken.symbol} ≈ ${t("format.tokenAmount", { value: toFromRate })} ${fromToken.symbol}`
33
+ children: rateText
33
34
  });
34
35
  };
35
36
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"TokenRate.js","names":[],"sources":["../../../../src/components/TokenRate/TokenRate.tsx"],"sourcesContent":["import { formatUnits, type RouteExtended } from '@lifi/sdk'\nimport type { TypographyProps } from '@mui/material'\nimport type { MouseEventHandler } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { create } from 'zustand'\nimport { TokenRateTypography } from './TokenRate.style.js'\n\ninterface TokenRateProps extends TypographyProps {\n route: RouteExtended\n}\n\ninterface TokenRateState {\n isForward: boolean\n toggleIsForward(): void\n}\n\nconst useTokenRate = create<TokenRateState>((set) => ({\n isForward: true,\n toggleIsForward: () => set((state) => ({ isForward: !state.isForward })),\n}))\n\nexport const TokenRate: React.FC<TokenRateProps> = ({ route }) => {\n const { t } = useTranslation()\n const { isForward, toggleIsForward } = useTokenRate()\n\n const toggleRate: MouseEventHandler<HTMLSpanElement> = (e) => {\n e.stopPropagation()\n toggleIsForward()\n }\n\n const lastStep = route.steps.at(-1)\n\n const fromToken = {\n ...route.fromToken,\n amount: BigInt(route.fromAmount),\n }\n const toToken = {\n ...(lastStep?.execution?.toToken ??\n lastStep?.action.toToken ??\n route.toToken),\n amount: lastStep?.execution?.toAmount\n ? BigInt(lastStep.execution.toAmount)\n : BigInt(route.toAmount),\n }\n\n const fromToRate =\n Number.parseFloat(formatUnits(toToken.amount!, toToken.decimals)) /\n Number.parseFloat(formatUnits(fromToken.amount!, fromToken.decimals))\n const toFromRate =\n Number.parseFloat(formatUnits(fromToken.amount!, fromToken.decimals)) /\n Number.parseFloat(formatUnits(toToken.amount!, toToken.decimals))\n\n const rateText = isForward\n ? `1 ${fromToken.symbol} ≈ ${t('format.tokenAmount', { value: fromToRate })} ${toToken.symbol}`\n : `1 ${toToken.symbol} ≈ ${t('format.tokenAmount', { value: toFromRate })} ${fromToken.symbol}`\n\n return (\n <TokenRateTypography onClick={toggleRate} role=\"button\">\n {rateText}\n </TokenRateTypography>\n )\n}\n"],"mappings":";;;;;;AAgBA,MAAM,eAAe,QAAwB,SAAS;CACpD,WAAW;CACX,uBAAuB,KAAK,WAAW,EAAE,WAAW,CAAC,MAAM,UAAU,EAAE;AACzE,EAAE;AAEF,MAAa,aAAuC,EAAE,YAAY;CAChE,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,EAAE,WAAW,oBAAoB,aAAa;CAEpD,MAAM,cAAkD,MAAM;EAC5D,EAAE,gBAAgB;EAClB,gBAAgB;CAClB;CAEA,MAAM,WAAW,MAAM,MAAM,GAAG,EAAE;CAElC,MAAM,YAAY;EAChB,GAAG,MAAM;EACT,QAAQ,OAAO,MAAM,UAAU;CACjC;CACA,MAAM,UAAU;EACd,GAAI,UAAU,WAAW,WACvB,UAAU,OAAO,WACjB,MAAM;EACR,QAAQ,UAAU,WAAW,WACzB,OAAO,SAAS,UAAU,QAAQ,IAClC,OAAO,MAAM,QAAQ;CAC3B;CAEA,MAAM,aACJ,OAAO,WAAW,YAAY,QAAQ,QAAS,QAAQ,QAAQ,CAAC,IAChE,OAAO,WAAW,YAAY,UAAU,QAAS,UAAU,QAAQ,CAAC;CACtE,MAAM,aACJ,OAAO,WAAW,YAAY,UAAU,QAAS,UAAU,QAAQ,CAAC,IACpE,OAAO,WAAW,YAAY,QAAQ,QAAS,QAAQ,QAAQ,CAAC;CAMlE,OACE,oBAAC,qBAAD;EAAqB,SAAS;EAAY,MAAK;EALhC,UAAA,YACb,KAAK,UAAU,OAAO,KAAK,EAAE,sBAAsB,EAAE,OAAO,WAAW,CAAC,EAAE,GAAG,QAAQ,WACrF,KAAK,QAAQ,OAAO,KAAK,EAAE,sBAAsB,EAAE,OAAO,WAAW,CAAC,EAAE,GAAG,UAAU;CAKlE,CAAA;AAEzB"}
1
+ {"version":3,"file":"TokenRate.js","names":[],"sources":["../../../../src/components/TokenRate/TokenRate.tsx"],"sourcesContent":["import { formatUnits, type RouteExtended } from '@lifi/sdk'\nimport type { TypographyProps } from '@mui/material'\nimport type { MouseEventHandler } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { create } from 'zustand'\nimport { TokenRateTypography } from './TokenRate.style.js'\n\ninterface TokenRateProps extends TypographyProps {\n route: RouteExtended\n}\n\ninterface TokenRateState {\n isForward: boolean\n toggleIsForward(): void\n}\n\nconst useTokenRate = create<TokenRateState>((set) => ({\n isForward: true,\n toggleIsForward: () => set((state) => ({ isForward: !state.isForward })),\n}))\n\nexport const TokenRate: React.FC<TokenRateProps> = ({ route }) => {\n const { t } = useTranslation()\n const { isForward, toggleIsForward } = useTokenRate()\n\n const toggleRate: MouseEventHandler<HTMLSpanElement> = (e) => {\n e.stopPropagation()\n toggleIsForward()\n }\n\n const lastStep = route.steps.at(-1)\n\n const fromToken = {\n ...route.fromToken,\n amount: BigInt(route.fromAmount),\n }\n const toToken = {\n ...(lastStep?.execution?.toToken ??\n lastStep?.action.toToken ??\n route.toToken),\n amount: lastStep?.execution?.toAmount\n ? BigInt(lastStep.execution.toAmount)\n : BigInt(route.toAmount),\n }\n\n const fromToRate =\n Number.parseFloat(formatUnits(toToken.amount!, toToken.decimals)) /\n Number.parseFloat(formatUnits(fromToken.amount!, fromToken.decimals))\n const toFromRate =\n Number.parseFloat(formatUnits(fromToken.amount!, fromToken.decimals)) /\n Number.parseFloat(formatUnits(toToken.amount!, toToken.decimals))\n\n const rateText = isForward\n ? `1 ${fromToken.symbol} ≈ ${t('format.tokenAmount', { value: fromToRate })} ${toToken.symbol}`\n : `1 ${toToken.symbol} ≈ ${t('format.tokenAmount', { value: toFromRate })} ${fromToken.symbol}`\n\n return (\n <TokenRateTypography onClick={toggleRate} role=\"button\">\n {rateText}\n </TokenRateTypography>\n )\n}\n"],"mappings":";;;;;;AAgBA,MAAM,eAAe,QAAwB,SAAS;CACpD,WAAW;CACX,uBAAuB,KAAK,WAAW,EAAE,WAAW,CAAC,MAAM,UAAU,EAAE;AACzE,EAAE;AAEF,MAAa,aAAuC,EAAE,YAAY;CAChE,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,EAAE,WAAW,oBAAoB,aAAa;CAEpD,MAAM,cAAkD,MAAM;EAC5D,EAAE,gBAAgB;EAClB,gBAAgB;CAClB;CAEA,MAAM,WAAW,MAAM,MAAM,GAAG,EAAE;CAElC,MAAM,YAAY;EAChB,GAAG,MAAM;EACT,QAAQ,OAAO,MAAM,UAAU;CACjC;CACA,MAAM,UAAU;EACd,GAAI,UAAU,WAAW,WACvB,UAAU,OAAO,WACjB,MAAM;EACR,QAAQ,UAAU,WAAW,WACzB,OAAO,SAAS,UAAU,QAAQ,IAClC,OAAO,MAAM,QAAQ;CAC3B;CAEA,MAAM,aACJ,OAAO,WAAW,YAAY,QAAQ,QAAS,QAAQ,QAAQ,CAAC,IAChE,OAAO,WAAW,YAAY,UAAU,QAAS,UAAU,QAAQ,CAAC;CACtE,MAAM,aACJ,OAAO,WAAW,YAAY,UAAU,QAAS,UAAU,QAAQ,CAAC,IACpE,OAAO,WAAW,YAAY,QAAQ,QAAS,QAAQ,QAAQ,CAAC;CAElE,MAAM,WAAW,YACb,KAAK,UAAU,OAAO,KAAK,EAAE,sBAAsB,EAAE,OAAO,WAAW,CAAC,EAAE,GAAG,QAAQ,WACrF,KAAK,QAAQ,OAAO,KAAK,EAAE,sBAAsB,EAAE,OAAO,WAAW,CAAC,EAAE,GAAG,UAAU;CAEzF,OACE,oBAAC,qBAAD;EAAqB,SAAS;EAAY,MAAK;EAC5C,UAAA;CACkB,CAAA;AAEzB"}
@@ -19,32 +19,33 @@ const ActiveTransactionCard = ({ route, status, onClick, onDelete }) => {
19
19
  const lastActiveAction = lastActiveStep?.execution?.actions?.at(-1);
20
20
  const { title } = useActionMessage(lastActiveStep, lastActiveAction);
21
21
  const isFailed = status === 8;
22
+ const statusIcon = (() => {
23
+ if (isFailed) return /* @__PURE__ */ jsx(IconCircle, {
24
+ status: "error",
25
+ size: 24
26
+ });
27
+ if ([
28
+ "ACTION_REQUIRED",
29
+ "MESSAGE_REQUIRED",
30
+ "RESET_REQUIRED"
31
+ ].includes(lastActiveAction?.status ?? "")) return /* @__PURE__ */ jsx(IconCircle, {
32
+ status: "info",
33
+ size: 24
34
+ });
35
+ return /* @__PURE__ */ jsx(PendingCircle, { children: /* @__PURE__ */ jsx(CircularProgressPending, {
36
+ size: 24,
37
+ sx: {
38
+ top: -2,
39
+ left: -2
40
+ }
41
+ }) });
42
+ })();
22
43
  return /* @__PURE__ */ jsxs(Card$1, {
23
44
  onClick,
24
45
  indented: true,
25
46
  children: [/* @__PURE__ */ jsx(ActionRow, {
26
47
  sx: { mb: 1.5 },
27
- startAdornment: (() => {
28
- if (isFailed) return /* @__PURE__ */ jsx(IconCircle, {
29
- status: "error",
30
- size: 24
31
- });
32
- if ([
33
- "ACTION_REQUIRED",
34
- "MESSAGE_REQUIRED",
35
- "RESET_REQUIRED"
36
- ].includes(lastActiveAction?.status ?? "")) return /* @__PURE__ */ jsx(IconCircle, {
37
- status: "info",
38
- size: 24
39
- });
40
- return /* @__PURE__ */ jsx(PendingCircle, { children: /* @__PURE__ */ jsx(CircularProgressPending, {
41
- size: 24,
42
- sx: {
43
- top: -2,
44
- left: -2
45
- }
46
- }) });
47
- })(),
48
+ startAdornment: statusIcon,
48
49
  message: title ?? "",
49
50
  endAdornment: isFailed ? /* @__PURE__ */ jsx(Tooltip, {
50
51
  title: t("button.clearTransaction"),
@@ -1 +1 @@
1
- {"version":3,"file":"ActiveTransactionCard.js","names":["Card"],"sources":["../../../../src/components/TransactionCard/ActiveTransactionCard.tsx"],"sourcesContent":["import type { RouteExtended } from '@lifi/sdk'\nimport DeleteOutline from '@mui/icons-material/DeleteOutlined'\nimport { Tooltip, Typography } from '@mui/material'\nimport type { JSX, MouseEvent } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useActionMessage } from '../../hooks/useActionMessage.js'\nimport { RouteExecutionStatus } from '../../stores/routes/types.js'\nimport { getExpiryTimestamp } from '../../utils/timer.js'\nimport { ActionRow } from '../ActionRow/ActionRow.js'\nimport { Card } from '../Card/Card.js'\nimport { IconCircle } from '../IconCircle/IconCircle.js'\nimport { RouteTokens } from '../RouteCard/RouteTokens.js'\nimport { ExecutionTimerText } from '../Timer/ExecutionTimerText.js'\nimport { CircularProgressPending } from '../Timer/StepStatusTimer.style.js'\nimport { DeleteButton, PendingCircle } from './ActiveTransactionCard.style.js'\n\ninterface ActiveTransactionCardProps {\n route: RouteExtended\n status?: RouteExecutionStatus\n onClick: () => void\n onDelete: (e: MouseEvent) => void\n}\n\nexport const ActiveTransactionCard = ({\n route,\n status,\n onClick,\n onDelete,\n}: ActiveTransactionCardProps): JSX.Element => {\n const { t } = useTranslation()\n\n const lastActiveStep = route.steps.findLast((step) => step.execution)\n const lastActiveAction = lastActiveStep?.execution?.actions?.at(-1)\n const { title } = useActionMessage(lastActiveStep, lastActiveAction)\n\n const isFailed = status === RouteExecutionStatus.Failed\n\n const statusIcon = (() => {\n if (isFailed) {\n return <IconCircle status=\"error\" size={24} />\n }\n if (\n ['ACTION_REQUIRED', 'MESSAGE_REQUIRED', 'RESET_REQUIRED'].includes(\n lastActiveAction?.status ?? ''\n )\n ) {\n return <IconCircle status=\"info\" size={24} />\n }\n return (\n <PendingCircle>\n <CircularProgressPending size={24} sx={{ top: -2, left: -2 }} />\n </PendingCircle>\n )\n })()\n\n return (\n <Card onClick={onClick} indented>\n <ActionRow\n sx={{ mb: 1.5 }}\n startAdornment={statusIcon}\n message={title ?? ''}\n endAdornment={\n isFailed ? (\n <Tooltip title={t('button.clearTransaction')}>\n <DeleteButton size=\"small\" onClick={onDelete}>\n <DeleteOutline sx={{ fontSize: 16 }} />\n </DeleteButton>\n </Tooltip>\n ) : lastActiveStep?.execution?.signedAt ? (\n <Typography sx={{ fontSize: 12, fontWeight: 600 }}>\n <ExecutionTimerText\n expiryTimestamp={getExpiryTimestamp(lastActiveStep)}\n />\n </Typography>\n ) : undefined\n }\n />\n <RouteTokens route={route} />\n </Card>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuBA,MAAa,yBAAyB,EACpC,OACA,QACA,SACA,eAC6C;CAC7C,MAAM,EAAE,MAAM,eAAe;CAE7B,MAAM,iBAAiB,MAAM,MAAM,UAAU,SAAS,KAAK,SAAS;CACpE,MAAM,mBAAmB,gBAAgB,WAAW,SAAS,GAAG,EAAE;CAClE,MAAM,EAAE,UAAU,iBAAiB,gBAAgB,gBAAgB;CAEnE,MAAM,WAAW,WAAA;CAoBjB,OACE,qBAACA,QAAD;EAAe;EAAS,UAAA;EAAxB,UAAA,CACE,oBAAC,WAAD;GACE,IAAI,EAAE,IAAI,IAAI;GACd,uBAtBoB;IACxB,IAAI,UACF,OAAO,oBAAC,YAAD;KAAY,QAAO;KAAQ,MAAM;IAAK,CAAA;IAE/C,IACE;KAAC;KAAmB;KAAoB;IAAgB,CAAC,CAAC,SACxD,kBAAkB,UAAU,EAC9B,GAEA,OAAO,oBAAC,YAAD;KAAY,QAAO;KAAO,MAAM;IAAK,CAAA;IAE9C,OACE,oBAAC,eAAD,EAAA,UACE,oBAAC,yBAAD;KAAyB,MAAM;KAAI,IAAI;MAAE,KAAK;MAAI,MAAM;KAAG;IAAI,CAAA,EAClD,CAAA;GAEnB,EAAA,CAM+B;GACzB,SAAS,SAAS;GAClB,cACE,WACE,oBAAC,SAAD;IAAS,OAAO,EAAE,yBAAyB;IACzC,UAAA,oBAAC,cAAD;KAAc,MAAK;KAAQ,SAAS;KAClC,UAAA,oBAAC,eAAD,EAAe,IAAI,EAAE,UAAU,GAAG,EAAI,CAAA;IAC1B,CAAA;GACP,CAAA,IACP,gBAAgB,WAAW,WAC7B,oBAAC,YAAD;IAAY,IAAI;KAAE,UAAU;KAAI,YAAY;IAAI;IAC9C,UAAA,oBAAC,oBAAD,EACE,iBAAiB,mBAAmB,cAAc,EACnD,CAAA;GACS,CAAA,IACV,KAAA;EAEP,CAAA,GACD,oBAAC,aAAD,EAAoB,MAAQ,CAAA,CACxB;;AAEV"}
1
+ {"version":3,"file":"ActiveTransactionCard.js","names":["Card"],"sources":["../../../../src/components/TransactionCard/ActiveTransactionCard.tsx"],"sourcesContent":["import type { RouteExtended } from '@lifi/sdk'\nimport DeleteOutline from '@mui/icons-material/DeleteOutlined'\nimport { Tooltip, Typography } from '@mui/material'\nimport type { JSX, MouseEvent } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useActionMessage } from '../../hooks/useActionMessage.js'\nimport { RouteExecutionStatus } from '../../stores/routes/types.js'\nimport { getExpiryTimestamp } from '../../utils/timer.js'\nimport { ActionRow } from '../ActionRow/ActionRow.js'\nimport { Card } from '../Card/Card.js'\nimport { IconCircle } from '../IconCircle/IconCircle.js'\nimport { RouteTokens } from '../RouteCard/RouteTokens.js'\nimport { ExecutionTimerText } from '../Timer/ExecutionTimerText.js'\nimport { CircularProgressPending } from '../Timer/StepStatusTimer.style.js'\nimport { DeleteButton, PendingCircle } from './ActiveTransactionCard.style.js'\n\ninterface ActiveTransactionCardProps {\n route: RouteExtended\n status?: RouteExecutionStatus\n onClick: () => void\n onDelete: (e: MouseEvent) => void\n}\n\nexport const ActiveTransactionCard = ({\n route,\n status,\n onClick,\n onDelete,\n}: ActiveTransactionCardProps): JSX.Element => {\n const { t } = useTranslation()\n\n const lastActiveStep = route.steps.findLast((step) => step.execution)\n const lastActiveAction = lastActiveStep?.execution?.actions?.at(-1)\n const { title } = useActionMessage(lastActiveStep, lastActiveAction)\n\n const isFailed = status === RouteExecutionStatus.Failed\n\n const statusIcon = (() => {\n if (isFailed) {\n return <IconCircle status=\"error\" size={24} />\n }\n if (\n ['ACTION_REQUIRED', 'MESSAGE_REQUIRED', 'RESET_REQUIRED'].includes(\n lastActiveAction?.status ?? ''\n )\n ) {\n return <IconCircle status=\"info\" size={24} />\n }\n return (\n <PendingCircle>\n <CircularProgressPending size={24} sx={{ top: -2, left: -2 }} />\n </PendingCircle>\n )\n })()\n\n return (\n <Card onClick={onClick} indented>\n <ActionRow\n sx={{ mb: 1.5 }}\n startAdornment={statusIcon}\n message={title ?? ''}\n endAdornment={\n isFailed ? (\n <Tooltip title={t('button.clearTransaction')}>\n <DeleteButton size=\"small\" onClick={onDelete}>\n <DeleteOutline sx={{ fontSize: 16 }} />\n </DeleteButton>\n </Tooltip>\n ) : lastActiveStep?.execution?.signedAt ? (\n <Typography sx={{ fontSize: 12, fontWeight: 600 }}>\n <ExecutionTimerText\n expiryTimestamp={getExpiryTimestamp(lastActiveStep)}\n />\n </Typography>\n ) : undefined\n }\n />\n <RouteTokens route={route} />\n </Card>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuBA,MAAa,yBAAyB,EACpC,OACA,QACA,SACA,eAC6C;CAC7C,MAAM,EAAE,MAAM,eAAe;CAE7B,MAAM,iBAAiB,MAAM,MAAM,UAAU,SAAS,KAAK,SAAS;CACpE,MAAM,mBAAmB,gBAAgB,WAAW,SAAS,GAAG,EAAE;CAClE,MAAM,EAAE,UAAU,iBAAiB,gBAAgB,gBAAgB;CAEnE,MAAM,WAAW,WAAA;CAEjB,MAAM,oBAAoB;EACxB,IAAI,UACF,OAAO,oBAAC,YAAD;GAAY,QAAO;GAAQ,MAAM;EAAK,CAAA;EAE/C,IACE;GAAC;GAAmB;GAAoB;EAAgB,CAAC,CAAC,SACxD,kBAAkB,UAAU,EAC9B,GAEA,OAAO,oBAAC,YAAD;GAAY,QAAO;GAAO,MAAM;EAAK,CAAA;EAE9C,OACE,oBAAC,eAAD,EAAA,UACE,oBAAC,yBAAD;GAAyB,MAAM;GAAI,IAAI;IAAE,KAAK;IAAI,MAAM;GAAG;EAAI,CAAA,EAClD,CAAA;CAEnB,EAAA,CAAG;CAEH,OACE,qBAACA,QAAD;EAAe;EAAS,UAAA;EAAxB,UAAA,CACE,oBAAC,WAAD;GACE,IAAI,EAAE,IAAI,IAAI;GACd,gBAAgB;GAChB,SAAS,SAAS;GAClB,cACE,WACE,oBAAC,SAAD;IAAS,OAAO,EAAE,yBAAyB;IACzC,UAAA,oBAAC,cAAD;KAAc,MAAK;KAAQ,SAAS;KAClC,UAAA,oBAAC,eAAD,EAAe,IAAI,EAAE,UAAU,GAAG,EAAI,CAAA;IAC1B,CAAA;GACP,CAAA,IACP,gBAAgB,WAAW,WAC7B,oBAAC,YAAD;IAAY,IAAI;KAAE,UAAU;KAAI,YAAY;IAAI;IAC9C,UAAA,oBAAC,oBAAD,EACE,iBAAiB,mBAAmB,cAAc,EACnD,CAAA;GACS,CAAA,IACV,KAAA;EAEP,CAAA,GACD,oBAAC,aAAD,EAAoB,MAAQ,CAAA,CACxB;;AAEV"}
@@ -1,6 +1,6 @@
1
1
  //#region src/config/version.d.ts
2
2
  declare const name = "@lifi/widget";
3
- declare const version = "4.5.0";
3
+ declare const version = "4.6.0";
4
4
  //#endregion
5
5
  export { name, version };
6
6
  //# sourceMappingURL=version.d.ts.map
@@ -1,6 +1,6 @@
1
1
  //#region src/config/version.ts
2
2
  const name = "@lifi/widget";
3
- const version = "4.5.0";
3
+ const version = "4.6.0";
4
4
  //#endregion
5
5
  export { name, version };
6
6
 
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","names":[],"sources":["../../../src/config/version.ts"],"sourcesContent":["export const name = '@lifi/widget'\nexport const version = '4.5.0'\n"],"mappings":";AAAA,MAAa,OAAO;AACpB,MAAa,UAAU"}
1
+ {"version":3,"file":"version.js","names":[],"sources":["../../../src/config/version.ts"],"sourcesContent":["export const name = '@lifi/widget'\nexport const version = '4.6.0'\n"],"mappings":";AAAA,MAAa,OAAO;AACpB,MAAa,UAAU"}
@@ -1,8 +1,8 @@
1
1
  //#region src/hooks/timer/time.ts
2
2
  function getTimeFromMilliseconds(millisecs, isCountDown = true) {
3
3
  const totalSeconds = isCountDown ? Math.ceil(millisecs / 1e3) : Math.floor(millisecs / 1e3);
4
- const days = Math.floor(totalSeconds / (3600 * 24));
5
- const hours = Math.floor(totalSeconds % (3600 * 24) / 3600);
4
+ const days = Math.floor(totalSeconds / 86400);
5
+ const hours = Math.floor(totalSeconds % 86400 / 3600);
6
6
  const minutes = Math.floor(totalSeconds % 3600 / 60);
7
7
  const seconds = Math.floor(totalSeconds % 60);
8
8
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"time.js","names":[],"sources":["../../../../src/hooks/timer/time.ts"],"sourcesContent":["export type TimeFromMillisecondsType = {\n totalMilliseconds: number\n totalSeconds: number\n milliseconds: number\n seconds: number\n minutes: number\n hours: number\n days: number\n}\n\nexport function getTimeFromMilliseconds(\n millisecs: number,\n isCountDown = true\n): TimeFromMillisecondsType {\n const totalSeconds = isCountDown\n ? Math.ceil(millisecs / 1000)\n : Math.floor(millisecs / 1000)\n const days = Math.floor(totalSeconds / (60 * 60 * 24))\n const hours = Math.floor((totalSeconds % (60 * 60 * 24)) / (60 * 60))\n const minutes = Math.floor((totalSeconds % (60 * 60)) / 60)\n const seconds = Math.floor(totalSeconds % 60)\n const milliseconds = Math.floor(millisecs % 1000)\n\n return {\n totalMilliseconds: millisecs,\n totalSeconds,\n milliseconds,\n seconds,\n minutes,\n hours,\n days,\n }\n}\n\nexport function getMillisecondsFromExpiry(expiry: Date): number {\n const now = Date.now()\n const milliSecondsDistance = expiry?.getTime() - now\n return milliSecondsDistance > 0 ? milliSecondsDistance : 0\n}\n"],"mappings":";AAUA,SAAgB,wBACd,WACA,cAAc,MACY;CAC1B,MAAM,eAAe,cACjB,KAAK,KAAK,YAAY,GAAI,IAC1B,KAAK,MAAM,YAAY,GAAI;CAC/B,MAAM,OAAO,KAAK,MAAM,gBAAgB,OAAU,GAAG;CACrD,MAAM,QAAQ,KAAK,MAAO,gBAAgB,OAAU,MAAQ,IAAQ;CACpE,MAAM,UAAU,KAAK,MAAO,eAAgB,OAAY,EAAE;CAC1D,MAAM,UAAU,KAAK,MAAM,eAAe,EAAE;CAG5C,OAAO;EACL,mBAAmB;EACnB;EACA,cALmB,KAAK,MAAM,YAAY,GAK/B;EACX;EACA;EACA;EACA;CACF;AACF;AAEA,SAAgB,0BAA0B,QAAsB;CAC9D,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,uBAAuB,QAAQ,QAAQ,IAAI;CACjD,OAAO,uBAAuB,IAAI,uBAAuB;AAC3D"}
1
+ {"version":3,"file":"time.js","names":[],"sources":["../../../../src/hooks/timer/time.ts"],"sourcesContent":["export type TimeFromMillisecondsType = {\n totalMilliseconds: number\n totalSeconds: number\n milliseconds: number\n seconds: number\n minutes: number\n hours: number\n days: number\n}\n\nexport function getTimeFromMilliseconds(\n millisecs: number,\n isCountDown = true\n): TimeFromMillisecondsType {\n const totalSeconds = isCountDown\n ? Math.ceil(millisecs / 1000)\n : Math.floor(millisecs / 1000)\n const days = Math.floor(totalSeconds / (60 * 60 * 24))\n const hours = Math.floor((totalSeconds % (60 * 60 * 24)) / (60 * 60))\n const minutes = Math.floor((totalSeconds % (60 * 60)) / 60)\n const seconds = Math.floor(totalSeconds % 60)\n const milliseconds = Math.floor(millisecs % 1000)\n\n return {\n totalMilliseconds: millisecs,\n totalSeconds,\n milliseconds,\n seconds,\n minutes,\n hours,\n days,\n }\n}\n\nexport function getMillisecondsFromExpiry(expiry: Date): number {\n const now = Date.now()\n const milliSecondsDistance = expiry?.getTime() - now\n return milliSecondsDistance > 0 ? milliSecondsDistance : 0\n}\n"],"mappings":";AAUA,SAAgB,wBACd,WACA,cAAc,MACY;CAC1B,MAAM,eAAe,cACjB,KAAK,KAAK,YAAY,GAAI,IAC1B,KAAK,MAAM,YAAY,GAAI;CAC/B,MAAM,OAAO,KAAK,MAAM,eAAgB,KAAa;CACrD,MAAM,QAAQ,KAAK,MAAO,eAAgB,QAAkB,IAAQ;CACpE,MAAM,UAAU,KAAK,MAAO,eAAgB,OAAY,EAAE;CAC1D,MAAM,UAAU,KAAK,MAAM,eAAe,EAAE;CAG5C,OAAO;EACL,mBAAmB;EACnB;EACA,cALmB,KAAK,MAAM,YAAY,GAK/B;EACX;EACA;EACA;EACA;CACF;AACF;AAEA,SAAgB,0BAA0B,QAAsB;CAC9D,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,uBAAuB,QAAQ,QAAQ,IAAI;CACjD,OAAO,uBAAuB,IAAI,uBAAuB;AAC3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"useAvailableChains.d.ts","names":[],"sources":["../../../src/hooks/useAvailableChains.ts"],"mappings":";;;KAUK,gBACH,kBACA,SAAS,oBACN;cAUQ,qBACX,aAAa,aACb,eAAe;EAEf,QAAQ;EACR,cAAc;EACd"}
1
+ {"version":3,"file":"useAvailableChains.d.ts","names":[],"sources":["../../../src/hooks/useAvailableChains.ts"],"mappings":";;;KAUK,gBACH,kBACA,SAAS,oBACN;cAWQ,qBACX,aAAa,aACb,eAAe;EAEf,QAAQ;EACR,cAAc;EACd"}
@@ -11,7 +11,8 @@ const supportedChainTypes = [
11
11
  ChainType.SVM,
12
12
  ChainType.UTXO,
13
13
  ChainType.MVM,
14
- ChainType.TVM
14
+ ChainType.TVM,
15
+ ChainType.STL
15
16
  ];
16
17
  const useAvailableChains = (chainTypes, widgetConfig) => {
17
18
  const { chains: internalChains, keyPrefix: internalKeyPrefix } = useWidgetConfig();
@@ -1 +1 @@
1
- {"version":3,"file":"useAvailableChains.js","names":[],"sources":["../../../src/hooks/useAvailableChains.ts"],"sourcesContent":["import type { ExtendedChain } from '@lifi/sdk'\nimport { ChainType, createClient, getChains } from '@lifi/sdk'\nimport { useQuery } from '@tanstack/react-query'\nimport { useCallback, useEffect, useMemo } from 'react'\nimport { useSDKClient } from '../providers/SDKClientProvider.js'\nimport { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'\nimport type { WidgetConfig } from '../types/widget.js'\nimport { getConfigItemSets, isItemAllowedForSets } from '../utils/item.js'\nimport { getQueryKey } from '../utils/queries.js'\n\ntype GetChainById = (\n chainId?: number,\n chains?: ExtendedChain[]\n) => ExtendedChain | undefined\n\nconst supportedChainTypes = [\n ChainType.EVM,\n ChainType.SVM,\n ChainType.UTXO,\n ChainType.MVM,\n ChainType.TVM,\n]\n\nexport const useAvailableChains = (\n chainTypes?: ChainType[],\n widgetConfig?: WidgetConfig\n): {\n chains: ExtendedChain[] | undefined\n getChainById: GetChainById\n isLoading: boolean\n} => {\n const { chains: internalChains, keyPrefix: internalKeyPrefix } =\n useWidgetConfig()\n const internalClient = useSDKClient()\n\n const externalClient = useMemo(() => {\n if (!widgetConfig) {\n return undefined\n }\n return createClient({\n ...widgetConfig.sdkConfig,\n apiKey: widgetConfig.apiKey,\n integrator: widgetConfig.integrator ?? window?.location.hostname,\n })\n }, [widgetConfig])\n\n // Overwrite widget config if passed as param\n const keyPrefix = widgetConfig?.keyPrefix ?? internalKeyPrefix\n const chains = widgetConfig?.chains ?? internalChains\n\n const { data, isLoading } = useQuery({\n queryKey: [\n getQueryKey('chains', keyPrefix),\n chains?.types,\n chains?.allow,\n chains?.deny,\n chains?.from,\n chains?.to,\n !!externalClient,\n ] as const,\n queryFn: async ({ queryKey: [, chainTypesConfig] }) => {\n const chainsConfigSets = getConfigItemSets(\n chainTypesConfig,\n (chains) => new Set(chains)\n )\n const chainTypesRequest = supportedChainTypes.filter((chainType) =>\n isItemAllowedForSets(chainType, chainsConfigSets)\n )\n const client = externalClient ?? internalClient\n const availableChains = await getChains(client, {\n chainTypes: chainTypes || chainTypesRequest,\n })\n client.setChains(availableChains)\n return availableChains\n },\n refetchInterval: 300_000,\n staleTime: 300_000,\n })\n\n // Ensure the current internal client always has chains, even when:\n // - the query result came from cache (external call populated it first)\n // - the client was recreated due to config changes (stale closure in queryFn)\n useEffect(() => {\n if (data && !externalClient) {\n internalClient.setChains(data)\n }\n }, [data, externalClient, internalClient])\n\n const getChainById: GetChainById = useCallback(\n (chainId?: number, chains: ExtendedChain[] | undefined = data) => {\n if (!chainId) {\n return\n }\n const chain = chains?.find((chain) => chain.id === chainId)\n // if (!chain) {\n // throw new Error('Chain not found or chainId is invalid.');\n // }\n return chain\n },\n [data]\n )\n\n return {\n chains: data,\n getChainById,\n isLoading,\n }\n}\n"],"mappings":";;;;;;;;AAeA,MAAM,sBAAsB;CAC1B,UAAU;CACV,UAAU;CACV,UAAU;CACV,UAAU;CACV,UAAU;AACZ;AAEA,MAAa,sBACX,YACA,iBAKG;CACH,MAAM,EAAE,QAAQ,gBAAgB,WAAW,sBACzC,gBAAgB;CAClB,MAAM,iBAAiB,aAAa;CAEpC,MAAM,iBAAiB,cAAc;EACnC,IAAI,CAAC,cACH;EAEF,OAAO,aAAa;GAClB,GAAG,aAAa;GAChB,QAAQ,aAAa;GACrB,YAAY,aAAa,cAAc,QAAQ,SAAS;EAC1D,CAAC;CACH,GAAG,CAAC,YAAY,CAAC;CAGjB,MAAM,YAAY,cAAc,aAAa;CAC7C,MAAM,SAAS,cAAc,UAAU;CAEvC,MAAM,EAAE,MAAM,cAAc,SAAS;EACnC,UAAU;GACR,YAAY,UAAU,SAAS;GAC/B,QAAQ;GACR,QAAQ;GACR,QAAQ;GACR,QAAQ;GACR,QAAQ;GACR,CAAC,CAAC;EACJ;EACA,SAAS,OAAO,EAAE,UAAU,GAAG,wBAAwB;GACrD,MAAM,mBAAmB,kBACvB,mBACC,WAAW,IAAI,IAAI,MAAM,CAC5B;GACA,MAAM,oBAAoB,oBAAoB,QAAQ,cACpD,qBAAqB,WAAW,gBAAgB,CAClD;GACA,MAAM,SAAS,kBAAkB;GACjC,MAAM,kBAAkB,MAAM,UAAU,QAAQ,EAC9C,YAAY,cAAc,kBAC5B,CAAC;GACD,OAAO,UAAU,eAAe;GAChC,OAAO;EACT;EACA,iBAAiB;EACjB,WAAW;CACb,CAAC;CAKD,gBAAgB;EACd,IAAI,QAAQ,CAAC,gBACX,eAAe,UAAU,IAAI;CAEjC,GAAG;EAAC;EAAM;EAAgB;CAAc,CAAC;CAgBzC,OAAO;EACL,QAAQ;EACR,cAhBiC,aAChC,SAAkB,SAAsC,SAAS;GAChE,IAAI,CAAC,SACH;GAMF,OAJc,QAAQ,MAAM,UAAU,MAAM,OAAO,OAAO;EAK5D,GACA,CAAC,IAAI,CAKM;EACX;CACF;AACF"}
1
+ {"version":3,"file":"useAvailableChains.js","names":[],"sources":["../../../src/hooks/useAvailableChains.ts"],"sourcesContent":["import type { ExtendedChain } from '@lifi/sdk'\nimport { ChainType, createClient, getChains } from '@lifi/sdk'\nimport { useQuery } from '@tanstack/react-query'\nimport { useCallback, useEffect, useMemo } from 'react'\nimport { useSDKClient } from '../providers/SDKClientProvider.js'\nimport { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'\nimport type { WidgetConfig } from '../types/widget.js'\nimport { getConfigItemSets, isItemAllowedForSets } from '../utils/item.js'\nimport { getQueryKey } from '../utils/queries.js'\n\ntype GetChainById = (\n chainId?: number,\n chains?: ExtendedChain[]\n) => ExtendedChain | undefined\n\nconst supportedChainTypes = [\n ChainType.EVM,\n ChainType.SVM,\n ChainType.UTXO,\n ChainType.MVM,\n ChainType.TVM,\n ChainType.STL,\n]\n\nexport const useAvailableChains = (\n chainTypes?: ChainType[],\n widgetConfig?: WidgetConfig\n): {\n chains: ExtendedChain[] | undefined\n getChainById: GetChainById\n isLoading: boolean\n} => {\n const { chains: internalChains, keyPrefix: internalKeyPrefix } =\n useWidgetConfig()\n const internalClient = useSDKClient()\n\n const externalClient = useMemo(() => {\n if (!widgetConfig) {\n return undefined\n }\n return createClient({\n ...widgetConfig.sdkConfig,\n apiKey: widgetConfig.apiKey,\n integrator: widgetConfig.integrator ?? window?.location.hostname,\n })\n }, [widgetConfig])\n\n // Overwrite widget config if passed as param\n const keyPrefix = widgetConfig?.keyPrefix ?? internalKeyPrefix\n const chains = widgetConfig?.chains ?? internalChains\n\n const { data, isLoading } = useQuery({\n queryKey: [\n getQueryKey('chains', keyPrefix),\n chains?.types,\n chains?.allow,\n chains?.deny,\n chains?.from,\n chains?.to,\n !!externalClient,\n ] as const,\n queryFn: async ({ queryKey: [, chainTypesConfig] }) => {\n const chainsConfigSets = getConfigItemSets(\n chainTypesConfig,\n (chains) => new Set(chains)\n )\n const chainTypesRequest = supportedChainTypes.filter((chainType) =>\n isItemAllowedForSets(chainType, chainsConfigSets)\n )\n const client = externalClient ?? internalClient\n const availableChains = await getChains(client, {\n chainTypes: chainTypes || chainTypesRequest,\n })\n client.setChains(availableChains)\n return availableChains\n },\n refetchInterval: 300_000,\n staleTime: 300_000,\n })\n\n // Ensure the current internal client always has chains, even when:\n // - the query result came from cache (external call populated it first)\n // - the client was recreated due to config changes (stale closure in queryFn)\n useEffect(() => {\n if (data && !externalClient) {\n internalClient.setChains(data)\n }\n }, [data, externalClient, internalClient])\n\n const getChainById: GetChainById = useCallback(\n (chainId?: number, chains: ExtendedChain[] | undefined = data) => {\n if (!chainId) {\n return\n }\n const chain = chains?.find((chain) => chain.id === chainId)\n // if (!chain) {\n // throw new Error('Chain not found or chainId is invalid.');\n // }\n return chain\n },\n [data]\n )\n\n return {\n chains: data,\n getChainById,\n isLoading,\n }\n}\n"],"mappings":";;;;;;;;AAeA,MAAM,sBAAsB;CAC1B,UAAU;CACV,UAAU;CACV,UAAU;CACV,UAAU;CACV,UAAU;CACV,UAAU;AACZ;AAEA,MAAa,sBACX,YACA,iBAKG;CACH,MAAM,EAAE,QAAQ,gBAAgB,WAAW,sBACzC,gBAAgB;CAClB,MAAM,iBAAiB,aAAa;CAEpC,MAAM,iBAAiB,cAAc;EACnC,IAAI,CAAC,cACH;EAEF,OAAO,aAAa;GAClB,GAAG,aAAa;GAChB,QAAQ,aAAa;GACrB,YAAY,aAAa,cAAc,QAAQ,SAAS;EAC1D,CAAC;CACH,GAAG,CAAC,YAAY,CAAC;CAGjB,MAAM,YAAY,cAAc,aAAa;CAC7C,MAAM,SAAS,cAAc,UAAU;CAEvC,MAAM,EAAE,MAAM,cAAc,SAAS;EACnC,UAAU;GACR,YAAY,UAAU,SAAS;GAC/B,QAAQ;GACR,QAAQ;GACR,QAAQ;GACR,QAAQ;GACR,QAAQ;GACR,CAAC,CAAC;EACJ;EACA,SAAS,OAAO,EAAE,UAAU,GAAG,wBAAwB;GACrD,MAAM,mBAAmB,kBACvB,mBACC,WAAW,IAAI,IAAI,MAAM,CAC5B;GACA,MAAM,oBAAoB,oBAAoB,QAAQ,cACpD,qBAAqB,WAAW,gBAAgB,CAClD;GACA,MAAM,SAAS,kBAAkB;GACjC,MAAM,kBAAkB,MAAM,UAAU,QAAQ,EAC9C,YAAY,cAAc,kBAC5B,CAAC;GACD,OAAO,UAAU,eAAe;GAChC,OAAO;EACT;EACA,iBAAiB;EACjB,WAAW;CACb,CAAC;CAKD,gBAAgB;EACd,IAAI,QAAQ,CAAC,gBACX,eAAe,UAAU,IAAI;CAEjC,GAAG;EAAC;EAAM;EAAgB;CAAc,CAAC;CAgBzC,OAAO;EACL,QAAQ;EACR,cAhBiC,aAChC,SAAkB,SAAsC,SAAS;GAChE,IAAI,CAAC,SACH;GAMF,OAJc,QAAQ,MAAM,UAAU,MAAM,OAAO,OAAO;EAK5D,GACA,CAAC,IAAI,CAKM;EACX;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useExplorer.d.ts","names":[],"sources":["../../../src/hooks/useExplorer.ts"],"mappings":";;KAiBK;EACH,QAAQ;EACR;EACA;;cAGW;EACX,qBAAqB,OAAO;EAC5B,iBAAiB,iBAAiB,QAAQ"}
1
+ {"version":3,"file":"useExplorer.d.ts","names":[],"sources":["../../../src/hooks/useExplorer.ts"],"mappings":";;KAmBK;EACH,QAAQ;EACR;EACA;;cAGW;EACX,qBAAqB,OAAO;EAC5B,iBAAiB,iBAAiB,QAAQ"}
@@ -17,6 +17,10 @@ const explorerPathOverrides = {
17
17
  [ChainType.TVM]: {
18
18
  txPath: "#/transaction",
19
19
  addressPath: "#/address"
20
+ },
21
+ [ChainType.STL]: {
22
+ txPath: "tx",
23
+ addressPath: "account"
20
24
  }
21
25
  };
22
26
  const useExplorer = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"useExplorer.js","names":[],"sources":["../../../src/hooks/useExplorer.ts"],"sourcesContent":["import type { Chain } from '@lifi/sdk'\nimport { ChainId, ChainType, isHex } from '@lifi/sdk'\nimport { useCallback } from 'react'\nimport { internalExplorerUrl } from '../config/constants.js'\nimport { useAvailableChains } from '../hooks/useAvailableChains.js'\nimport { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'\n\nconst sanitiseBaseUrl = (baseUrl: string) => baseUrl.trim().replace(/\\/+$/, '')\n\nconst explorerPathOverrides: Partial<\n Record<ChainType | ChainId, { txPath: string; addressPath: string }>\n> = {\n [ChainId.SUI]: { txPath: 'txblock', addressPath: 'coin' },\n [ChainId.LTR]: { txPath: 'logs', addressPath: 'accounts' },\n [ChainType.TVM]: { txPath: '#/transaction', addressPath: '#/address' },\n}\n\ntype TransactionLinkProps = {\n chain?: Chain | number\n txHash?: string\n txLink?: string\n}\n\nexport const useExplorer = (): {\n getTransactionLink: (props: TransactionLinkProps) => string | undefined\n getAddressLink: (address: string, chain?: Chain | number) => string\n} => {\n const { explorerUrls } = useWidgetConfig()\n const { getChainById } = useAvailableChains()\n\n const getExplorerConfig = useCallback(\n (chain?: Chain | number) => {\n const resolvedChain = Number.isFinite(chain)\n ? getChainById(chain as number)\n : (chain as Chain)\n\n const explorerUrl =\n (resolvedChain\n ? (explorerUrls?.[resolvedChain.id]?.[0] ??\n resolvedChain.metamask.blockExplorerUrls[0])\n : explorerUrls?.internal?.[0]) || internalExplorerUrl\n\n const baseUrl =\n typeof explorerUrl === 'string' ? explorerUrl : explorerUrl.url\n\n const overrides =\n explorerPathOverrides[resolvedChain?.id as ChainId] ??\n explorerPathOverrides[resolvedChain?.chainType as ChainType]\n\n const defaultTxPath = overrides?.txPath ?? 'tx'\n const defaultAddressPath = overrides?.addressPath ?? 'address'\n const txPath =\n typeof explorerUrl === 'string'\n ? defaultTxPath\n : explorerUrl.txPath || defaultTxPath\n const addressPath =\n typeof explorerUrl === 'string'\n ? defaultAddressPath\n : explorerUrl.addressPath || defaultAddressPath\n\n return {\n url: sanitiseBaseUrl(baseUrl),\n txPath,\n addressPath,\n resolvedChain,\n hasOverride: Boolean(overrides),\n }\n },\n [explorerUrls, getChainById]\n )\n\n const getTransactionLink = useCallback(\n ({ txHash, txLink, chain }: TransactionLinkProps): string | undefined => {\n if (txHash) {\n const config = getExplorerConfig(chain)\n // For EVM chains, sanity-check the transaction hash as some relayers return custom task hashes that are not visible on-chain.\n // Skip the check when a chain-specific path override is registered — that's an explicit signal the chain's\n // explorer accepts a non-hex hash (e.g. Lighter, whose hash has no 0x prefix).\n const validForEvm =\n config.resolvedChain?.chainType !== ChainType.EVM ||\n config.hasOverride ||\n isHex(txHash, { strict: true })\n if (validForEvm) {\n return `${config.url}/${config.txPath}/${txHash}`\n }\n }\n return txLink\n },\n [getExplorerConfig]\n )\n\n const getAddressLink = useCallback(\n (address: string, chain?: Chain | number) => {\n const config = getExplorerConfig(chain)\n return `${config.url}/${config.addressPath}/${address}`\n },\n [getExplorerConfig]\n )\n\n return {\n getTransactionLink,\n getAddressLink,\n }\n}\n"],"mappings":";;;;;;AAOA,MAAM,mBAAmB,YAAoB,QAAQ,KAAK,CAAC,CAAC,QAAQ,QAAQ,EAAE;AAE9E,MAAM,wBAEF;EACD,QAAQ,MAAM;EAAE,QAAQ;EAAW,aAAa;CAAO;EACvD,QAAQ,MAAM;EAAE,QAAQ;EAAQ,aAAa;CAAW;EACxD,UAAU,MAAM;EAAE,QAAQ;EAAiB,aAAa;CAAY;AACvE;AAQA,MAAa,oBAGR;CACH,MAAM,EAAE,iBAAiB,gBAAgB;CACzC,MAAM,EAAE,iBAAiB,mBAAmB;CAE5C,MAAM,oBAAoB,aACvB,UAA2B;EAC1B,MAAM,gBAAgB,OAAO,SAAS,KAAK,IACvC,aAAa,KAAe,IAC3B;EAEL,MAAM,eACH,gBACI,eAAe,cAAc,GAAG,GAAG,MACpC,cAAc,SAAS,kBAAkB,KACzC,cAAc,WAAW,OAAA;EAE/B,MAAM,UACJ,OAAO,gBAAgB,WAAW,cAAc,YAAY;EAE9D,MAAM,YACJ,sBAAsB,eAAe,OACrC,sBAAsB,eAAe;EAEvC,MAAM,gBAAgB,WAAW,UAAU;EAC3C,MAAM,qBAAqB,WAAW,eAAe;EACrD,MAAM,SACJ,OAAO,gBAAgB,WACnB,gBACA,YAAY,UAAU;EAC5B,MAAM,cACJ,OAAO,gBAAgB,WACnB,qBACA,YAAY,eAAe;EAEjC,OAAO;GACL,KAAK,gBAAgB,OAAO;GAC5B;GACA;GACA;GACA,aAAa,QAAQ,SAAS;EAChC;CACF,GACA,CAAC,cAAc,YAAY,CAC7B;CA8BA,OAAO;EACL,oBA7ByB,aACxB,EAAE,QAAQ,QAAQ,YAAsD;GACvE,IAAI,QAAQ;IACV,MAAM,SAAS,kBAAkB,KAAK;IAQtC,IAHE,OAAO,eAAe,cAAc,UAAU,OAC9C,OAAO,eACP,MAAM,QAAQ,EAAE,QAAQ,KAAK,CAAC,GAE9B,OAAO,GAAG,OAAO,IAAI,GAAG,OAAO,OAAO,GAAG;GAE7C;GACA,OAAO;EACT,GACA,CAAC,iBAAiB,CAYD;EACjB,gBAVqB,aACpB,SAAiB,UAA2B;GAC3C,MAAM,SAAS,kBAAkB,KAAK;GACtC,OAAO,GAAG,OAAO,IAAI,GAAG,OAAO,YAAY,GAAG;EAChD,GACA,CAAC,iBAAiB,CAKL;CACf;AACF"}
1
+ {"version":3,"file":"useExplorer.js","names":[],"sources":["../../../src/hooks/useExplorer.ts"],"sourcesContent":["import type { Chain } from '@lifi/sdk'\nimport { ChainId, ChainType, isHex } from '@lifi/sdk'\nimport { useCallback } from 'react'\nimport { internalExplorerUrl } from '../config/constants.js'\nimport { useAvailableChains } from '../hooks/useAvailableChains.js'\nimport { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js'\n\nconst sanitiseBaseUrl = (baseUrl: string) => baseUrl.trim().replace(/\\/+$/, '')\n\nconst explorerPathOverrides: Partial<\n Record<ChainType | ChainId, { txPath: string; addressPath: string }>\n> = {\n [ChainId.SUI]: { txPath: 'txblock', addressPath: 'coin' },\n [ChainId.LTR]: { txPath: 'logs', addressPath: 'accounts' },\n [ChainType.TVM]: { txPath: '#/transaction', addressPath: '#/address' },\n // StellarExpert and steexp both route accounts at /account, never /address.\n [ChainType.STL]: { txPath: 'tx', addressPath: 'account' },\n}\n\ntype TransactionLinkProps = {\n chain?: Chain | number\n txHash?: string\n txLink?: string\n}\n\nexport const useExplorer = (): {\n getTransactionLink: (props: TransactionLinkProps) => string | undefined\n getAddressLink: (address: string, chain?: Chain | number) => string\n} => {\n const { explorerUrls } = useWidgetConfig()\n const { getChainById } = useAvailableChains()\n\n const getExplorerConfig = useCallback(\n (chain?: Chain | number) => {\n const resolvedChain = Number.isFinite(chain)\n ? getChainById(chain as number)\n : (chain as Chain)\n\n const explorerUrl =\n (resolvedChain\n ? (explorerUrls?.[resolvedChain.id]?.[0] ??\n resolvedChain.metamask.blockExplorerUrls[0])\n : explorerUrls?.internal?.[0]) || internalExplorerUrl\n\n const baseUrl =\n typeof explorerUrl === 'string' ? explorerUrl : explorerUrl.url\n\n const overrides =\n explorerPathOverrides[resolvedChain?.id as ChainId] ??\n explorerPathOverrides[resolvedChain?.chainType as ChainType]\n\n const defaultTxPath = overrides?.txPath ?? 'tx'\n const defaultAddressPath = overrides?.addressPath ?? 'address'\n const txPath =\n typeof explorerUrl === 'string'\n ? defaultTxPath\n : explorerUrl.txPath || defaultTxPath\n const addressPath =\n typeof explorerUrl === 'string'\n ? defaultAddressPath\n : explorerUrl.addressPath || defaultAddressPath\n\n return {\n url: sanitiseBaseUrl(baseUrl),\n txPath,\n addressPath,\n resolvedChain,\n hasOverride: Boolean(overrides),\n }\n },\n [explorerUrls, getChainById]\n )\n\n const getTransactionLink = useCallback(\n ({ txHash, txLink, chain }: TransactionLinkProps): string | undefined => {\n if (txHash) {\n const config = getExplorerConfig(chain)\n // For EVM chains, sanity-check the transaction hash as some relayers return custom task hashes that are not visible on-chain.\n // Skip the check when a chain-specific path override is registered — that's an explicit signal the chain's\n // explorer accepts a non-hex hash (e.g. Lighter, whose hash has no 0x prefix).\n const validForEvm =\n config.resolvedChain?.chainType !== ChainType.EVM ||\n config.hasOverride ||\n isHex(txHash, { strict: true })\n if (validForEvm) {\n return `${config.url}/${config.txPath}/${txHash}`\n }\n }\n return txLink\n },\n [getExplorerConfig]\n )\n\n const getAddressLink = useCallback(\n (address: string, chain?: Chain | number) => {\n const config = getExplorerConfig(chain)\n return `${config.url}/${config.addressPath}/${address}`\n },\n [getExplorerConfig]\n )\n\n return {\n getTransactionLink,\n getAddressLink,\n }\n}\n"],"mappings":";;;;;;AAOA,MAAM,mBAAmB,YAAoB,QAAQ,KAAK,CAAC,CAAC,QAAQ,QAAQ,EAAE;AAE9E,MAAM,wBAEF;EACD,QAAQ,MAAM;EAAE,QAAQ;EAAW,aAAa;CAAO;EACvD,QAAQ,MAAM;EAAE,QAAQ;EAAQ,aAAa;CAAW;EACxD,UAAU,MAAM;EAAE,QAAQ;EAAiB,aAAa;CAAY;EAEpE,UAAU,MAAM;EAAE,QAAQ;EAAM,aAAa;CAAU;AAC1D;AAQA,MAAa,oBAGR;CACH,MAAM,EAAE,iBAAiB,gBAAgB;CACzC,MAAM,EAAE,iBAAiB,mBAAmB;CAE5C,MAAM,oBAAoB,aACvB,UAA2B;EAC1B,MAAM,gBAAgB,OAAO,SAAS,KAAK,IACvC,aAAa,KAAe,IAC3B;EAEL,MAAM,eACH,gBACI,eAAe,cAAc,GAAG,GAAG,MACpC,cAAc,SAAS,kBAAkB,KACzC,cAAc,WAAW,OAAA;EAE/B,MAAM,UACJ,OAAO,gBAAgB,WAAW,cAAc,YAAY;EAE9D,MAAM,YACJ,sBAAsB,eAAe,OACrC,sBAAsB,eAAe;EAEvC,MAAM,gBAAgB,WAAW,UAAU;EAC3C,MAAM,qBAAqB,WAAW,eAAe;EACrD,MAAM,SACJ,OAAO,gBAAgB,WACnB,gBACA,YAAY,UAAU;EAC5B,MAAM,cACJ,OAAO,gBAAgB,WACnB,qBACA,YAAY,eAAe;EAEjC,OAAO;GACL,KAAK,gBAAgB,OAAO;GAC5B;GACA;GACA;GACA,aAAa,QAAQ,SAAS;EAChC;CACF,GACA,CAAC,cAAc,YAAY,CAC7B;CA8BA,OAAO;EACL,oBA7ByB,aACxB,EAAE,QAAQ,QAAQ,YAAsD;GACvE,IAAI,QAAQ;IACV,MAAM,SAAS,kBAAkB,KAAK;IAQtC,IAHE,OAAO,eAAe,cAAc,UAAU,OAC9C,OAAO,eACP,MAAM,QAAQ,EAAE,QAAQ,KAAK,CAAC,GAE9B,OAAO,GAAG,OAAO,IAAI,GAAG,OAAO,OAAO,GAAG;GAE7C;GACA,OAAO;EACT,GACA,CAAC,iBAAiB,CAYD;EACjB,gBAVqB,aACpB,SAAiB,UAA2B;GAC3C,MAAM,SAAS,kBAAkB,KAAK;GACtC,OAAO,GAAG,OAAO,IAAI,GAAG,OAAO,YAAY,GAAG;EAChD,GACA,CAAC,iBAAiB,CAKL;CACf;AACF"}
@@ -0,0 +1,28 @@
1
+ import { useAvailableChains } from "./useAvailableChains.js";
2
+ import { getNativeTokenAddresses } from "../utils/token.js";
3
+ import { useMemo, useRef } from "react";
4
+ //#region src/hooks/useFlaggedTokenGuard.ts
5
+ /**
6
+ * Route tokens the screening provider flagged, and the sheet that warns about
7
+ * them before the transaction starts. A native token is exempt: the provider
8
+ * screens the native-address convention and calls it a scam on some chains.
9
+ */
10
+ const useFlaggedTokenGuard = (route) => {
11
+ const { chains } = useAvailableChains();
12
+ const flaggedTokenSheetRef = useRef(null);
13
+ return {
14
+ flaggedTokens: useMemo(() => {
15
+ const nativeTokenAddresses = getNativeTokenAddresses(chains);
16
+ return [route.fromToken, route.toToken].filter((token) => token.verificationStatus === "flagged" && nativeTokenAddresses.get(token.chainId)?.toLowerCase() !== token.address.toLowerCase());
17
+ }, [
18
+ chains,
19
+ route.fromToken,
20
+ route.toToken
21
+ ]),
22
+ flaggedTokenSheetRef
23
+ };
24
+ };
25
+ //#endregion
26
+ export { useFlaggedTokenGuard };
27
+
28
+ //# sourceMappingURL=useFlaggedTokenGuard.js.map