UNPKG

@reservoir0x/relay-kit-ui

Version:

Relay is the Fastest and Cheapest Way to Bridge and Transact Across Chains.

402 lines 25.2 kB
"use strict"; 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