@reservoir0x/relay-kit-ui
Version:
Relay is the Fastest and Cheapest Way to Bridge and Transact Across Chains.
402 lines • 25.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const index_js_1 = require("../../primitives/index.js");
const Modal_js_1 = require("../Modal.js");
const react_fontawesome_1 = require("@fortawesome/react-fontawesome");
const free_solid_svg_icons_1 = require("@fortawesome/free-solid-svg-icons");
const useRelayClient_js_1 = tslib_1.__importDefault(require("../../../hooks/useRelayClient.js"));
const index_js_2 = require("../../../hooks/index.js");
const usehooks_ts_1 = require("usehooks-ts");
const relay_kit_hooks_1 = require("@reservoir0x/relay-kit-hooks");
const events_js_1 = require("../../../constants/events.js");
const UnverifiedTokenModal_js_1 = require("../UnverifiedTokenModal.js");
const useEnhancedTokensList_js_1 = require("../../../hooks/useEnhancedTokensList.js");
const ChainFilter_js_1 = tslib_1.__importDefault(require("./ChainFilter.js"));
const TokenList_js_1 = require("./TokenList.js");
const depositAddresses_js_1 = require("../../../constants/depositAddresses.js");
const localStorage_js_1 = require("../../../utils/localStorage.js");
const address_js_1 = require("../../../utils/address.js");
const AccessibleList_js_1 = require("../../primitives/AccessibleList.js");
const solana_js_1 = require("../../../utils/solana.js");
const bitcoin_js_1 = require("../../../utils/bitcoin.js");
const ChainFilterSidebar_js_1 = require("./ChainFilterSidebar.js");
const SuggestedTokens_js_1 = require("./SuggestedTokens.js");
const tokens_js_1 = require("../../../utils/tokens.js");
const relay_sdk_1 = require("@reservoir0x/relay-sdk");
const tokenSelector_js_1 = require("../../../utils/tokenSelector.js");
const index_js_3 = require("../../../hooks/index.js");
const relay_kit_hooks_2 = require("@reservoir0x/relay-kit-hooks");
const TokenSelector = ({ token, trigger, chainIdsFilter, lockedChainIds, context, address, isValidAddress, multiWalletSupportEnabled = false, fromChainWalletVMSupported, supportedWalletVMs, popularChainIds, setToken, onAnalyticEvent }) => {
const relayClient = (0, useRelayClient_js_1.default)();
const { chains: allRelayChains } = (0, index_js_3.useInternalRelayChains)();
const isDesktop = (0, usehooks_ts_1.useMediaQuery)('(min-width: 660px)');
const [open, setOpen] = (0, react_1.useState)(false);
const [unverifiedTokenModalOpen, setUnverifiedTokenModalOpen] = (0, react_1.useState)(false);
const [unverifiedToken, setUnverifiedToken] = (0, react_1.useState)();
const [chainFilter, setChainFilter] = (0, react_1.useState)({
id: undefined,
name: 'All Chains'
});
const { value: tokenSearchInput, debouncedValue: debouncedTokenSearchValue, setValue: setTokenSearchInput } = (0, index_js_2.useDebounceState)('', 500);
const depositAddressOnly = context === 'from'
? chainFilter?.vmType
? !supportedWalletVMs?.includes(chainFilter.vmType)
: !chainFilter.id
? false
: !fromChainWalletVMSupported && chainFilter.id === token?.chainId
: !fromChainWalletVMSupported;
const isReceivingDepositAddress = depositAddressOnly && context === 'to';
const configuredChains = (0, react_1.useMemo)(() => {
let chains = allRelayChains?.filter((chain) => relayClient?.chains?.find((relayChain) => relayChain.id === chain.id)) ?? [];
if (!multiWalletSupportEnabled && context === 'from') {
chains = chains.filter((chain) => chain.vmType === 'evm');
}
if (isReceivingDepositAddress) {
chains = chains.filter(({ id }) => !depositAddresses_js_1.UnsupportedDepositAddressChainIds.includes(id));
}
return (0, tokenSelector_js_1.sortChains)(chains);
}, [
allRelayChains,
relayClient?.chains,
multiWalletSupportEnabled,
context,
depositAddressOnly
]);
const configuredChainIds = (0, react_1.useMemo)(() => {
if (lockedChainIds) {
return lockedChainIds;
}
let _chainIds = configuredChains.map((chain) => chain.id);
if (chainIdsFilter) {
_chainIds = _chainIds.filter((id) => !chainIdsFilter.includes(id));
}
return _chainIds;
}, [configuredChains, lockedChainIds, chainIdsFilter, depositAddressOnly]);
const hasMultipleConfiguredChainIds = configuredChainIds.length > 1;
const chainFilterOptions = context === 'from'
? configuredChains?.filter((chain) => (chain.vmType === 'evm' ||
chain.vmType === 'suivm' ||
chain.vmType === 'tvm' ||
chain.vmType === 'hypevm' ||
chain.id === solana_js_1.solana.id ||
chain.id === solana_js_1.eclipse.id ||
chain.id === bitcoin_js_1.bitcoin.id) &&
configuredChainIds.includes(chain.id))
: configuredChains?.filter((chain) => configuredChainIds.includes(chain.id));
const allChains = [
...(isReceivingDepositAddress
? []
: [{ id: undefined, name: 'All Chains' }]),
...chainFilterOptions
];
const useDefaultTokenList = debouncedTokenSearchValue === '';
const { data: duneTokens, balanceMap: tokenBalances, isLoading: isLoadingBalances } = (0, index_js_2.useDuneBalances)(address &&
address !== relay_sdk_1.evmDeadAddress &&
address !== relay_sdk_1.solDeadAddress &&
address !== relay_sdk_1.bitcoinDeadAddress &&
isValidAddress
? address
: undefined, relayClient?.baseApiUrl?.includes('testnet') ? 'testnet' : 'mainnet', {
staleTime: 60000,
gcTime: 60000
});
const filteredDuneTokenBalances = (0, react_1.useMemo)(() => {
return duneTokens?.balances?.filter((balance) => configuredChainIds.includes(balance.chain_id));
}, [duneTokens?.balances, configuredChainIds]);
const userTokensQuery = (0, react_1.useMemo)(() => {
if (filteredDuneTokenBalances && filteredDuneTokenBalances.length > 0) {
return filteredDuneTokenBalances.map((balance) => `${balance.chain_id}:${balance.address}`);
}
return undefined;
}, [filteredDuneTokenBalances]);
const { data: userTokens, isLoading: isLoadingUserTokens } = (0, relay_kit_hooks_1.useTokenList)(relayClient?.baseApiUrl, userTokensQuery
? {
tokens: userTokensQuery,
limit: 100,
depositAddressOnly,
referrer: relayClient?.source
}
: undefined, {
enabled: !!filteredDuneTokenBalances
});
const isSearchTermValidAddress = (0, address_js_1.isValidAddress)(chainFilter.vmType, debouncedTokenSearchValue, chainFilter.id);
const { data: trendingTokens, isLoading: isLoadingTrendingTokens } = (0, relay_kit_hooks_2.useTrendingCurrencies)(relayClient?.baseApiUrl, {
referrer: relayClient?.source
}, {
enabled: context === 'to'
});
const { data: tokenList, isLoading: isLoadingTokenList } = (0, relay_kit_hooks_1.useTokenList)(relayClient?.baseApiUrl, {
chainIds: chainFilter.id
? [chainFilter.id]
: configuredChains.map((c) => c.id),
address: isSearchTermValidAddress ? debouncedTokenSearchValue : undefined,
term: !isSearchTermValidAddress ? debouncedTokenSearchValue : undefined,
defaultList: useDefaultTokenList && !depositAddressOnly,
limit: 12,
depositAddressOnly,
referrer: relayClient?.source
});
const { data: externalTokenList, isLoading: isLoadingExternalList } = (0, relay_kit_hooks_1.useTokenList)(relayClient?.baseApiUrl, {
chainIds: chainFilter.id
? [chainFilter.id]
: configuredChains.map((c) => c.id),
address: isSearchTermValidAddress
? debouncedTokenSearchValue
: undefined,
term: !isSearchTermValidAddress ? debouncedTokenSearchValue : undefined,
defaultList: false,
limit: 12,
useExternalSearch: true,
referrer: relayClient?.source
}, {
enabled: !!debouncedTokenSearchValue && !depositAddressOnly
});
const combinedTokenList = (0, react_1.useMemo)(() => {
if (!debouncedTokenSearchValue)
return tokenList;
return (0, tokens_js_1.mergeTokenLists)([tokenList, externalTokenList]);
}, [tokenList, externalTokenList, debouncedTokenSearchValue]);
const sortedUserTokens = (0, useEnhancedTokensList_js_1.useEnhancedTokensList)(userTokens, tokenBalances, context, multiWalletSupportEnabled, chainFilter.id, true);
const sortedTrendingTokens = (0, useEnhancedTokensList_js_1.useEnhancedTokensList)(trendingTokens, tokenBalances, 'to', multiWalletSupportEnabled, undefined, false);
const sortedCombinedTokens = (0, useEnhancedTokensList_js_1.useEnhancedTokensList)(combinedTokenList, tokenBalances, context, multiWalletSupportEnabled, chainFilter.id, false);
const [chainSearchInputElement, setChainSearchInputElement] = (0, react_1.useState)(null);
const [tokenSearchInputElement, setTokenSearchInputElement] = (0, react_1.useState)(null);
const inputElement = hasMultipleConfiguredChainIds
? chainSearchInputElement
: tokenSearchInputElement;
const resetState = (0, react_1.useCallback)(() => {
setTokenSearchInput('');
setChainSearchInputElement(null);
setTokenSearchInputElement(null);
}, []);
const onOpenChange = (0, react_1.useCallback)((openChange) => {
let tokenCount = undefined;
let usdcCount = 0;
let usdtCount = 0;
let ethCount = 0;
try {
if (!isLoadingBalances && tokenBalances) {
tokenCount = Object.keys(tokenBalances).length;
Object.values(tokenBalances).forEach((token) => {
const tokenSymbol = token.symbol
? token.symbol.toLowerCase()
: token.symbol;
if (tokenSymbol === 'usdc') {
usdcCount += 1;
}
else if (tokenSymbol === 'usdt') {
usdtCount += 1;
}
else if (tokenSymbol === 'eth') {
ethCount += 1;
}
});
}
onAnalyticEvent?.(openChange
? events_js_1.EventNames.SWAP_START_TOKEN_SELECT
: events_js_1.EventNames.SWAP_EXIT_TOKEN_SELECT, {
direction: context === 'from' ? 'input' : 'output',
...(!openChange && {
balanceData: {
tokenCount,
usdcCount,
usdtCount,
ethCount,
balanceAddress: address
}
})
});
}
catch (error) {
console.error(error);
}
if (openChange) {
const chainFilter = (0, tokenSelector_js_1.getInitialChainFilter)(chainFilterOptions, context, depositAddressOnly, token);
setChainFilter(chainFilter);
}
setOpen(openChange);
}, [
tokenBalances,
isLoadingBalances,
context,
address,
onAnalyticEvent,
setOpen,
chainFilterOptions,
depositAddressOnly,
token
]);
const handleTokenSelection = (0, react_1.useCallback)((selectedToken) => {
const isVerified = selectedToken.verified;
const direction = context === 'from' ? 'input' : 'output';
let position = undefined;
if (debouncedTokenSearchValue.length > 0) {
position = sortedCombinedTokens.findIndex((token) => token.chainId === selectedToken.chainId &&
token.address?.toLowerCase() ===
selectedToken.address?.toLowerCase());
}
if (!isVerified) {
const relayUiKitData = (0, localStorage_js_1.getRelayUiKitData)();
const tokenKey = `${selectedToken.chainId}:${selectedToken.address}`;
const isAlreadyAccepted = relayUiKitData.acceptedUnverifiedTokens.includes(tokenKey);
if (isAlreadyAccepted) {
onAnalyticEvent?.(events_js_1.EventNames.SWAP_TOKEN_SELECT, {
direction,
token_symbol: selectedToken.symbol,
chain_id: selectedToken.chainId,
token_address: selectedToken.address,
search_term: debouncedTokenSearchValue,
position
});
setToken(selectedToken);
}
else {
setUnverifiedToken(selectedToken);
setUnverifiedTokenModalOpen(true);
return;
}
}
else {
onAnalyticEvent?.(events_js_1.EventNames.SWAP_TOKEN_SELECT, {
direction,
token_symbol: selectedToken.symbol,
chain_id: selectedToken.chainId,
token_address: selectedToken.address,
search_term: debouncedTokenSearchValue,
position
});
setToken(selectedToken);
}
onOpenChange(false);
}, [
setToken,
onOpenChange,
resetState,
context,
onAnalyticEvent,
debouncedTokenSearchValue,
sortedCombinedTokens
]);
(0, react_1.useEffect)(() => {
if (!open) {
resetState();
}
}, [open]);
(0, react_1.useEffect)(() => {
if (open && inputElement && isDesktop) {
inputElement.focus();
}
}, [open, inputElement]);
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { style: { position: 'relative' }, children: (0, jsx_runtime_1.jsx)(Modal_js_1.Modal, { open: open, onOpenChange: onOpenChange, showCloseButton: true, trigger: trigger, css: {
p: '4',
display: 'flex',
flexDirection: 'column',
height: 'min(85vh, 600px)',
'@media(min-width: 660px)': {
minWidth: isDesktop
? hasMultipleConfiguredChainIds
? 660
: 408
: 400,
maxWidth: isDesktop && hasMultipleConfiguredChainIds ? 660 : 408
}
}, children: (0, jsx_runtime_1.jsxs)(index_js_1.Flex, { direction: "column", css: {
width: '100%',
height: '100%',
gap: '3',
overflowY: 'hidden'
}, children: [(0, jsx_runtime_1.jsx)(index_js_1.Text, { style: "h6", children: "Select Token" }), (0, jsx_runtime_1.jsxs)(index_js_1.Flex, { css: { flex: 1, gap: '3', overflow: 'hidden' }, children: [isDesktop &&
(!configuredChainIds || hasMultipleConfiguredChainIds) ? ((0, jsx_runtime_1.jsx)(ChainFilterSidebar_js_1.ChainFilterSidebar, { options: allChains, value: chainFilter, isOpen: open, onSelect: setChainFilter, onAnalyticEvent: onAnalyticEvent, onInputRef: setChainSearchInputElement, tokenSearchInputRef: tokenSearchInputElement, popularChainIds: popularChainIds, context: context })) : null, (0, jsx_runtime_1.jsxs)(AccessibleList_js_1.AccessibleList, { onSelect: (value) => {
if (value === 'input')
return;
const [chainId, ...addressParts] = value.split(':');
const address = addressParts.join(':');
const allTokens = [
...sortedUserTokens,
...sortedCombinedTokens,
...sortedTrendingTokens
];
const selectedToken = allTokens.find((token) => token.chainId === Number(chainId) &&
token.address?.toLowerCase() === address?.toLowerCase());
if (selectedToken) {
handleTokenSelection(selectedToken);
}
}, css: {
display: 'flex',
flexDirection: 'column',
width: '100%',
minWidth: 0,
height: '100%'
}, children: [(0, jsx_runtime_1.jsxs)(index_js_1.Flex, { direction: "column", align: "start", css: {
width: '100%',
gap: '2',
background: 'modal-background'
}, children: [(0, jsx_runtime_1.jsx)(AccessibleList_js_1.AccessibleListItem, { value: "input", asChild: true, children: (0, jsx_runtime_1.jsx)(index_js_1.Input, { ref: setTokenSearchInputElement, placeholder: "Search for a token or paste address", icon: (0, jsx_runtime_1.jsx)(index_js_1.Box, { css: { color: 'gray9' }, children: (0, jsx_runtime_1.jsx)(react_fontawesome_1.FontAwesomeIcon, { icon: free_solid_svg_icons_1.faMagnifyingGlass, width: 16, height: 16 }) }), containerCss: {
width: '100%',
height: 40,
mb: isDesktop ? '1' : '0'
}, css: {
width: '100%',
_placeholder_parent: {
textOverflow: 'ellipsis'
}
}, value: tokenSearchInput, onChange: (e) => {
const value = e.target.value;
setTokenSearchInput(value);
if ((0, address_js_1.isValidAddress)(chainFilter.vmType, value)) {
onAnalyticEvent?.(events_js_1.EventNames.TOKEN_SELECTOR_CONTRACT_SEARCH, {
search_term: value,
chain_filter: chainFilter.id
});
}
} }) }), !isDesktop &&
(!configuredChainIds || hasMultipleConfiguredChainIds) ? ((0, jsx_runtime_1.jsx)(ChainFilter_js_1.default, { options: allChains, value: chainFilter, onSelect: setChainFilter, popularChainIds: popularChainIds })) : null] }), (0, jsx_runtime_1.jsxs)(index_js_1.Flex, { direction: "column", css: {
flex: 1,
overflowY: 'auto',
gap: '3',
pt: '2',
scrollbarColor: 'var(--relay-colors-gray5) transparent'
}, children: [chainFilter.id &&
tokenSearchInput.length === 0 &&
!depositAddressOnly ? ((0, jsx_runtime_1.jsx)(SuggestedTokens_js_1.SuggestedTokens, { chainId: chainFilter.id, depositAddressOnly: depositAddressOnly, onSelect: (token) => {
handleTokenSelection(token);
} })) : null, tokenSearchInput.length > 0 ? ((0, jsx_runtime_1.jsx)(TokenList_js_1.TokenList, { title: "Results", tokens: sortedCombinedTokens, isLoading: isLoadingTokenList ||
tokenSearchInput !== debouncedTokenSearchValue, isLoadingBalances: isLoadingBalances, chainFilterId: chainFilter.id })) : ((0, jsx_runtime_1.jsx)(index_js_1.Flex, { direction: "column", css: { gap: '3' }, children: [
{
title: 'Your Tokens',
tokens: sortedUserTokens,
isLoading: isLoadingUserTokens,
show: sortedUserTokens.length > 0
},
{
title: 'Global 24H Volume',
tokens: sortedCombinedTokens,
isLoading: isLoadingTokenList,
show: true
},
{
title: 'Relay 24H Volume',
tokens: sortedTrendingTokens,
isLoading: isLoadingTrendingTokens,
show: context === 'to' && chainFilter.id === undefined,
showMoreButton: true
}
]
.sort((a, b) => (context === 'to' ? -1 : 1))
.map(({ title, tokens, isLoading, show, showMoreButton }) => show && ((0, jsx_runtime_1.jsx)(TokenList_js_1.TokenList, { title: title, tokens: tokens, isLoading: isLoading, isLoadingBalances: isLoadingBalances, chainFilterId: chainFilter.id, showMoreButton: showMoreButton }, title))) })), !isLoadingTokenList &&
!isLoadingExternalList &&
tokenList?.length === 0 &&
externalTokenList?.length === 0 ? ((0, jsx_runtime_1.jsxs)(index_js_1.Flex, { direction: "column", align: "center", css: { py: '5', maxWidth: 312, alignSelf: 'center' }, children: [!chainFilter?.id && isSearchTermValidAddress && ((0, jsx_runtime_1.jsx)(index_js_1.Box, { css: { color: 'gray8', mb: '2' }, children: (0, jsx_runtime_1.jsx)(react_fontawesome_1.FontAwesomeIcon, { icon: free_solid_svg_icons_1.faFolderOpen, size: "xl", width: 27, height: 24 }) })), (0, jsx_runtime_1.jsx)(index_js_1.Text, { color: "subtle", style: "body2", css: { textAlign: 'center' }, children: !chainFilter?.id && isSearchTermValidAddress
? 'No results. Switch to the desired chain to search by contract.'
: 'No results.' })] })) : null] }, chainFilter.id ?? 'all')] })] })] }) }) }), unverifiedTokenModalOpen && ((0, jsx_runtime_1.jsx)(UnverifiedTokenModal_js_1.UnverifiedTokenModal, { open: unverifiedTokenModalOpen, onOpenChange: setUnverifiedTokenModalOpen, data: unverifiedToken ? { token: unverifiedToken } : undefined, onAcceptToken: (token) => {
if (token) {
handleTokenSelection(token);
}
setUnverifiedTokenModalOpen(false);
} }))] }));
};
exports.default = TokenSelector;
//# sourceMappingURL=TokenSelector.js.map