@lifi/widget 1.12.1 → 1.13.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 (61) hide show
  1. package/components/Initializer.js +1 -1
  2. package/components/PoweredBy/PoweredBy.js +3 -2
  3. package/components/Step/CircularProgress.d.ts +0 -1
  4. package/components/SwapButton/ButtonTooltip.d.ts +0 -1
  5. package/components/SwapInput/FormPriceHelperText.js +2 -2
  6. package/components/SwapInput/SwapInputAdornment.d.ts +0 -1
  7. package/components/SwapInput/SwapInputAdornment.js +2 -2
  8. package/components/SwapRouteCard/SwapRouteCard.style.js +1 -1
  9. package/components/TokenAvatar/TokenAvatar.js +3 -2
  10. package/components/TokenList/TokenList.js +25 -43
  11. package/components/TokenList/TokenList.style.js +5 -2
  12. package/components/TokenList/TokenListItem.d.ts +2 -2
  13. package/components/TokenList/TokenListItem.js +7 -10
  14. package/components/TokenList/TokenNotFound.d.ts +2 -0
  15. package/components/TokenList/TokenNotFound.js +15 -0
  16. package/components/TokenList/VirtualizedTokenList.d.ts +3 -0
  17. package/components/TokenList/VirtualizedTokenList.js +53 -0
  18. package/components/TokenList/types.d.ts +16 -1
  19. package/config/sentry.d.ts +1 -1
  20. package/config/sentry.js +33 -18
  21. package/config/version.d.ts +1 -1
  22. package/config/version.js +1 -1
  23. package/hooks/index.d.ts +3 -0
  24. package/hooks/index.js +3 -0
  25. package/hooks/useFeaturedTokens.d.ts +1 -0
  26. package/hooks/useFeaturedTokens.js +6 -0
  27. package/hooks/useToken.d.ts +2 -1
  28. package/hooks/useToken.js +2 -1
  29. package/hooks/useTokenBalance.d.ts +2 -4
  30. package/hooks/useTokenBalance.js +11 -42
  31. package/hooks/useTokenBalances.d.ts +5 -4
  32. package/hooks/useTokenBalances.js +25 -13
  33. package/hooks/useTokenSearch.d.ts +7 -0
  34. package/hooks/useTokenSearch.js +37 -0
  35. package/hooks/useTokens.d.ts +2 -1
  36. package/hooks/useTokens.js +12 -4
  37. package/i18n/en/translation.json +2 -0
  38. package/i18n/index.d.ts +2 -0
  39. package/package.json +8 -9
  40. package/pages/SelectTokenPage/ChainSelect.d.ts +0 -1
  41. package/pages/SelectTokenPage/ChainSelect.js +8 -5
  42. package/pages/SelectTokenPage/SearchTokenInput.d.ts +0 -1
  43. package/pages/SelectTokenPage/SearchTokenInput.js +2 -2
  44. package/pages/SelectWalletPage/SelectWalletPage.d.ts +0 -1
  45. package/pages/SettingsPage/AdvancedPreferences.d.ts +0 -1
  46. package/pages/SettingsPage/GasPriceSelect.d.ts +0 -1
  47. package/pages/SettingsPage/SettingsPage.d.ts +0 -1
  48. package/pages/SettingsPage/SlippageInput.d.ts +0 -1
  49. package/pages/SwapPage/StatusBottomSheet.js +2 -2
  50. package/providers/SwapFormProvider/SwapFormProvider.d.ts +1 -1
  51. package/providers/SwapFormProvider/SwapFormProvider.js +22 -5
  52. package/providers/SwapFormProvider/types.d.ts +2 -3
  53. package/providers/SwapFormProvider/types.js +1 -2
  54. package/providers/WidgetProvider/WidgetProvider.js +24 -15
  55. package/types/index.d.ts +1 -0
  56. package/types/index.js +1 -0
  57. package/types/token.d.ts +4 -0
  58. package/types/token.js +1 -0
  59. package/types/widget.d.ts +3 -2
  60. package/components/TokenList/utils.d.ts +0 -15
  61. package/components/TokenList/utils.js +0 -10
@@ -1,4 +1,4 @@
1
- import { useInitializer } from '../hooks/useInitializer';
1
+ import { useInitializer } from '../hooks';
2
2
  export const Initializer = () => {
3
3
  useInitializer();
4
4
  return null;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Typography } from '@mui/material';
2
+ import { Box, Tooltip, Typography } from '@mui/material';
3
+ import { version } from '../../config/version';
3
4
  import { LiFiLogo } from '../LiFiLogo';
4
5
  import { Link } from './PoweredBy.style';
5
6
  export const PoweredBy = () => {
@@ -7,5 +8,5 @@ export const PoweredBy = () => {
7
8
  display: 'flex',
8
9
  alignItems: 'flex-end',
9
10
  justifyContent: 'flex-end',
10
- } }, { children: _jsxs(Link, Object.assign({ href: "https://li.fi", target: "_blank", underline: "none", color: "text.primary" }, { children: [_jsx(Typography, Object.assign({ color: "text.secondary", fontSize: 12, px: 0.5 }, { children: "Powered by" })), _jsx(LiFiLogo, { variant: "full", style: { height: 16, width: 42 } })] })) })));
11
+ } }, { children: _jsx(Tooltip, Object.assign({ title: `v${version}`, placement: "top", enterDelay: 5000, arrow: true }, { children: _jsxs(Link, Object.assign({ href: "https://li.fi", target: "_blank", underline: "none", color: "text.primary" }, { children: [_jsx(Typography, Object.assign({ color: "text.secondary", fontSize: 12, px: 0.5 }, { children: "Powered by" })), _jsx(LiFiLogo, { variant: "full", style: { height: 16, width: 42 } })] })) })) })));
11
12
  };
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { Status } from '@lifi/sdk';
3
2
  export declare function CircularProgress({ status }: {
4
3
  status: Status;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  export declare const ButtonTooltip: ({ title, children, }: {
3
2
  title?: string | undefined;
4
3
  children: JSX.Element;
@@ -14,14 +14,14 @@ export const FormPriceHelperText = ({ formType, selected }) => {
14
14
  SwapFormKeyHelper.getTokenKey(formType),
15
15
  ],
16
16
  });
17
- const { token, isLoading, isFetching } = useTokenBalance(chainId, tokenAddress);
17
+ const { token, isLoading } = useTokenBalance(chainId, tokenAddress);
18
18
  const fromAmountTokenPrice = formatTokenPrice(amount, token === null || token === void 0 ? void 0 : token.priceUSD);
19
19
  return (_jsxs(FormHelperText, Object.assign({ component: "div", sx: { display: 'flex', justifyContent: 'space-between', margin: 0 } }, { children: [_jsx(Typography, Object.assign({ color: fromAmountTokenPrice ? 'text.secondary' : 'grey.600', fontWeight: 400, fontSize: 12, marginLeft: selected ? 8 : 2, lineHeight: 1.3334, flex: 1, sx: {
20
20
  wordBreak: 'break-word',
21
21
  overflowWrap: 'break-word',
22
22
  } }, { children: t(`swap.currency`, {
23
23
  value: fromAmountTokenPrice,
24
- }) })), isLoading && isFetching ? (_jsx(Skeleton, { variant: "text", width: 48, height: 16, sx: { borderRadius: 0.25 } })) : (token === null || token === void 0 ? void 0 : token.amount) ? (_jsx(Typography, Object.assign({ fontWeight: 400, fontSize: 12, color: "text.secondary", lineHeight: 1.3334, pl: 0.25 }, { children: t(`swap.maxAmount`, {
24
+ }) })), isLoading && tokenAddress ? (_jsx(Skeleton, { variant: "text", width: 48, height: 16, sx: { borderRadius: 0.25 } })) : (token === null || token === void 0 ? void 0 : token.amount) ? (_jsx(Typography, Object.assign({ fontWeight: 400, fontSize: 12, color: "text.secondary", lineHeight: 1.3334, pl: 0.25 }, { children: t(`swap.maxAmount`, {
25
25
  amount: token === null || token === void 0 ? void 0 : token.amount,
26
26
  }) }))) : null] })));
27
27
  };
@@ -1,3 +1,2 @@
1
- /// <reference types="react" />
2
1
  import { SwapFormTypeProps } from '../../providers/SwapFormProvider';
3
2
  export declare const SwapInputAdornment: ({ formType }: SwapFormTypeProps) => JSX.Element;
@@ -14,10 +14,10 @@ export const SwapInputAdornment = ({ formType }) => {
14
14
  SwapFormKeyHelper.getTokenKey(formType),
15
15
  ],
16
16
  });
17
- const { token, isLoading, isFetching } = useTokenBalance(chainId, tokenAddress);
17
+ const { token, isLoading } = useTokenBalance(chainId, tokenAddress);
18
18
  const handleMax = () => {
19
19
  var _a;
20
20
  setValue(SwapFormKeyHelper.getAmountKey(formType), (_a = token === null || token === void 0 ? void 0 : token.amount) !== null && _a !== void 0 ? _a : '');
21
21
  };
