From 48776d59c8ff8372c898d1df2f16a53c66bcef03 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 18 Nov 2024 19:25:46 -0500 Subject: [PATCH] feat: Replace chain picker with new ChainSearchMenu from Widgets lib (#328) Use Widgets lib's ChainSearchMenu but with a custom field of the number of routes for the oppositely selected chain --------- Co-authored-by: Xaroz --- src/features/chains/ChainSelectField.tsx | 8 ++-- src/features/chains/ChainSelectModal.tsx | 52 +++++++++------------ src/features/chains/utils.ts | 43 ++++++++++++++++- src/features/transfer/TransferTokenForm.tsx | 26 +++++++++-- 4 files changed, 89 insertions(+), 40 deletions(-) diff --git a/src/features/chains/ChainSelectField.tsx b/src/features/chains/ChainSelectField.tsx index bdf5e9c1..50e544e5 100644 --- a/src/features/chains/ChainSelectField.tsx +++ b/src/features/chains/ChainSelectField.tsx @@ -1,4 +1,4 @@ -import { ChevronIcon } from '@hyperlane-xyz/widgets'; +import { ChainSearchMenuProps, ChevronIcon } from '@hyperlane-xyz/widgets'; import { useField, useFormikContext } from 'formik'; import { useState } from 'react'; import { ChainLogo } from '../../components/icons/ChainLogo'; @@ -9,12 +9,12 @@ import { useChainDisplayName } from './hooks'; type Props = { name: string; label: string; - chains: ChainName[]; onChange?: (id: ChainName) => void; disabled?: boolean; + customListItemField: ChainSearchMenuProps['customListItemField']; }; -export function ChainSelectField({ name, label, chains, onChange, disabled }: Props) { +export function ChainSelectField({ name, label, onChange, disabled, customListItemField }: Props) { const [field, , helpers] = useField(name); const { setFieldValue } = useFormikContext(); @@ -59,8 +59,8 @@ export function ChainSelectField({ name, label, chains, onChange, disabled }: Pr setIsModalOpen(false)} - chains={chains} onSelect={handleChange} + customListItemField={customListItemField} /> ); diff --git a/src/features/chains/ChainSelectModal.tsx b/src/features/chains/ChainSelectModal.tsx index d4db7652..cf892582 100644 --- a/src/features/chains/ChainSelectModal.tsx +++ b/src/features/chains/ChainSelectModal.tsx @@ -1,51 +1,41 @@ -import { Modal } from '@hyperlane-xyz/widgets'; -import { useMemo } from 'react'; -import { ChainLogo } from '../../components/icons/ChainLogo'; +import { ChainMetadata } from '@hyperlane-xyz/sdk'; +import { ChainSearchMenu, ChainSearchMenuProps, Modal } from '@hyperlane-xyz/widgets'; +import { useStore } from '../store'; import { useMultiProvider } from './hooks'; -import { getChainDisplayName } from './utils'; export function ChainSelectListModal({ isOpen, close, - chains, onSelect, + customListItemField, }: { isOpen: boolean; close: () => void; - chains: ChainName[]; onSelect: (chain: ChainName) => void; + customListItemField: ChainSearchMenuProps['customListItemField']; }) { const multiProvider = useMultiProvider(); - const sortedChains = useMemo(() => chains.sort(), [chains]); + const { chainMetadataOverrides, setChainMetadataOverrides } = useStore((s) => ({ + chainMetadataOverrides: s.chainMetadataOverrides, + setChainMetadataOverrides: s.setChainMetadataOverrides, + })); - const onSelectChain = (chain: ChainName) => { - return () => { - onSelect(chain); - close(); - }; + const onSelectChain = (chain: ChainMetadata) => { + onSelect(chain.name); + close(); }; return ( - -
- {sortedChains.map((c) => ( - - ))} -
+ + ); } diff --git a/src/features/chains/utils.ts b/src/features/chains/utils.ts index 85cba6ae..71e00170 100644 --- a/src/features/chains/utils.ts +++ b/src/features/chains/utils.ts @@ -1,6 +1,7 @@ import { isAbacusWorksChain } from '@hyperlane-xyz/registry'; -import { MultiProtocolProvider } from '@hyperlane-xyz/sdk'; -import { ProtocolType, toTitleCase } from '@hyperlane-xyz/utils'; +import { ChainMap, MultiProtocolProvider, WarpCore } from '@hyperlane-xyz/sdk'; +import { ProtocolType, toTitleCase, trimToLength } from '@hyperlane-xyz/utils'; +import { ChainSearchMenuProps } from '@hyperlane-xyz/widgets'; export function getChainDisplayName( multiProvider: MultiProtocolProvider, @@ -31,3 +32,41 @@ export function getChainByRpcUrl(multiProvider: MultiProtocolProvider, url?: str (m) => !!m.rpcUrls.find((rpc) => rpc.http.toLowerCase().includes(url.toLowerCase())), ); } + +/** + * Returns an object that contains the amount of + * routes from a single chain to every other chain + */ +export function getNumRoutesWithSelectedChain( + warpCore: WarpCore, + selectedChain: ChainName, + isSelectedChainOrigin: boolean, +): ChainSearchMenuProps['customListItemField'] { + const multiProvider = warpCore.multiProvider; + const chains = multiProvider.metadata; + const selectedChainDisplayName = trimToLength( + getChainDisplayName(multiProvider, selectedChain, true), + 10, + ); + + const data = Object.keys(chains).reduce>( + (result, otherChain) => { + const origin = isSelectedChainOrigin ? selectedChain : otherChain; + const destination = isSelectedChainOrigin ? otherChain : selectedChain; + const tokens = warpCore.getTokensForRoute(origin, destination).length; + result[otherChain] = { + display: `${tokens} route${tokens > 1 ? 's' : ''}`, + sortValue: tokens, + }; + + return result; + }, + {}, + ); + + const preposition = isSelectedChainOrigin ? 'from' : 'to'; + return { + header: `Routes ${preposition} ${selectedChainDisplayName}`, + data, + }; +} diff --git a/src/features/transfer/TransferTokenForm.tsx b/src/features/transfer/TransferTokenForm.tsx index 397bbc5b..b2ee1c8a 100644 --- a/src/features/transfer/TransferTokenForm.tsx +++ b/src/features/transfer/TransferTokenForm.tsx @@ -23,6 +23,7 @@ import { logger } from '../../utils/logger'; import { ChainSelectField } from '../chains/ChainSelectField'; import { ChainWalletWarning } from '../chains/ChainWalletWarning'; import { useChainDisplayName, useMultiProvider } from '../chains/hooks'; +import { getNumRoutesWithSelectedChain } from '../chains/utils'; import { useIsAccountSanctioned } from '../sanctions/hooks/useIsAccountSanctioned'; import { useStore } from '../store'; import { SelectOrInputTokenIds } from '../tokens/SelectOrInputTokenIds'; @@ -113,15 +114,34 @@ function SwapChainsButton({ disabled }: { disabled?: boolean }) { function ChainSelectSection({ isReview }: { isReview: boolean }) { const warpCore = useWarpCore(); - const chains = useMemo(() => warpCore.getTokenChains(), [warpCore]); + + const { values } = useFormikContext(); + + const originRouteCounts = useMemo(() => { + return getNumRoutesWithSelectedChain(warpCore, values.origin, true); + }, [values.origin, warpCore]); + + const destinationRouteCounts = useMemo(() => { + return getNumRoutesWithSelectedChain(warpCore, values.destination, false); + }, [values.destination, warpCore]); return (
- +
- +
); }