diff --git a/.release/.changeset/eighty-clouds-jog.md b/.release/.changeset/eighty-clouds-jog.md new file mode 100644 index 00000000..d50b8e30 --- /dev/null +++ b/.release/.changeset/eighty-clouds-jog.md @@ -0,0 +1,5 @@ +--- +"@bnb-chain/canonical-bridge-widget": patch +--- + +Update token list sort rules diff --git a/.release/.changeset/light-emus-switch.md b/.release/.changeset/light-emus-switch.md new file mode 100644 index 00000000..a63eadc9 --- /dev/null +++ b/.release/.changeset/light-emus-switch.md @@ -0,0 +1,5 @@ +--- +'@bnb-chain/canonical-bridge-widget': patch +--- + +Show token address on `You Receive` diff --git a/.release/.changeset/pre.json b/.release/.changeset/pre.json new file mode 100644 index 00000000..21a91838 --- /dev/null +++ b/.release/.changeset/pre.json @@ -0,0 +1,14 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "@bnb-chain/canonical-bridge-sdk": "0.4.2", + "@bnb-chain/canonical-bridge-widget": "0.5.7" + }, + "changesets": [ + "eighty-clouds-jog", + "light-emus-switch", + "sharp-dryers-hear", + "slimy-crabs-know" + ] +} diff --git a/.release/.changeset/sharp-dryers-hear.md b/.release/.changeset/sharp-dryers-hear.md new file mode 100644 index 00000000..4c231116 --- /dev/null +++ b/.release/.changeset/sharp-dryers-hear.md @@ -0,0 +1,5 @@ +--- +"@bnb-chain/canonical-bridge-widget": patch +--- + +Support searching token by address diff --git a/.release/.changeset/slimy-crabs-know.md b/.release/.changeset/slimy-crabs-know.md new file mode 100644 index 00000000..d819d83e --- /dev/null +++ b/.release/.changeset/slimy-crabs-know.md @@ -0,0 +1,5 @@ +--- +"@bnb-chain/canonical-bridge-widget": patch +--- + +Show token address on `You Receive` diff --git a/apps/canonical-bridge-ui/token-config/mainnet/chains.ts b/apps/canonical-bridge-ui/token-config/mainnet/chains.ts index e66aa78b..1edae413 100644 --- a/apps/canonical-bridge-ui/token-config/mainnet/chains.ts +++ b/apps/canonical-bridge-ui/token-config/mainnet/chains.ts @@ -1,6 +1,20 @@ import { IChainConfig } from '@bnb-chain/canonical-bridge-widget'; export const chains: IChainConfig[] = [ + { + id: 56, + name: 'BNB Smart Chain', + nativeCurrency: { + name: 'BNB Chain Native Token', + symbol: 'BNB', + decimals: 18, + }, + rpcUrl: 'https://bsc-dataseed.bnbchain.org', + explorer: { + name: 'bscscan', + url: 'https://bscscan.com', + }, + }, { id: 1, name: 'Ethereum', @@ -71,20 +85,6 @@ export const chains: IChainConfig[] = [ url: 'https://crab-scan.darwinia.network', }, }, - { - id: 56, - name: 'BNB Smart Chain', - nativeCurrency: { - name: 'BNB Chain Native Token', - symbol: 'BNB', - decimals: 18, - }, - rpcUrl: 'https://bsc-dataseed.bnbchain.org', - explorer: { - name: 'bscscan', - url: 'https://bscscan.com', - }, - }, { id: 57, name: 'Syscoin', diff --git a/apps/canonical-bridge-ui/token-config/mainnet/useTransferConfig.ts b/apps/canonical-bridge-ui/token-config/mainnet/useTransferConfig.ts index a68d93bb..aab46a77 100644 --- a/apps/canonical-bridge-ui/token-config/mainnet/useTransferConfig.ts +++ b/apps/canonical-bridge-ui/token-config/mainnet/useTransferConfig.ts @@ -125,7 +125,7 @@ export function useTransferConfig() { exclude: { chains: [], tokens: { - 1: ['cUSDCv3'], + 1: ['cUSDCv3', '0x5e21d1ee5cf0077b314c381720273ae82378d613'], 56: [ '0x67d66e8ec1fd25d98b3ccd3b19b7dc4b4b7fc493', '0x0000000000000000000000000000000000000000', diff --git a/packages/canonical-bridge-widget/CHANGELOG.md b/packages/canonical-bridge-widget/CHANGELOG.md index 7ae3936b..6289a2d4 100644 --- a/packages/canonical-bridge-widget/CHANGELOG.md +++ b/packages/canonical-bridge-widget/CHANGELOG.md @@ -1,5 +1,42 @@ # @bnb-chain/canonical-bridge-widget +## 0.5.8-alpha.5 + +### Patch Changes + +- Update token list sort rules + +## 0.5.8-alpha.4 + +### Patch Changes + +- Show token address on `You Receive` + +## 0.5.8-alpha.3 + +### Patch Changes + +- Show token address on `You Receive` + +## 0.5.8-alpha.2 + +### Patch Changes + +- Support searching token by address + +## 0.5.8-alpha.1 + +### Patch Changes + +- Show token address on `You Receive` +- Show token address on `You Receive` + +## 0.5.8-alpha.0 + +### Patch Changes + +- Show token address on `You Recivce` + ## 0.5.7 ### Patch Changes diff --git a/packages/canonical-bridge-widget/package.json b/packages/canonical-bridge-widget/package.json index 2eaa8ef1..d9b42cba 100644 --- a/packages/canonical-bridge-widget/package.json +++ b/packages/canonical-bridge-widget/package.json @@ -1,6 +1,6 @@ { "name": "@bnb-chain/canonical-bridge-widget", - "version": "0.5.7", + "version": "0.5.8-alpha.5", "description": "canonical bridge widget", "author": "bnb-chain", "private": false, diff --git a/packages/canonical-bridge-widget/src/core/components/IconImage/index.tsx b/packages/canonical-bridge-widget/src/core/components/IconImage/index.tsx index 88398828..ddab849b 100644 --- a/packages/canonical-bridge-widget/src/core/components/IconImage/index.tsx +++ b/packages/canonical-bridge-widget/src/core/components/IconImage/index.tsx @@ -22,7 +22,7 @@ export function IconImage(props: IconImageProps) { className="default-icon" size={boxSize} {...restProps} - bg={fallbackBgColor ?? theme.colors[colorMode].support.primary[1]} + bg={fallbackBgColor ?? theme.colors[colorMode].support.primary[4]} /> ) : ( { iconProps?: IconProps; + children?: React.ReactElement; } export const InfoTooltip = (props: InfoTooltipProps) => { - const { iconProps, ...restProps } = props; + const { iconProps, children, ...restProps } = props; // Make tooltip controlled on mobile devices, default tooltip doesn't work. const isBase = useBreakpointValue({ base: true, md: false }) ?? false; const { isOpen, onOpen, onToggle, onClose } = useDisclosure(); + const clone = useMemo(() => { + const element = children || ; + return React.cloneElement(element, { + ...iconProps, + ...(isBase && { + onMouseEnter: onOpen, + onMouseLeave: onClose, + onClick: onToggle, + }), + }); + }, [children, iconProps, isBase, onClose, onOpen, onToggle]); + if (!restProps.label) return null; return ( { })} {...restProps} > - + {clone} ); }; diff --git a/packages/canonical-bridge-widget/src/core/components/icons/AlertIcon.tsx b/packages/canonical-bridge-widget/src/core/components/icons/AlertIcon.tsx new file mode 100644 index 00000000..1e04ee42 --- /dev/null +++ b/packages/canonical-bridge-widget/src/core/components/icons/AlertIcon.tsx @@ -0,0 +1,27 @@ +import { Icon, IconProps } from '@bnb-chain/space'; + +export function AlertIcon(props: IconProps) { + return ( + + + + + + + + + ); +} diff --git a/packages/canonical-bridge-widget/src/core/locales/en.ts b/packages/canonical-bridge-widget/src/core/locales/en.ts index 2991cc42..ad0829fa 100644 --- a/packages/canonical-bridge-widget/src/core/locales/en.ts +++ b/packages/canonical-bridge-widget/src/core/locales/en.ts @@ -24,6 +24,8 @@ export const en = { 'route.fees.title': 'Fees:', 'route.time.title': 'Time:', 'route.allowed-send-amount': 'Your send amount should be between {min} to {max} {symbol}', + 'route.token-address.tips': + 'Please double check the received token address before making the transaction.', 'route.no-found.title': 'No Available Routes', 'route.no-found.desc': `Low liquidity, small input, or incompatible network/token. Adjust and retry.`, @@ -84,7 +86,7 @@ export const en = { 'Try adjusting your search request to find what you’re looking for', 'select-modal.token.title': 'Choose Token', - 'select-modal.token.placeholder': 'Search token by name', + 'select-modal.token.placeholder': 'Search token by name or address', 'select-modal.token.column.name': 'Name', 'select-modal.token.column.balance': 'Wallet Balance', 'select-modal.token.incompatible.tooltip': diff --git a/packages/canonical-bridge-widget/src/core/utils/address.ts b/packages/canonical-bridge-widget/src/core/utils/address.ts index 89bf1b60..6147ccef 100644 --- a/packages/canonical-bridge-widget/src/core/utils/address.ts +++ b/packages/canonical-bridge-widget/src/core/utils/address.ts @@ -50,7 +50,7 @@ export function isEvmAddress(address?: string) { return !!address && /^0x[a-f0-9]{40}$/i.test(address); } -export function isNativeToken(tokenAddress: string, chainType: ChainType = 'evm') { +export function isNativeToken(tokenAddress?: string, chainType: ChainType = 'evm') { if (chainType === 'solana') { return tokenAddress === '11111111111111111111111111111111'; } diff --git a/packages/canonical-bridge-widget/src/modules/aggregator/components/SelectModal/ChooseTokenModal.tsx b/packages/canonical-bridge-widget/src/modules/aggregator/components/SelectModal/ChooseTokenModal.tsx index 2b9e162e..af62c3ca 100644 --- a/packages/canonical-bridge-widget/src/modules/aggregator/components/SelectModal/ChooseTokenModal.tsx +++ b/packages/canonical-bridge-widget/src/modules/aggregator/components/SelectModal/ChooseTokenModal.tsx @@ -43,17 +43,18 @@ export function ChooseTokenModal(props: ChooseTokenModalProps) { toChainId: toChain?.id, }); - const { isNoResult, result, onSearch } = useSearch({ + const { isNoResult, result, keyword, onSearch } = useSearch({ data: tokens, filter: (item, keyword) => { return ( - item.displaySymbol?.toLowerCase().includes(keyword) || - item.name?.toLowerCase().includes(keyword) + item.displaySymbol?.toLowerCase().includes(keyword?.toLowerCase()) || + item.name?.toLowerCase().includes(keyword?.toLowerCase()) || + isSameAddress(item.address, keyword) ); }, }); - const { isLoading, data } = useTokenList(result); + const { isLoading, data } = useTokenList(result, keyword); const evmAccount = useAccount(); const tronAccount = useTronAccount(); @@ -108,7 +109,7 @@ export function ChooseTokenModal(props: ChooseTokenModalProps) { iconUrl={item.icon} isActive={isActive} isDisabled={isDisabled} - data-address={item.address?.toLowerCase()} + data-address={item.address} incompatibleTooltip={formatMessage({ id: 'select-modal.token.incompatible.tooltip', })} @@ -146,24 +147,22 @@ export function ChooseTokenModal(props: ChooseTokenModalProps) { - {(!isActive || isNative) && ( - - {item.name} - - )} + + {item.name} + {!isNative && ( (props: UseSearchProps) { }; const result = useMemo(() => { - return data.filter((item) => filter(item, keyword.toLowerCase())).sort(sorter); + return data.filter((item) => filter(item, keyword)).sort(sorter); }, [data, filter, keyword, sorter]); const isNoResult = keyword.length > 0 && !result.length; @@ -24,5 +24,6 @@ export function useSearch(props: UseSearchProps) { onSearch, isNoResult, result, + keyword, }; } diff --git a/packages/canonical-bridge-widget/src/modules/aggregator/components/SelectModal/hooks/useTokenList.ts b/packages/canonical-bridge-widget/src/modules/aggregator/components/SelectModal/hooks/useTokenList.ts index 8e27d4b7..41fe26b8 100644 --- a/packages/canonical-bridge-widget/src/modules/aggregator/components/SelectModal/hooks/useTokenList.ts +++ b/packages/canonical-bridge-widget/src/modules/aggregator/components/SelectModal/hooks/useTokenList.ts @@ -9,7 +9,7 @@ import { sortTokens } from '@/modules/aggregator/shared/sortTokens'; import { useAggregator } from '@/modules/aggregator/components/AggregatorProvider'; import { isChainOrTokenCompatible } from '@/modules/aggregator/shared/isChainOrTokenCompatible'; -export function useTokenList(tokens: IBridgeToken[] = []) { +export function useTokenList(tokens: IBridgeToken[] = [], keyword?: string) { const selectedToken = useAppSelector((state) => state.transfer.selectedToken); const isLoadingTokenBalances = useAppSelector((state) => state.aggregator.isLoadingTokenBalances); const isLoadingTokenPrices = useAppSelector((state) => state.aggregator.isLoadingTokenPrices); @@ -46,11 +46,11 @@ export function useTokenList(tokens: IBridgeToken[] = []) { return sortedTokens; }, [ + tokens, transferConfig.order?.tokens, getTokenBalance, getTokenPrice, selectedToken?.address, - tokens, ]); return { data: sortedTokens, isLoading: isLoadingTokenBalances || isLoadingTokenPrices }; diff --git a/packages/canonical-bridge-widget/src/modules/transfer/components/ReceiveInfo/index.tsx b/packages/canonical-bridge-widget/src/modules/transfer/components/ReceiveInfo/index.tsx index bef7a378..b29f63ab 100644 --- a/packages/canonical-bridge-widget/src/modules/transfer/components/ReceiveInfo/index.tsx +++ b/packages/canonical-bridge-widget/src/modules/transfer/components/ReceiveInfo/index.tsx @@ -209,6 +209,7 @@ export const ReceiveInfo = ({ onOpen }: ReceiveInfoProps) => { receiveAmt ? formatNumber(Number(Number(receiveAmt)), 8) : undefined } toTokenInfo={toTokenInfo?.[bridgeType]} + hoverToShowTokenAddress={false} /> )} diff --git a/packages/canonical-bridge-widget/src/modules/transfer/components/TransferOverview/RouteInfo/RouteName/index.tsx b/packages/canonical-bridge-widget/src/modules/transfer/components/TransferOverview/RouteInfo/RouteName/index.tsx index b6d9abdf..50a285e3 100644 --- a/packages/canonical-bridge-widget/src/modules/transfer/components/TransferOverview/RouteInfo/RouteName/index.tsx +++ b/packages/canonical-bridge-widget/src/modules/transfer/components/TransferOverview/RouteInfo/RouteName/index.tsx @@ -44,7 +44,7 @@ export const RouteName = React.memo( w={['16px', '16px', '16px', '20px']} h={['16px', '16px', '16px', '20px']} borderRadius={'100%'} - mb={['4px', '4px', '4px', '0']} + mb={['0', '0', '4px', '0']} /> ) : bridgeType === 'stargate' ? ( @@ -63,7 +63,7 @@ export const RouteName = React.memo( w={['16px', '16px', '16px', '20px']} h={['16px', '16px', '16px', '20px']} borderRadius={'100%'} - mb={['4px', '4px', '4px', '0']} + mb={['0', '0', '4px', '0']} /> ) : null} diff --git a/packages/canonical-bridge-widget/src/modules/transfer/components/TransferOverview/RouteInfo/RouteTitle.tsx b/packages/canonical-bridge-widget/src/modules/transfer/components/TransferOverview/RouteInfo/RouteTitle.tsx index 69a5423a..5bd08573 100644 --- a/packages/canonical-bridge-widget/src/modules/transfer/components/TransferOverview/RouteInfo/RouteTitle.tsx +++ b/packages/canonical-bridge-widget/src/modules/transfer/components/TransferOverview/RouteInfo/RouteTitle.tsx @@ -1,23 +1,34 @@ -import { Flex, useColorMode, useTheme } from '@bnb-chain/space'; +import { Center, Flex, Link, useColorMode, useIntl, useTheme } from '@bnb-chain/space'; +import React from 'react'; import { TokenInfoTooltip } from '@/modules/transfer/components/TransferOverview/RouteInfo/TokenInfoTooltip'; import { IconImage } from '@/core/components/IconImage'; import { useAppSelector } from '@/modules/store/StoreProvider'; import { formatTokenUrl } from '@/core/utils/string'; import { IBridgeTokenBaseInfo } from '@/modules/aggregator/types'; +import { AlertIcon } from '@/core/components/icons/AlertIcon'; +import { InfoTooltip } from '@/core/components/InfoTooltip'; +import { formatAppAddress, isNativeToken } from '@/core/utils/address'; interface RouteTitleProps { receiveAmt?: string; toTokenInfo?: IBridgeTokenBaseInfo; isError?: boolean; + hoverToShowTokenAddress?: boolean; } -export const RouteTitle = ({ receiveAmt, toTokenInfo, isError }: RouteTitleProps) => { +export const RouteTitle = ({ + receiveAmt, + toTokenInfo, + isError, + hoverToShowTokenAddress = true, +}: RouteTitleProps) => { const theme = useTheme(); const { colorMode } = useColorMode(); const toChain = useAppSelector((state) => state.transfer.toChain); const tokenUrl = formatTokenUrl(toChain?.tokenUrlPattern, toTokenInfo?.address); + const isNative = isNativeToken(toTokenInfo?.address, toChain?.chainType); return ( {receiveAmt} - {toTokenInfo && ( + {toTokenInfo && hoverToShowTokenAddress && ( - - - {toTokenInfo.displaySymbol} - + )} + + {toTokenInfo && !hoverToShowTokenAddress && ( + + )} ); }; + +interface ToTokenInfoProps { + toTokenInfo: IBridgeTokenBaseInfo; + tokenUrl: string; + hoverToShowTokenAddress: boolean; + isNative: boolean; +} + +export const ToTokenInfo = React.forwardRef( + (props: ToTokenInfoProps, ref: React.LegacyRef) => { + const { + toTokenInfo, + tokenUrl, + hoverToShowTokenAddress = true, + isNative = false, + ...restProps + } = props; + const theme = useTheme(); + const { colorMode } = useColorMode(); + const { formatMessage } = useIntl(); + + return ( + + + {toTokenInfo.displaySymbol} + {!hoverToShowTokenAddress && !isNative && ( + + + ( + {formatAppAddress({ + address: toTokenInfo.address, + })} + ) + + +
+ +
+
+
+ )} +
+ ); + }, +); + +ToTokenInfo.displayName = 'ToTokenInfo';