22
- return (_jsx(InputAdornment, Object.assign({ position: "end" }, { children: isLoading && isFetching ? (_jsx(Skeleton, { variant: "rectangular", width: 46, height: 20, sx: { borderRadius: 0.5 } })) : formType === 'from' && (token === null || token === void 0 ? void 0 : token.amount) ? (_jsx(Button, Object.assign({ onClick: handleMax, variant: "outlined" }, { children: t('button.max') }))) : null })));
22
+ return (_jsx(InputAdornment, Object.assign({ position: "end" }, { children: isLoading && tokenAddress ? (_jsx(Skeleton, { variant: "rectangular", width: 46, height: 20, sx: { borderRadius: 0.5 } })) : formType === 'from' && (token === null || token === void 0 ? void 0 : token.amount) ? (_jsx(Button, Object.assign({ onClick: handleMax, variant: "outlined" }, { children: t('button.max') }))) : null })));
23
23
  };
@@ -1,6 +1,6 @@
1
- import { getContrastTextColor } from '@lifi/widget/utils';
2
1
  import { Typography } from '@mui/material';
3
2
  import { styled } from '@mui/material/styles';
3
+ import { getContrastTextColor } from '../../utils';
4
4
  export const Label = styled(Typography, {
5
5
  shouldForwardProp: (prop) => prop !== 'active',
6
6
  })(({ theme, active }) => ({
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Avatar, Badge } from '@mui/material';
3
- import { useChain } from '../../hooks';
3
+ import { useChain, useToken } from '../../hooks';
4
4
  import { SmallAvatar } from '../SmallAvatar';
5
5
  export const TokenAvatar = ({ token, sx }) => {
6
6
  const { chain } = useChain(token.chainId);
7
- return (_jsx(Badge, Object.assign({ overlap: "circular", anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, badgeContent: chain ? (_jsx(SmallAvatar, Object.assign({ src: chain.logoURI, alt: chain.name }, { children: chain.name[0] }))) : null, sx: sx }, { children: _jsx(Avatar, Object.assign({ src: token.logoURI, alt: token.symbol }, { children: token.symbol[0] })) })));
7
+ const { token: chainToken } = useToken(token.chainId, token.address);
8
+ return (_jsx(Badge, Object.assign({ overlap: "circular", anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, badgeContent: chain ? (_jsx(SmallAvatar, Object.assign({ src: chain.logoURI, alt: chain.name }, { children: chain.name[0] }))) : null, sx: sx }, { children: _jsx(Avatar, Object.assign({ src: token.logoURI || (chainToken === null || chainToken === void 0 ? void 0 : chainToken.logoURI), alt: token.symbol }, { children: token.symbol[0] })) })));
8
9
  };
@@ -1,49 +1,37 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, List, Typography } from '@mui/material';
3
- import { useCallback, useEffect, useMemo, useRef } from 'react';
2
+ import { Box } from '@mui/material';
3
+ import { useCallback, useRef } from 'react';
4
4
  import { useFormContext, useWatch } from 'react-hook-form';
5
- import { useTranslation } from 'react-i18next';
6
- import { useVirtual } from 'react-virtual';
7
- import { useDebouncedWatch, useTokenBalances } from '../../hooks';
5
+ import { useDebouncedWatch, useTokenBalances, useTokenSearch, } from '../../hooks';
8
6
  import { SwapFormKey, SwapFormKeyHelper, } from '../../providers/SwapFormProvider';
9
7
  import { useWallet } from '../../providers/WalletProvider';
10
- import { TokenListItem, TokenListItemSkeleton } from './TokenListItem';
11
- import { createTokenAmountSkeletons, skeletonKey } from './utils';
8
+ import { TokenNotFound } from './TokenNotFound';
9
+ import { VirtualizedTokenList } from './VirtualizedTokenList';
12
10
  export const TokenList = ({ formType, height, onClick, }) => {
13
- const { t } = useTranslation();
11
+ var _a, _b;
12
+ const parentRef = useRef(null);
14
13
  const { account } = useWallet();
15
14
  const { setValue, getValues } = useFormContext();
16
15
  const [selectedChainId] = useWatch({
17
16
  name: [SwapFormKeyHelper.getChainKey(formType)],
18
17
  });
19
- const [searchTokensFilter] = useDebouncedWatch([SwapFormKey.SearchTokensFilter], 250);
20
- const { tokens: tokensWithoutBalance, tokensWithBalance, isLoading, isBalanceLoading, } = useTokenBalances(selectedChainId);
21
- const tokens = tokensWithBalance !== null && tokensWithBalance !== void 0 ? tokensWithBalance : tokensWithoutBalance;
22
- const chainTokens = useMemo(() => {
23
- var _a;
24
- let chainTokens = tokens !== null && tokens !== void 0 ? tokens : [];
25
- const searchFilter = (_a = searchTokensFilter === null || searchTokensFilter === void 0 ? void 0 : searchTokensFilter.toUpperCase()) !== null && _a !== void 0 ? _a : '';
26
- chainTokens = isLoading
27
- ? createTokenAmountSkeletons()
28
- : searchTokensFilter
29
- ? chainTokens.filter((token) => token.name.toUpperCase().includes(searchFilter) ||
30
- token.symbol.toUpperCase().includes(searchFilter) ||
31
- token.address.toUpperCase().includes(searchFilter))
32
- : chainTokens;
33
- return chainTokens;
34
- }, [isLoading, searchTokensFilter, tokens]);
35
- const parentRef = useRef(null);
36
- const { virtualItems, totalSize, scrollToIndex } = useVirtual({
37
- size: chainTokens.length,
38
- parentRef,
39
- overscan: 3,
40
- paddingEnd: 12,
41
- estimateSize: useCallback(() => 64, []),
42
- keyExtractor: (index) => { var _a; return (_a = chainTokens[index].address) !== null && _a !== void 0 ? _a : index; },
43
- });
44
- useEffect(() => {
45
- scrollToIndex(0);
46
- }, [scrollToIndex, selectedChainId]);
18
+ const [tokenSearchFilter] = useDebouncedWatch([SwapFormKey.TokenSearchFilter], 250);
19
+ const { tokens: chainTokens, tokensWithBalance, isLoading: isTokensLoading, isBalanceLoading, featuredTokens, } = useTokenBalances(selectedChainId);
20
+ let filteredTokens = ((_a = tokensWithBalance !== null && tokensWithBalance !== void 0 ? tokensWithBalance : chainTokens) !== null && _a !== void 0 ? _a : []);
21
+ const searchFilter = (_b = tokenSearchFilter === null || tokenSearchFilter === void 0 ? void 0 : tokenSearchFilter.toUpperCase()) !== null && _b !== void 0 ? _b : '';
22
+ filteredTokens = tokenSearchFilter
23
+ ? filteredTokens.filter((token) => token.name.toUpperCase().includes(searchFilter) ||
24
+ token.symbol.toUpperCase().includes(searchFilter) ||
25
+ token.address.toUpperCase().includes(searchFilter))
26
+ : filteredTokens;
27
+ const tokenSearchEnabled = !filteredTokens.length && !isTokensLoading;
28
+ const { token: searchedToken, isLoading: isSearchedTokenLoading } = useTokenSearch(tokenSearchFilter, selectedChainId, tokenSearchEnabled);
29
+ const isLoading = isTokensLoading || (tokenSearchEnabled && isSearchedTokenLoading);
30
+ const tokens = filteredTokens.length
31
+ ? filteredTokens
32
+ : searchedToken
33
+ ? [searchedToken]
34
+ : filteredTokens;
47
35
  const handleTokenClick = useCallback((tokenAddress) => {
48
36
  setValue(SwapFormKeyHelper.getTokenKey(formType), tokenAddress);
49
37
  setValue(SwapFormKeyHelper.getAmountKey(formType), '');
@@ -59,11 +47,5 @@ export const TokenList = ({ formType, height, onClick, }) => {
59
47
  }
60
48
  onClick === null || onClick === void 0 ? void 0 : onClick();
61
49
  }, [formType, getValues, onClick, setValue]);
62
- return (_jsxs(Box, Object.assign({ ref: parentRef, style: { height, overflow: 'auto' } }, { children: [!virtualItems.length ? (_jsx(Typography, Object.assign({ variant: "body1", align: "center", py: 2, px: 3 }, { children: t('swap.couldntFindTokens') }))) : null, _jsx(List, Object.assign({ style: { height: totalSize }, disablePadding: true }, { children: virtualItems.map((item) => {
63
- const token = chainTokens[item.index];
64
- if (token.name.includes(skeletonKey)) {
65
- return (_jsx(TokenListItemSkeleton, { size: item.size, start: item.start }, item.key));
66
- }
67
- return (_jsx(TokenListItem, { onClick: handleTokenClick, size: item.size, start: item.start, token: token, isBalanceLoading: isBalanceLoading, showBalance: account.isActive }, item.key));
68
- }) }))] })));
50
+ return (_jsxs(Box, Object.assign({ ref: parentRef, style: { height, overflow: 'auto' } }, { children: [!tokens.length && !isLoading ? _jsx(TokenNotFound, {}) : null, _jsx(VirtualizedTokenList, { tokens: tokens, featuredTokensLength: featuredTokens === null || featuredTokens === void 0 ? void 0 : featuredTokens.length, scrollElementRef: parentRef, chainId: selectedChainId, isLoading: isLoading, isBalanceLoading: isBalanceLoading, showBalance: account.isActive, showFeatured: !tokenSearchFilter, onClick: handleTokenClick })] })));
69
51
  };
@@ -6,6 +6,7 @@ export const ListItemButton = styled(MuiListItemButton)(({ theme }) => ({
6
6
  borderRadius: theme.shape.borderRadiusSecondary,
7
7
  paddingLeft: theme.spacing(2),
8
8
  height: 64,
9
+ width: '100%',
9
10
  '&:hover': {
10
11
  backgroundColor: getContrastAlphaColor(theme, '4%'),
11
12
  },
@@ -14,8 +15,10 @@ export const ListItem = styled(MuiListItem)(({ theme }) => ({
14
15
  position: 'absolute',
15
16
  top: 0,
16
17
  left: 0,
17
- width: '100%',
18
- padding: theme.spacing(0.5, 3),
18
+ height: 64,
19
+ flexDirection: 'column',
20
+ alignItems: 'flex-start',
21
+ padding: theme.spacing(0, 3),
19
22
  [`.${listItemSecondaryActionClasses.root}`]: {
20
23
  right: theme.spacing(5),
21
24
  },
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import { TokenListItemBaseProps, TokenListItemProps } from './types';
2
+ import { TokenListItemProps } from './types';
3
3
  export declare const TokenListItem: React.FC<TokenListItemProps>;
4
- export declare const TokenListItemSkeleton: React.FC<TokenListItemBaseProps>;
4
+ export declare const TokenListItemSkeleton: () => JSX.Element;
5
5
  export declare const TokenAmountSkeleton: React.FC;
@@ -4,22 +4,19 @@ import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { formatTokenPrice } from '../../utils';
6
6
  import { ListItem, ListItemButton } from './TokenList.style';
7
- export const TokenListItem = memo(({ onClick, size, start, token, showBalance, isBalanceLoading }) => {
7
+ export const TokenListItem = memo(({ onClick, size, start, token, showBalance, isBalanceLoading, startAdornment, endAdornment, }) => {
8
8
  const { t } = useTranslation();
9
9
  const handleClick = () => onClick === null || onClick === void 0 ? void 0 : onClick(token.address);
10
10
  const tokenPrice = formatTokenPrice(token.amount, token.priceUSD);
11
- return (_jsx(ListItem, Object.assign({ disablePadding: true, style: {
11
+ return (_jsxs(ListItem, Object.assign({ disablePadding: true, style: {
12
12
  height: `${size}px`,
13
13
  transform: `translateY(${start}px)`,
14
- } }, { children: _jsxs(ListItemButton, Object.assign({ onClick: handleClick, dense: true, disableRipple: true }, { children: [_jsx(ListItemAvatar, { children: _jsx(Avatar, Object.assign({ src: token.logoURI, alt: token.symbol }, { children: token.symbol[0] })) }), _jsx(ListItemText, { primary: token.symbol, secondary: token.name }), showBalance ? (isBalanceLoading ? (_jsx(TokenAmountSkeleton, {})) : (_jsxs(Box, Object.assign({ sx: { textAlign: 'right' } }, { children: [Number(token.amount) ? (_jsx(Typography, Object.assign({ variant: "body1", noWrap: true }, { children: token.amount }))) : null, tokenPrice ? (_jsx(Typography, Object.assign({ fontWeight: 400, fontSize: 12, color: "text.secondary", "data-price": token.priceUSD }, { children: t(`swap.currency`, {
15
- value: tokenPrice,
16
- }) }))) : null] })))) : null] })) })));
14
+ } }, { children: [startAdornment, _jsxs(ListItemButton, Object.assign({ onClick: handleClick, dense: true, disableRipple: true }, { children: [_jsx(ListItemAvatar, { children: _jsx(Avatar, Object.assign({ src: token.logoURI, alt: token.symbol }, { children: token.symbol[0] })) }), _jsx(ListItemText, { primary: token.symbol, secondary: token.name }), showBalance ? (isBalanceLoading ? (_jsx(TokenAmountSkeleton, {})) : (_jsxs(Box, Object.assign({ sx: { textAlign: 'right' } }, { children: [Number(token.amount) ? (_jsx(Typography, Object.assign({ variant: "body1", noWrap: true }, { children: token.amount }))) : null, tokenPrice ? (_jsx(Typography, Object.assign({ fontWeight: 400, fontSize: 12, color: "text.secondary", "data-price": token.priceUSD }, { children: t(`swap.currency`, {
15
+ value: tokenPrice,
16
+ }) }))) : null] })))) : null] })), endAdornment] })));
17
17
  });
18
- export const TokenListItemSkeleton = ({ size, start, }) => {
19
- return (_jsxs(ListItem, Object.assign({ secondaryAction: _jsx(TokenAmountSkeleton, {}), disablePadding: true, style: {
20
- height: `${size}px`,
21
- transform: `translateY(${start}px)`,
22
- } }, { children: [_jsx(ListItemAvatar, { children: _jsx(Skeleton, { variant: "circular", width: 32, height: 32, sx: { marginLeft: 2, marginRight: 2 } }) }), _jsx(ListItemText, { primary: _jsx(Skeleton, { variant: "text", width: 48, height: 20 }), secondary: _jsx(Skeleton, { variant: "text", width: 96, height: 20 }) })] })));
18
+ export const TokenListItemSkeleton = () => {
19
+ return (_jsxs(ListItem, Object.assign({ secondaryAction: _jsx(TokenAmountSkeleton, {}), disablePadding: true, sx: { position: 'relative', flexDirection: 'row', alignItems: 'center' } }, { children: [_jsx(ListItemAvatar, { children: _jsx(Skeleton, { variant: "circular", width: 32, height: 32, sx: { marginLeft: 2, marginRight: 2 } }) }), _jsx(ListItemText, { primary: _jsx(Skeleton, { variant: "text", width: 48, height: 20 }), secondary: _jsx(Skeleton, { variant: "text", width: 96, height: 20 }) })] })));
23
20
  };
24
21
  export const TokenAmountSkeleton = () => {
25
22
  return (_jsxs(Box, Object.assign({ sx: {
@@ -0,0 +1,2 @@
1
+ /// <reference types="react" />
2
+ export declare const TokenNotFound: React.FC;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { SearchOff as SearchOffIcon } from '@mui/icons-material';
3
+ import { Box, Typography } from '@mui/material';
4
+ import { useTranslation } from 'react-i18next';
5
+ export const TokenNotFound = () => {
6
+ const { t } = useTranslation();
7
+ return (_jsxs(Box, Object.assign({ sx: {
8
+ display: 'flex',
9
+ justifyContent: 'center',
10
+ alignItems: 'center',
11
+ flexDirection: 'column',
12
+ flex: 1,
13
+ padding: 3,
14
+ } }, { children: [_jsx(Typography, Object.assign({ fontSize: 48, lineHeight: 1 }, { children: _jsx(SearchOffIcon, { fontSize: "inherit" }) })), _jsx(Typography, Object.assign({ fontSize: 14, color: "text.secondary", textAlign: "center", mt: 2, px: 2 }, { children: t('swap.couldntFindTokens') }))] })));
15
+ };
@@ -0,0 +1,3 @@
1
+ import { FC } from 'react';
2
+ import { VirtualizedTokenListProps } from './types';
3
+ export declare const VirtualizedTokenList: FC<VirtualizedTokenListProps>;
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { List, Typography } from '@mui/material';
3
+ import { useVirtualizer } from '@tanstack/react-virtual';
4
+ import { useEffect } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { TokenListItem, TokenListItemSkeleton } from './TokenListItem';
7
+ export const VirtualizedTokenList = ({ tokens, featuredTokensLength, scrollElementRef, chainId, isLoading, isBalanceLoading, showBalance, showFeatured, onClick, }) => {
8
+ const { t } = useTranslation();
9
+ const hasFeaturedTokens = !!featuredTokensLength && showFeatured;
10
+ const featuredTokensLastIndex = (featuredTokensLength !== null && featuredTokensLength !== void 0 ? featuredTokensLength : 0) - 1;
11
+ const tokensLastIndex = tokens.length - 1;
12
+ const { getVirtualItems, getTotalSize, scrollToIndex } = useVirtualizer({
13
+ count: tokens.length,
14
+ getScrollElement: () => scrollElementRef.current,
15
+ overscan: 5,
16
+ paddingEnd: 12,
17
+ estimateSize: (index) => {
18
+ var _a, _b;
19
+ // heigth of TokenListItem
20
+ let size = 64;
21
+ if (!hasFeaturedTokens) {
22
+ return size;
23
+ }
24
+ if (index === 0 && ((_a = tokens[index]) === null || _a === void 0 ? void 0 : _a.featured)) {
25
+ // height of startAdornment
26
+ size += 24;
27
+ }
28
+ if (index === featuredTokensLastIndex &&
29
+ index !== tokensLastIndex &&
30
+ ((_b = tokens[index]) === null || _b === void 0 ? void 0 : _b.featured)) {
31
+ // height of endAdornment
32
+ size += 32;
33
+ }
34
+ return size;
35
+ },
36
+ getItemKey: (index) => { var _a; return (_a = tokens[index].address) !== null && _a !== void 0 ? _a : index; },
37
+ });
38
+ useEffect(() => {
39
+ scrollToIndex(0, { align: 'start', smoothScroll: false });
40
+ }, [scrollToIndex, chainId]);
41
+ if (isLoading) {
42
+ return (_jsx(List, Object.assign({ disablePadding: true }, { children: Array.from({ length: 3 }).map((_, index) => (
43
+ // eslint-disable-next-line react/no-array-index-key
44
+ _jsx(TokenListItemSkeleton, {}, index))) })));
45
+ }
46
+ return (_jsx(List, Object.assign({ style: { height: getTotalSize() }, disablePadding: true }, { children: getVirtualItems().map((item) => {
47
+ const token = tokens[item.index];
48
+ return (_jsx(TokenListItem, { onClick: onClick, size: item.size, start: item.start, token: token, isBalanceLoading: isBalanceLoading, showBalance: showBalance, startAdornment: hasFeaturedTokens && token.featured && item.index === 0 ? (_jsx(Typography, Object.assign({ fontSize: 14, fontWeight: 600, lineHeight: 1, px: 2, pb: 1.25 }, { children: t('swap.featuredTokens') }))) : null, endAdornment: hasFeaturedTokens &&
49
+ token.featured &&
50
+ item.index === featuredTokensLastIndex &&
51
+ item.index !== tokensLastIndex ? (_jsx(Typography, Object.assign({ fontSize: 14, fontWeight: 600, lineHeight: 1, px: 2, py: 1.25 }, { children: t('swap.otherTokens') }))) : null }, item.key));
52
+ }) })));
53
+ };
@@ -1,12 +1,25 @@
1
1
  import { TokenAmount } from '@lifi/sdk';
2
+ import { MutableRefObject } from 'react';
2
3
  import { SwapFormDirection } from '../../providers/SwapFormProvider';
4
+ import { Token } from '../../types';
3
5
  export interface TokenListProps {
4
6
  formType: SwapFormDirection;
5
7
  height: number;
6
8
  onClick?(): void;
7
9
  }
10
+ export interface VirtualizedTokenListProps {
11
+ tokens: Token[];
12
+ featuredTokensLength?: number;
13
+ scrollElementRef: MutableRefObject<HTMLElement | null>;
14
+ isLoading: boolean;
15
+ isBalanceLoading: boolean;
16
+ chainId: number;
17
+ showBalance?: boolean;
18
+ showFeatured?: boolean;
19
+ onClick(tokenAddress: string): void;
20
+ }
8
21
  export interface TokenListItemBaseProps {
9
- onClick?(token: string): void;
22
+ onClick?(tokenAddress: string): void;
10
23
  size: number;
11
24
  start: number;
12
25
  }
@@ -14,4 +27,6 @@ export interface TokenListItemProps extends TokenListItemBaseProps {
14
27
  showBalance?: boolean;
15
28
  token: TokenAmount;
16
29
  isBalanceLoading?: boolean;
30
+ startAdornment?: React.ReactNode;
31
+ endAdornment?: React.ReactNode;
17
32
  }
@@ -1 +1 @@
1
- export declare const initSentry: (enabled?: boolean) => void;
1
+ export declare const initSentry: (enabled?: boolean) => Promise<void>;
package/config/sentry.js CHANGED
@@ -1,20 +1,35 @@
1
- import { CaptureConsole } from '@sentry/integrations';
2
- import * as Sentry from '@sentry/react';
3
- import { BrowserTracing } from '@sentry/tracing';
4
- import { version } from './version';
5
- export const initSentry = (enabled) => {
6
- Sentry.init({
7
- dsn: 'https://bc1312161bf948db9b9c82618035ec22@o1302189.ingest.sentry.io/6539228',
8
- integrations: [
9
- new BrowserTracing(),
10
- new CaptureConsole({
11
- levels: ['error'],
12
- }),
13
- ],
14
- sampleRate: 1,
15
- tracesSampleRate: 0.2,
16
- enabled,
17
- environment: process.env.NODE_ENV,
18
- release: version,
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
8
  });
20
9
  };
10
+ import { version } from './version';
11
+ let sentryLoaded = false;
12
+ export const initSentry = (enabled) => __awaiter(void 0, void 0, void 0, function* () {
13
+ if (enabled || sentryLoaded) {
14
+ const [Sentry, { CaptureConsole }, { BrowserTracing }] = yield Promise.all([
15
+ import('@sentry/react'),
16
+ import('@sentry/integrations'),
17
+ import('@sentry/tracing'),
18
+ ]);
19
+ Sentry.init({
20
+ dsn: 'https://bc1312161bf948db9b9c82618035ec22@o1302189.ingest.sentry.io/6539228',
21
+ integrations: [
22
+ new BrowserTracing(),
23
+ new CaptureConsole({
24
+ levels: ['error'],
25
+ }),
26
+ ],
27
+ sampleRate: 1,
28
+ tracesSampleRate: 0.2,
29
+ enabled,
30
+ environment: process.env.NODE_ENV,
31
+ release: version,
32
+ });
33
+ sentryLoaded = true;
34
+ }
35
+ });
@@ -1,2 +1,2 @@
1
1
  export declare const name = "@lifi/widget";
2
- export declare const version = "1.12.1";
2
+ export declare const version = "1.13.0";
package/config/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export const name = '@lifi/widget';
2
- export const version = '1.12.1';
2
+ export const version = '1.13.0';
package/hooks/index.d.ts CHANGED
@@ -2,7 +2,9 @@ export * from './useChain';
2
2
  export * from './useChains';
3
3
  export * from './useContentHeight';
4
4
  export * from './useDebouncedWatch';
5
+ export * from './useFeaturedTokens';
5
6
  export * from './useGasSufficiency';
7
+ export * from './useInitializer';
6
8
  export * from './useRouteExecution';
7
9
  export * from './useScrollableContainer';
8
10
  export * from './useSwapRoutes';
@@ -11,4 +13,5 @@ export * from './useToken';
11
13
  export * from './useTokenBalance';
12
14
  export * from './useTokenBalances';
13
15
  export * from './useTokens';
16
+ export * from './useTokenSearch';
14
17
  export * from './useTools';
package/hooks/index.js CHANGED
@@ -2,7 +2,9 @@ export * from './useChain';
2
2
  export * from './useChains';
3
3
  export * from './useContentHeight';
4
4
  export * from './useDebouncedWatch';
5
+ export * from './useFeaturedTokens';
5
6
  export * from './useGasSufficiency';
7
+ export * from './useInitializer';
6
8
  export * from './useRouteExecution';
7
9
  export * from './useScrollableContainer';
8
10
  export * from './useSwapRoutes';
@@ -11,4 +13,5 @@ export * from './useToken';
11
13
  export * from './useTokenBalance';
12
14
  export * from './useTokenBalances';
13
15
  export * from './useTokens';
16
+ export * from './useTokenSearch';
14
17
  export * from './useTools';
@@ -0,0 +1 @@
1
+ export declare const useFeaturedTokens: (selectedChainId: number) => import("@lifi/types").Token[] | undefined;
@@ -0,0 +1,6 @@
1
+ import { useMemo } from 'react';
2
+ import { useWidgetConfig } from '../providers/WidgetProvider';
3
+ export const useFeaturedTokens = (selectedChainId) => {
4
+ const { featuredTokens } = useWidgetConfig();
5
+ return useMemo(() => featuredTokens === null || featuredTokens === void 0 ? void 0 : featuredTokens.filter((token) => token.chainId === selectedChainId), [featuredTokens, selectedChainId]);
6
+ };
@@ -1,4 +1,5 @@
1
1
  export declare const useToken: (chainId: number, tokenAddress: string) => {
2
- token: import("@lifi/types").Token | undefined;
2
+ token: import("..").Token | undefined;
3
3
  isLoading: boolean;
4
+ isFetching: boolean;
4
5
  };
package/hooks/useToken.js CHANGED
@@ -8,6 +8,7 @@ export const useToken = (chainId, tokenAddress) => {
8
8
  }, [chainId, tokenAddress, tokens]);
9
9
  return {
10
10
  token,
11
- isLoading: isLoading && isFetching,
11
+ isLoading,
12
+ isFetching,
12
13
  };
13
14
  };
@@ -1,7 +1,5 @@
1
- import { TokenAmount } from '@lifi/sdk';
2
1
  export declare const useTokenBalance: (chainId: number, tokenAddress: string) => {
3
- token: TokenAmount | undefined;
2
+ token: import("..").Token | undefined;
4
3
  isLoading: boolean;
5
- isFetching: boolean;
6
- refetchBalance: (chainId?: number, tokenAddress?: string) => Promise<void>;
4
+ refetch: <TPageData>(options?: (import("@tanstack/query-core").RefetchOptions & import("@tanstack/query-core").RefetchQueryFilters<TPageData>) | undefined) => Promise<import("@tanstack/query-core").QueryObserverResult<import("..").Token[] | undefined, unknown>>;
7
5
  };
@@ -1,46 +1,15 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import { useQuery, useQueryClient } from '@tanstack/react-query';
11
- import { useCallback } from 'react';
12
- import { LiFi } from '../config/lifi';
13
- import { useWallet } from '../providers/WalletProvider';
14
- import { formatTokenAmount } from '../utils';
15
- import { useToken } from './useToken';
1
+ import { useMemo } from 'react';
2
+ import { useTokenBalances } from './useTokenBalances';
16
3
  export const useTokenBalance = (chainId, tokenAddress) => {
17
- const { account } = useWallet();
18
- const queryClient = useQueryClient();
19
- const { token } = useToken(chainId, tokenAddress);
20
- const { data: tokenWithBalance, isLoading, isFetching, refetch, } = useQuery(['token-balance', account.address, chainId, tokenAddress], ({ queryKey: [, address] }) => __awaiter(void 0, void 0, void 0, function* () {
21
- if (!address || !token) {
22
- return null;
23
- }
24
- const tokenBalance = yield LiFi.getTokenBalance(address, token);
25
- return Object.assign(Object.assign(Object.assign({}, token), tokenBalance), { amount: formatTokenAmount(tokenBalance === null || tokenBalance === void 0 ? void 0 : tokenBalance.amount) });
26
- }), {
27
- enabled: Boolean(account.address) && Boolean(token),
28
- refetchIntervalInBackground: true,
29
- refetchInterval: 30000,
30
- staleTime: 30000,
31
- cacheTime: 30000,
32
- });
33
- const refetchBalance = useCallback((chainId, tokenAddress) => __awaiter(void 0, void 0, void 0, function* () {
34
- if (!chainId && !tokenAddress) {
35
- refetch();
36
- return;
37
- }
38
- yield queryClient.invalidateQueries(['token', account.address, chainId, tokenAddress], { type: 'all', exact: true });
39
- }), [account.address, queryClient, refetch]);
4
+ const { tokens, tokensWithBalance, isBalanceLoading, refetch } = useTokenBalances(chainId);
5
+ const token = useMemo(() => {
6
+ var _a;
7
+ const token = (_a = (tokensWithBalance !== null && tokensWithBalance !== void 0 ? tokensWithBalance : tokens)) === null || _a === void 0 ? void 0 : _a.find((token) => token.address === tokenAddress && token.chainId === chainId);
8
+ return token;
9
+ }, [chainId, tokenAddress, tokens, tokensWithBalance]);
40
10
  return {
41
- token: tokenWithBalance !== null && tokenWithBalance !== void 0 ? tokenWithBalance : token,
42
- isLoading,
43
- isFetching,
44
- refetchBalance,
11
+ token,
12
+ isLoading: isBalanceLoading,
13
+ refetch,
45
14
  };
46
15
  };
@@ -1,9 +1,10 @@
1
- import { TokenAmount } from '@lifi/sdk';
1
+ import { Token } from '../types';
2
2
  export declare const useTokenBalances: (selectedChainId: number) => {
3
- tokens: import("@lifi/sdk").Token[] | undefined;
4
- tokensWithBalance: TokenAmount[] | undefined;
3
+ tokens: Token[] | undefined;
4
+ tokensWithBalance: Token[] | undefined;
5
+ featuredTokens: import("@lifi/types").Token[] | undefined;
5
6
  isLoading: boolean;
6
7
  isBalanceLoading: boolean;
7
8
  isBalanceFetched: boolean;
8
- updateBalances: <TPageData>(options?: (import("@tanstack/react-query").RefetchOptions & import("@tanstack/react-query").RefetchQueryFilters<TPageData>) | undefined) => Promise<import("@tanstack/react-query").QueryObserverResult<TokenAmount[] | undefined, unknown>>;
9
+ refetch: <TPageData>(options?: (import("@tanstack/react-query").RefetchOptions & import("@tanstack/react-query").RefetchQueryFilters<TPageData>) | undefined) => Promise<import("@tanstack/react-query").QueryObserverResult<Token[] | undefined, unknown>>;
9
10
  };
@@ -7,20 +7,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ /* eslint-disable consistent-return */
10
11
  import { useQuery } from '@tanstack/react-query';
11
12
  import { useState } from 'react';
12
13
  import { LiFi } from '../config/lifi';
13
14
  import { useWallet } from '../providers/WalletProvider';
14
15
  import { formatTokenAmount } from '../utils';
16
+ import { useFeaturedTokens } from './useFeaturedTokens';
15
17
  import { useTokens } from './useTokens';
16
18
  const defaultRefetchInterval = 60000;
17
19
  const minRefetchInterval = 1000;
18
20
  export const useTokenBalances = (selectedChainId) => {
19
21
  const { account } = useWallet();
22
+ const featuredTokens = useFeaturedTokens(selectedChainId);
20
23
  const { tokens, isLoading } = useTokens(selectedChainId);
21
24
  const [refetchInterval, setRefetchInterval] = useState(defaultRefetchInterval);
22
- const isBalanceLoadingEnabled = Boolean(account.address) && Boolean(tokens);
23
- const { data: tokensWithBalance, isLoading: isBalanceLoading, isFetched: isBalanceFetched, refetch, } = useQuery(['token-balances', selectedChainId, account.address], ({ queryKey: [, , accountAddress] }) => __awaiter(void 0, void 0, void 0, function* () {
25
+ const isBalanceLoadingEnabled = Boolean(account.address) && Boolean(tokens === null || tokens === void 0 ? void 0 : tokens.length);
26
+ const { data: tokensWithBalance, isLoading: isBalanceLoading, isFetched: isBalanceFetched, refetch, } = useQuery(['token-balances', account.address, selectedChainId, tokens === null || tokens === void 0 ? void 0 : tokens.length], ({ queryKey: [, accountAddress] }) => __awaiter(void 0, void 0, void 0, function* () {
24
27
  if (!accountAddress || !tokens) {
25
28
  return;
26
29
  }
@@ -33,20 +36,28 @@ export const useTokenBalances = (selectedChainId) => {
33
36
  : interval * 2);
34
37
  return;
35
38
  }
36
- const formatedTokens = (tokenBalances.length === 0 ? tokens : tokenBalances).map((token) => {
39
+ const featuredTokenAddresses = new Set(featuredTokens === null || featuredTokens === void 0 ? void 0 : featuredTokens.map((token) => token.address));
40
+ const sortFn = (a, b) => {
41
+ var _a, _b, _c, _d;
42
+ return parseFloat((_a = b.amount) !== null && _a !== void 0 ? _a : '0') * parseFloat((_b = b.priceUSD) !== null && _b !== void 0 ? _b : '0') -
43
+ parseFloat((_c = a.amount) !== null && _c !== void 0 ? _c : '0') * parseFloat((_d = a.priceUSD) !== null && _d !== void 0 ? _d : '0');
44
+ };
45
+ const formattedTokens = (tokenBalances.length === 0 ? tokens : tokenBalances).map((token) => {
37
46
  token.amount = formatTokenAmount(token.amount);
38
47
  return token;
39
48
  });
40
- return [
41
- ...formatedTokens
42
- .filter((token) => token.amount !== '0')
43
- .sort((a, b) => {
44
- var _a, _b, _c, _d;
45
- return parseFloat((_a = b.amount) !== null && _a !== void 0 ? _a : '0') * parseFloat((_b = b.priceUSD) !== null && _b !== void 0 ? _b : '0') -
46
- parseFloat((_c = a.amount) !== null && _c !== void 0 ? _c : '0') * parseFloat((_d = a.priceUSD) !== null && _d !== void 0 ? _d : '0');
47
- }),
48
- ...formatedTokens.filter((token) => token.amount === '0'),
49
+ const result = [
50
+ ...formattedTokens
51
+ .filter((token) => token.amount !== '0' && featuredTokenAddresses.has(token.address))
52
+ .sort(sortFn),
53
+ ...formattedTokens.filter((token) => token.amount === '0' && featuredTokenAddresses.has(token.address)),
54
+ ...formattedTokens
55
+ .filter((token) => token.amount !== '0' &&
56
+ !featuredTokenAddresses.has(token.address))
57
+ .sort(sortFn),
58
+ ...formattedTokens.filter((token) => token.amount === '0' && !featuredTokenAddresses.has(token.address)),
49
59
  ];
60
+ return result;
50
61
  }), {
51
62
  enabled: isBalanceLoadingEnabled,
52
63
  refetchIntervalInBackground: true,
@@ -56,9 +67,10 @@ export const useTokenBalances = (selectedChainId) => {
56
67
  return {
57
68
  tokens,
58
69
  tokensWithBalance,
70
+ featuredTokens,
59
71
  isLoading,
60
72
  isBalanceLoading: isBalanceLoading && isBalanceLoadingEnabled,
61
73
  isBalanceFetched,
62
- updateBalances: refetch,
74
+ refetch,
63
75
  };
64
76
  };
@@ -0,0 +1,7 @@
1
+ import { Token } from '../types';
2
+ export declare const useTokenSearch: (token: string, chainId: number, enabled?: boolean) => {
3
+ token: Token | undefined;
4
+ isLoading: boolean;
5
+ isFetching: boolean;
6
+ isFetched: boolean;
7
+ };
@@ -0,0 +1,37 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
11
+ import { LiFi } from '../config/lifi';
12
+ export const useTokenSearch = (token, chainId, enabled) => {
13
+ const queryClient = useQueryClient();
14
+ const { data, isLoading, isFetching, isFetched } = useQuery(['token-search', chainId, token], ({ queryKey: [, chainId, token], signal }) => __awaiter(void 0, void 0, void 0, function* () {
15
+ const data = yield LiFi.getToken(chainId, token, {
16
+ signal,
17
+ });
18
+ if (data) {
19
+ queryClient.setQueriesData(['tokens', chainId], (tokens) => {
20
+ if (!(tokens === null || tokens === void 0 ? void 0 : tokens.some((token) => token.address === data.address))) {
21
+ tokens === null || tokens === void 0 ? void 0 : tokens.push(data);
22
+ }
23
+ return tokens;
24
+ });
25
+ }
26
+ return data;
27
+ }), {
28
+ enabled,
29
+ retry: false,
30
+ });
31
+ return {
32
+ token: data,
33
+ isLoading,
34
+ isFetching,
35
+ isFetched,
36
+ };
37
+ };
@@ -1,5 +1,6 @@
1
+ import { Token } from '../types';
1
2
  export declare const useTokens: (selectedChainId: number) => {
2
- tokens: import("@lifi/types").Token[] | undefined;
3
+ tokens: Token[] | undefined;
3
4
  isLoading: boolean;
4
5
  isFetching: boolean;
5
6
  };
@@ -9,12 +9,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { useQuery } from '@tanstack/react-query';
11
11
  import { LiFi } from '../config/lifi';
12
+ import { useFeaturedTokens } from './useFeaturedTokens';
12
13
  export const useTokens = (selectedChainId) => {
13
- const { data: tokens, isLoading, isFetching, } = useQuery(['tokens', selectedChainId], () => __awaiter(void 0, void 0, void 0, function* () {
14
- var _a;
14
+ const featuredTokens = useFeaturedTokens(selectedChainId);
15
+ const { data: tokens, isLoading, isFetching, } = useQuery(['tokens', selectedChainId, featuredTokens === null || featuredTokens === void 0 ? void 0 : featuredTokens.length], () => __awaiter(void 0, void 0, void 0, function* () {
16
+ var _a, _b, _c;
15
17
  const data = yield LiFi.getTokens({ chains: [selectedChainId] });
16
- return (_a = data.tokens) === null || _a === void 0 ? void 0 : _a[selectedChainId];
17
- // .sort((a, b) => (a.symbol > b.symbol ? 1 : -1));
18
+ const featuredTokenAddresses = new Set(featuredTokens === null || featuredTokens === void 0 ? void 0 : featuredTokens.map((token) => token.address));
19
+ return [
20
+ ...((_a = featuredTokens === null || featuredTokens === void 0 ? void 0 : featuredTokens.map((token) => {
21
+ token.featured = true;
22
+ return token;
23
+ })) !== null && _a !== void 0 ? _a : []),
24
+ ...((_c = (_b = data.tokens) === null || _b === void 0 ? void 0 : _b[selectedChainId].filter((token) => !featuredTokenAddresses.has(token.address))) !== null && _c !== void 0 ? _c : []),
25
+ ];
18
26
  }));
19
27
  return {
20
28
  tokens,
@@ -50,6 +50,8 @@
50
50
  "selectChain": "Chain",
51
51
  "selectToken": "Token",
52
52
  "selectChainAndToken": "Select chain and token",
53
+ "featuredTokens": "Featured tokens",
54
+ "otherTokens": "Other tokens",
53
55
  "inProgress": "In progress",
54
56
  "couldntFindTokens": "We couldn't find tokens on this chain or you don't have any.",
55
57
  "stepSwap": "Swap",
package/i18n/index.d.ts CHANGED
@@ -53,6 +53,8 @@ export declare const resources: {
53
53
  selectChain: string;
54
54
  selectToken: string;
55
55
  selectChainAndToken: string;
56
+ featuredTokens: string;
57
+ otherTokens: string;
56
58
  inProgress: string;
57
59
  couldntFindTokens: string;
58
60
  stepSwap: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/widget",
3
- "version": "1.12.1",
3
+ "version": "1.13.0",
4
4
  "description": "LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.",
5
5
  "sideEffects": false,
6
6
  "main": "./index.js",
@@ -44,23 +44,22 @@
44
44
  "@lifi/sdk": "^1.1.3",
45
45
  "@lifi/wallet-management": "^1.1.3",
46
46
  "@mui/icons-material": "^5.8.4",
47
- "@mui/lab": "^5.0.0-alpha.93",
48
- "@mui/material": "^5.9.3",
49
- "@sentry/integrations": "^7.9.0",
50
- "@sentry/react": "^7.9.0",
51
- "@sentry/tracing": "^7.9.0",
47
+ "@mui/lab": "^5.0.0-alpha.94",
48
+ "@mui/material": "^5.10.0",
49
+ "@sentry/integrations": "^7.10.0",
50
+ "@sentry/react": "^7.10.0",
51
+ "@sentry/tracing": "^7.10.0",
52
52
  "@tanstack/react-query": "^4.0.10",
53
+ "@tanstack/react-virtual": "^3.0.0-beta.17",
53
54
  "big.js": "^6.2.1",
54
- "i18next": "^21.8.16",
55
+ "i18next": "^21.9.0",
55
56
  "immer": "^9.0.15",
56
57
  "react": "^18.2.0",
57
58
  "react-dom": "^18.2.0",
58
59
  "react-hook-form": "^7.34.0",
59
60
  "react-i18next": "^11.18.3",
60
- "react-resize-detector": "^7.1.2",
61
61
  "react-router-dom": "^6.3.0",
62
62
  "react-timer-hook": "^3.0.5",
63
- "react-virtual": "^2.10.4",
64
63
  "zustand": "^4.0.0"
65
64
  },
66
65
  "eslintConfig": {
@@ -1,3 +1,2 @@
1
- /// <reference types="react" />
2
1
  import { SwapFormTypeProps } from '../../providers/SwapFormProvider';
3
2
  export declare const ChainSelect: ({ formType }: SwapFormTypeProps) => JSX.Element;
@@ -6,20 +6,23 @@ import { useTranslation } from 'react-i18next';
6
6
  import { Card, CardTitle } from '../../components/Card';
7
7
  import { Select } from '../../components/Select';
8
8
  import { useChains } from '../../hooks';
9
- import { SwapFormKeyHelper, } from '../../providers/SwapFormProvider';
9
+ import { SwapFormKey, SwapFormKeyHelper, } from '../../providers/SwapFormProvider';
10
10
  import { useWidgetConfig } from '../../providers/WidgetProvider';
11
11
  export const ChainSelect = ({ formType }) => {
12
12
  const { t } = useTranslation();
13
- const { setValue } = useFormContext();
13
+ const { setValue, register } = useFormContext();
14
14
  const { fromChain, toChain } = useWidgetConfig();
15
15
  const { chains, isLoading } = useChains();
16
+ const chainKey = SwapFormKeyHelper.getChainKey(formType);
16
17
  const [chainId] = useWatch({
17
- name: [SwapFormKeyHelper.getChainKey(formType)],
18
+ name: [chainKey],
18
19
  });
20
+ const { onChange, onBlur, name, ref } = register(chainKey);
19
21
  const handleChain = (event) => {
20
- setValue(SwapFormKeyHelper.getChainKey(formType), event.target.value);
22
+ onChange(event);
21
23
  setValue(SwapFormKeyHelper.getTokenKey(formType), '');
22
24
  setValue(SwapFormKeyHelper.getAmountKey(formType), '');
25
+ setValue(SwapFormKey.TokenSearchFilter, '');
23
26
  };
24
- return !isLoading ? (_jsxs(Card, { children: [_jsx(CardTitle, { children: t(`swap.selectChain`) }), _jsx(FormControl, Object.assign({ fullWidth: true }, { children: _jsx(Select, Object.assign({ labelId: "label", MenuProps: { elevation: 2 }, defaultValue: formType === 'from' ? fromChain : toChain, value: chainId, onChange: handleChain, IconComponent: KeyboardArrowDownIcon }, { children: chains === null || chains === void 0 ? void 0 : chains.map((chain) => (_jsxs(MenuItem, Object.assign({ value: chain.id }, { children: [_jsx(ListItemAvatar, { children: _jsx(Avatar, Object.assign({ src: chain.logoURI, alt: chain.key }, { children: chain.name[0] })) }), chain.name] }), chain.key))) })) }))] })) : (_jsx(Skeleton, { variant: "rectangular", width: "100%", height: 98, sx: { borderRadius: 1 } }));
27
+ return !isLoading ? (_jsxs(Card, { children: [_jsx(CardTitle, { children: t(`swap.selectChain`) }), _jsx(FormControl, Object.assign({ fullWidth: true }, { children: _jsx(Select, Object.assign({ ref: ref, labelId: chainKey, name: name, MenuProps: { elevation: 2 }, defaultValue: formType === 'from' ? fromChain : toChain, value: chainId, onChange: handleChain, onBlur: onBlur, IconComponent: KeyboardArrowDownIcon }, { children: chains === null || chains === void 0 ? void 0 : chains.map((chain) => (_jsxs(MenuItem, Object.assign({ value: chain.id }, { children: [_jsx(ListItemAvatar, { children: _jsx(Avatar, Object.assign({ src: chain.logoURI, alt: chain.key }, { children: chain.name[0] })) }), chain.name] }), chain.key))) })) }))] })) : (_jsx(Skeleton, { variant: "rectangular", width: "100%", height: 98, sx: { borderRadius: 1 } }));
25
28
  };
@@ -1,2 +1 @@
1
- /// <reference types="react" />
2
1
  export declare const SearchTokenInput: () => JSX.Element;
@@ -11,7 +11,7 @@ export const SearchTokenInput = () => {
11
11
  const { t } = useTranslation();
12
12
  const { register, setValue } = useFormContext();
13
13
  useEffect(() => () => {
14
- setValue(SwapFormKey.SearchTokensFilter, '');
14
+ setValue(SwapFormKey.TokenSearchFilter, '');
15
15
  }, [setValue]);
16
- return (_jsx(Card, { children: _jsx(FormControl, Object.assign({ fullWidth: true }, { children: _jsx(Input, { size: "small", placeholder: t(`swap.tokenSearch`), defaultValue: "", endAdornment: _jsx(InputAdornment, Object.assign({ position: "end" }, { children: _jsx(SearchIcon, {}) })), inputProps: Object.assign({ inputMode: 'search' }, register(SwapFormKey.SearchTokensFilter)), autoComplete: "off" }) })) }));
16
+ return (_jsx(Card, { children: _jsx(FormControl, Object.assign({ fullWidth: true }, { children: _jsx(Input, { size: "small", placeholder: t(`swap.tokenSearch`), defaultValue: "", endAdornment: _jsx(InputAdornment, Object.assign({ position: "end" }, { children: _jsx(SearchIcon, {}) })), inputProps: Object.assign({ inputMode: 'search' }, register(SwapFormKey.TokenSearchFilter)), autoComplete: "off" }) })) }));
17
17
  };
@@ -1,2 +1 @@
1
- /// <reference types="react" />
2
1
  export declare const SelectWalletPage: () => JSX.Element;
@@ -1,2 +1 @@
1
- /// <reference types="react" />
2
1
  export declare const AdvancedPreferences: () => JSX.Element;
@@ -1,2 +1 @@
1
- /// <reference types="react" />
2
1
  export declare const GasPriceSelect: () => JSX.Element;
@@ -1,2 +1 @@
1
- /// <reference types="react" />
2
1
  export declare const SettingsPage: () => JSX.Element;
@@ -1,2 +1 @@
1
- /// <reference types="react" />
2
1
  export declare const SlippageInput: () => JSX.Element;
@@ -17,10 +17,10 @@ export const StatusBottomSheet = ({ status, route, }) => {
17
17
  const navigate = useNavigate();
18
18
  const ref = useRef(null);
19
19
  const { getChainById } = useChains();
20
- const { token, refetchBalance } = useTokenBalance(route.toChainId, route.toToken.address);
20
+ const { token, refetch: refetchBalance } = useTokenBalance(route.toChainId, route.toToken.address);
21
21
  const { setValue } = useFormContext();
22
22
  const clearFromAmount = () => {
23
- refetchBalance(route.fromChainId, route.fromToken.address);
23
+ refetchBalance();
24
24
  setValue(SwapFormKey.FromAmount, '');
25
25
  };
26
26
  const handleDone = () => {
@@ -1,6 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  export declare const formDefaultValues: {
3
3
  fromAmount: string;
4
- searchTokensFilter: string;
4
+ tokenSearchFilter: string;
5
5
  };
6
6
  export declare const SwapFormProvider: React.FC<React.PropsWithChildren<{}>>;
@@ -1,18 +1,35 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ChainId } from '@lifi/sdk';
3
+ import { useEffect } from 'react';
2
4
  import { FormProvider, useForm } from 'react-hook-form';
5
+ import { useWallet } from '../WalletProvider';
3
6
  import { useWidgetConfig } from '../WidgetProvider';
4
7
  import { SwapFormKey } from './types';
5
8
  export const formDefaultValues = {
6
9
  [SwapFormKey.FromAmount]: '',
7
- [SwapFormKey.SearchTokensFilter]: '',
10
+ [SwapFormKey.TokenSearchFilter]: '',
8
11
  };
9
12
  export const SwapFormProvider = ({ children, }) => {
10
- var _a;
13
+ const { account } = useWallet();
11
14
  const { fromChain, fromToken, fromAmount, toChain, toToken } = useWidgetConfig();
12
15
  const methods = useForm({
13
- defaultValues: Object.assign(Object.assign({}, formDefaultValues), { fromChain,
14
- fromToken, fromAmount: (_a = fromAmount === null || fromAmount === void 0 ? void 0 : fromAmount.toPrecision()) !== null && _a !== void 0 ? _a : formDefaultValues.fromAmount, toChain,
15
- toToken }),
16
+ defaultValues: Object.assign(Object.assign({}, formDefaultValues), { fromChain: fromChain !== null && fromChain !== void 0 ? fromChain : ChainId.ETH, fromToken, fromAmount: (typeof fromAmount === 'number'
17
+ ? fromAmount === null || fromAmount === void 0 ? void 0 : fromAmount.toPrecision()
18
+ : fromAmount) || formDefaultValues.fromAmount, toChain: toChain !== null && toChain !== void 0 ? toChain : ChainId.ETH, toToken }),
16
19
  });
20
+ // Set wallet chain as default if no fromChain is provided by config and if it wasn't changed during widget usage
21
+ useEffect(() => {
22
+ const { isDirty, isTouched } = methods.getFieldState(SwapFormKey.FromChain, methods.formState);
23
+ if (account.isActive &&
24
+ account.chainId &&
25
+ !fromChain &&
26
+ !isDirty &&
27
+ !isTouched) {
28
+ methods.setValue(SwapFormKey.FromChain, account.chainId, {
29
+ shouldDirty: false,
30
+ shouldTouch: false,
31
+ });
32
+ }
33
+ }, [account.chainId, account.isActive, fromChain, methods]);
17
34
  return _jsx(FormProvider, Object.assign({}, methods, { children: children }));
18
35
  };
@@ -2,7 +2,7 @@ export declare enum SwapFormKey {
2
2
  FromAmount = "fromAmount",
3
3
  FromChain = "fromChain",
4
4
  FromToken = "fromToken",
5
- SearchTokensFilter = "searchTokensFilter",
5
+ TokenSearchFilter = "tokenSearchFilter",
6
6
  ToChain = "toChain",
7
7
  ToToken = "toToken"
8
8
  }
@@ -10,7 +10,7 @@ export declare type SwapFormValues = {
10
10
  [SwapFormKey.FromAmount]: string;
11
11
  [SwapFormKey.FromChain]: number;
12
12
  [SwapFormKey.FromToken]: string;
13
- [SwapFormKey.SearchTokensFilter]: string;
13
+ [SwapFormKey.TokenSearchFilter]: string;
14
14
  [SwapFormKey.ToChain]: number;
15
15
  [SwapFormKey.ToToken]: string;
16
16
  };
@@ -19,7 +19,6 @@ export declare const SwapFormKeyHelper: {
19
19
  getChainKey: (formType: SwapFormDirection) => 'fromChain' | 'toChain';
20
20
  getTokenKey: (formType: SwapFormDirection) => 'fromToken' | 'toToken';
21
21
  getAmountKey: (formType: SwapFormDirection) => 'fromAmount' | 'toAmount';
22
- getSearchTokensFilterKey: (formType: SwapFormDirection) => string;
23
22
  };
24
23
  export interface SwapFormTypeProps {
25
24
  formType: SwapFormDirection;
@@ -3,7 +3,7 @@ export var SwapFormKey;
3
3
  SwapFormKey["FromAmount"] = "fromAmount";
4
4
  SwapFormKey["FromChain"] = "fromChain";
5
5
  SwapFormKey["FromToken"] = "fromToken";
6
- SwapFormKey["SearchTokensFilter"] = "searchTokensFilter";
6
+ SwapFormKey["TokenSearchFilter"] = "tokenSearchFilter";
7
7
  SwapFormKey["ToChain"] = "toChain";
8
8
  SwapFormKey["ToToken"] = "toToken";
9
9
  })(SwapFormKey || (SwapFormKey = {}));
@@ -11,5 +11,4 @@ export const SwapFormKeyHelper = {
11
11
  getChainKey: (formType) => `${formType}Chain`,
12
12
  getTokenKey: (formType) => `${formType}Token`,
13
13
  getAmountKey: (formType) => `${formType}Amount`,
14
- getSearchTokensFilterKey: (formType) => `${formType}SearchTokensFilter`,
15
14
  };
@@ -10,10 +10,9 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsx as _jsx } from "react/jsx-runtime";
13
- import { ChainId, getChainByKey } from '@lifi/sdk';
13
+ import { getChainByKey } from '@lifi/sdk';
14
14
  import { createContext, useContext, useEffect, useMemo } from 'react';
15
15
  import { updateLiFiConfig } from '../../config/lifi';
16
- import { useWallet } from '../WalletProvider';
17
16
  const stub = () => {
18
17
  throw new Error('You forgot to wrap your component in <WidgetProvider>.');
19
18
  };
@@ -23,26 +22,36 @@ const initialContext = {
23
22
  const WidgetContext = createContext(initialContext);
24
23
  export const useWidgetConfig = () => useContext(WidgetContext);
25
24
  export const WidgetProvider = (_a) => {
26
- var { children } = _a, _b = _a.config, _c = _b === void 0 ? {} : _b, { fromChain, fromToken, toChain, toToken, integrator } = _c, config = __rest(_c, ["fromChain", "fromToken", "toChain", "toToken", "integrator"]);
27
- const { account } = useWallet();
25
+ var { children } = _a, _b = _a.config, _c = _b === void 0 ? {} : _b, { fromChain, fromToken, toChain, toToken, fromAmount, integrator } = _c, config = __rest(_c, ["fromChain", "fromToken", "toChain", "toToken", "fromAmount", "integrator"]);
28
26
  const value = useMemo(() => {
29
- var _a;
27
+ var _a, _b, _c, _d;
30
28
  try {
31
- return Object.assign(Object.assign({}, config), { fromChain: typeof fromChain === 'number'
32
- ? fromChain
33
- : typeof fromChain === 'string'
34
- ? getChainByKey(fromChain.toLowerCase()).id
35
- : (_a = account.chainId) !== null && _a !== void 0 ? _a : ChainId.ETH, toChain: typeof toChain === 'number'
36
- ? toChain
37
- : typeof toChain === 'string'
38
- ? getChainByKey(toChain.toLowerCase()).id
39
- : ChainId.ETH, fromToken: fromToken === null || fromToken === void 0 ? void 0 : fromToken.toLowerCase(), toToken: toToken === null || toToken === void 0 ? void 0 : toToken.toLowerCase() });
29
+ const searchParams = Object.fromEntries(new URLSearchParams(window === null || window === void 0 ? void 0 : window.location.search));
30
+ return Object.assign(Object.assign({}, config), { fromChain: (searchParams.fromChain &&
31
+ isNaN(parseInt(searchParams.fromChain, 10))) ||
32
+ typeof fromChain === 'string'
33
+ ? getChainByKey((_a = (searchParams.fromChain || fromChain)) === null || _a === void 0 ? void 0 : _a.toLowerCase()).id
34
+ : (searchParams.fromChain &&
35
+ !isNaN(parseInt(searchParams.fromChain, 10))) ||
36
+ typeof fromChain === 'number'
37
+ ? parseInt(searchParams.fromChain, 10) || fromChain
38
+ : undefined, toChain: (searchParams.toChain && isNaN(parseInt(searchParams.toChain, 10))) ||
39
+ typeof toChain === 'string'
40
+ ? getChainByKey((_b = (searchParams.toChain || toChain)) === null || _b === void 0 ? void 0 : _b.toLowerCase()).id
41
+ : (searchParams.toChain &&
42
+ !isNaN(parseInt(searchParams.toChain, 10))) ||
43
+ typeof toChain === 'number'
44
+ ? parseInt(searchParams.toChain, 10) || toChain
45
+ : undefined, fromToken: ((_c = searchParams.fromToken) === null || _c === void 0 ? void 0 : _c.toLowerCase()) || (fromToken === null || fromToken === void 0 ? void 0 : fromToken.toLowerCase()), toToken: ((_d = searchParams.toToken) === null || _d === void 0 ? void 0 : _d.toLowerCase()) || (toToken === null || toToken === void 0 ? void 0 : toToken.toLowerCase()), fromAmount: typeof searchParams.fromAmount === 'string' &&
46
+ !isNaN(parseFloat(searchParams.fromAmount))
47
+ ? searchParams.fromAmount
48
+ : fromAmount });
40
49
  }
41
50
  catch (e) {
42
51
  console.warn(e);
43
52
  return config;
44
53
  }
45
- }, [account.chainId, config, fromChain, fromToken, toChain, toToken]);
54
+ }, [config, fromAmount, fromChain, fromToken, toChain, toToken]);
46
55
  useEffect(() => {
47
56
  updateLiFiConfig({
48
57
  defaultRouteOptions: {
package/types/index.d.ts CHANGED
@@ -1 +1,2 @@
1
+ export * from './token';
1
2
  export * from './widget';
package/types/index.js CHANGED
@@ -1 +1,2 @@
1
+ export * from './token';
1
2
  export * from './widget';
@@ -0,0 +1,4 @@
1
+ import { TokenAmount } from '@lifi/sdk';
2
+ export interface Token extends TokenAmount {
3
+ featured?: boolean;
4
+ }
package/types/token.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/types/widget.d.ts CHANGED
@@ -18,8 +18,7 @@ export interface WidgetWalletManagement {
18
18
  signer?: Signer;
19
19
  }
20
20
  interface WidgetConfigBase {
21
- fromAmount?: number;
22
- disabledChains?: number[];
21
+ fromAmount?: number | string;
23
22
  containerStyle?: CSSProperties;
24
23
  theme?: ThemeConfig;
25
24
  appearance?: Appearance;
@@ -27,6 +26,8 @@ interface WidgetConfigBase {
27
26
  disableTelemetry?: boolean;
28
27
  walletManagement?: WidgetWalletManagement;
29
28
  integrator?: string;
29
+ disabledChains?: number[];
30
+ featuredTokens?: Token[];
30
31
  }
31
32
  declare type WidgetFromTokenConfig = {
32
33
  fromChain: `${ChainKey}` | number;
@@ -1,15 +0,0 @@
1
- import { TokenAmount } from '@lifi/sdk';
2
- export declare const tokenAmountMock: TokenAmount;
3
- export declare const skeletonKey = "skeleton";
4
- export declare const createTokenAmountSkeletons: () => {
5
- address: string;
6
- name: string;
7
- amount: string;
8
- blockNumber?: number | undefined;
9
- symbol: string;
10
- decimals: number;
11
- chainId: number;
12
- coinKey?: import("@lifi/sdk").CoinKey | undefined;
13
- priceUSD?: string | undefined;
14
- logoURI?: string | undefined;
15
- }[];
@@ -1,10 +0,0 @@
1
- export const tokenAmountMock = {
2
- address: '-1x0',
3
- amount: '',
4
- chainId: -1,
5
- decimals: 0,
6
- name: '',
7
- symbol: '',
8
- };
9
- export const skeletonKey = 'skeleton';
10
- export const createTokenAmountSkeletons = () => Array.from({ length: 3 }).map((_, index) => (Object.assign(Object.assign({}, tokenAmountMock), { address: `${tokenAmountMock.address}-${index}`, name: `${skeletonKey}-${index}` })));