diff --git a/package.json b/package.json index 46dd1c781f1..332968432ff 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@rabby-wallet/gnosis-sdk": "1.3.10", "@rabby-wallet/page-provider": "0.4.2", "@rabby-wallet/rabby-action": "0.1.8", - "@rabby-wallet/rabby-api": "0.9.4", + "@rabby-wallet/rabby-api": "0.9.7", "@rabby-wallet/rabby-security-engine": "2.0.7", "@rabby-wallet/rabby-swap": "0.0.43", "@rabby-wallet/widgets": "1.0.9", diff --git a/src/ui/component/ChainIcon.tsx b/src/ui/component/ChainIcon.tsx index 8e2fc9b9057..7473b2a2a8c 100644 --- a/src/ui/component/ChainIcon.tsx +++ b/src/ui/component/ChainIcon.tsx @@ -22,8 +22,8 @@ const ChainIconEle = styled.img` height: 20px; } &.mini { - width: 16px; - height: 16px; + width: 18px; + height: 18px; } `; diff --git a/src/ui/component/ChainSelector/InForm.tsx b/src/ui/component/ChainSelector/InForm.tsx index 2f7ba32f02d..fa314f9b837 100644 --- a/src/ui/component/ChainSelector/InForm.tsx +++ b/src/ui/component/ChainSelector/InForm.tsx @@ -46,12 +46,12 @@ const ChainWrapper = styled.div` } } &.swap { - gap: 4px; + gap: 8px; padding: 0 16px; border: 0.5px solid transparent; background: var(--r-neutral-card1, #fff); border-radius: 8px; - height: 40px; + height: 44px; position: relative; &::before { content: ''; @@ -72,7 +72,8 @@ const ChainWrapper = styled.div` } & > .name { - color: var(--r-neutral-body, #3e495e); + color: var(--r-neutral-title-1, #192945); + font-size: 15px; } } &:hover { @@ -145,7 +146,7 @@ export const ChainRender = ({ size={swap ? 'mini' : 'small'} showCustomRPCToolTip tooltipProps={{ - visible: swap ? false : undefined, + visible: swap || mini ? false : undefined, }} /> )} diff --git a/src/ui/component/TokenSelect/index.tsx b/src/ui/component/TokenSelect/index.tsx index ad36eea434c..7853847c6f1 100644 --- a/src/ui/component/TokenSelect/index.tsx +++ b/src/ui/component/TokenSelect/index.tsx @@ -1,4 +1,11 @@ -import React, { ComponentProps, useMemo, useState, useEffect } from 'react'; +import React, { + ComponentProps, + useMemo, + useState, + useEffect, + forwardRef, + useImperativeHandle, +} from 'react'; import { Input, Skeleton } from 'antd'; import { TokenItem } from 'background/service/openapi'; import { abstractTokenToTokenItem, getTokenSymbol } from 'ui/utils/token'; @@ -90,167 +97,244 @@ type TokenSelectProps = BridgeFromProps | OtherProps; const defaultExcludeTokens = []; -const TokenSelect = ({ - token, - onChange, - onTokenChange, - chainId, - excludeTokens = defaultExcludeTokens, - type = 'default', - placeholder, - hideChainIcon = true, - value, - loading = false, - tokenRender, - useSwapTokenList = false, - disabledTips = 'Not supported', - drawerHeight, - supportChains, -}: TokenSelectProps) => { - const [queryConds, setQueryConds] = useState({ - keyword: '', - chainServerId: chainId, - }); - const [tokenSelectorVisible, setTokenSelectorVisible] = useState(false); - const [updateNonce, setUpdateNonce] = useState(0); - const currentAccount = useRabbySelector( - (state) => state.account.currentAccount - ); - const wallet = useWallet(); - - const handleCurrentTokenChange = (token: TokenItem) => { - onChange && onChange(''); - onTokenChange(token); - setTokenSelectorVisible(false); - - // const chainItem = findChainByServerID(token.chain); - setQueryConds((prev) => ({ ...prev, chainServerId: token.chain })); - }; - - const handleTokenSelectorClose = () => { - setTokenSelectorVisible(false); - setQueryConds((prev) => ({ - ...prev, +const TokenSelect = forwardRef< + { openTokenModal: React.Dispatch> }, + TokenSelectProps +>( + ( + { + token, + onChange, + onTokenChange, + chainId, + excludeTokens = defaultExcludeTokens, + type = 'default', + placeholder, + hideChainIcon = true, + value, + loading = false, + tokenRender, + useSwapTokenList = false, + disabledTips = 'Not supported', + drawerHeight, + supportChains, + }, + ref + ) => { + const [queryConds, setQueryConds] = useState({ + keyword: '', chainServerId: chainId, - })); - }; - - const handleSelectToken = () => { - if (allDisplayTokens.length > 0) { - setUpdateNonce(updateNonce + 1); - } - setTokenSelectorVisible(true); - }; - - const isSwapType = isSwapTokenType(type); - - // when no any queryConds - const { tokens: allTokens, isLoading: isLoadingAllTokens } = useTokens( - useSwapTokenList ? undefined : currentAccount?.address, - undefined, - tokenSelectorVisible, - updateNonce, - queryConds.chainServerId - ); - - const { - value: swapTokenList, - loading: swapTokenListLoading, - } = useAsync(async () => { - if (!currentAccount || !useSwapTokenList || !tokenSelectorVisible) - return []; - const list = await wallet.openapi.getSwapTokenList( - currentAccount.address, - queryConds.chainServerId ? queryConds.chainServerId : undefined + }); + const [tokenSelectorVisible, setTokenSelectorVisible] = useState(false); + const [updateNonce, setUpdateNonce] = useState(0); + const currentAccount = useRabbySelector( + (state) => state.account.currentAccount ); - return list; - }, [ - queryConds.chainServerId, - currentAccount, - useSwapTokenList, - tokenSelectorVisible, - ]); - - const allDisplayTokens = useMemo(() => { - if (useSwapTokenList) return swapTokenList || []; - return allTokens.map(abstractTokenToTokenItem); - }, [allTokens, swapTokenList, useSwapTokenList]); - - const { - isLoading: isSearchLoading, - list: searchedTokenByQuery, - } = useSearchToken( - currentAccount?.address, - queryConds.keyword, - queryConds.chainServerId, - isSwapType || type === 'bridgeFrom' ? false : true - ); - - const availableToken = useMemo(() => { - const allTokens = queryConds.chainServerId - ? allDisplayTokens.filter( - (token) => token.chain === queryConds.chainServerId - ) - : allDisplayTokens; - return uniqBy( - queryConds.keyword - ? searchedTokenByQuery.map(abstractTokenToTokenItem) - : allTokens, - (token) => { - return `${token.chain}-${token.id}`; - } - ).filter((e) => !excludeTokens.includes(e.id)); - }, [allDisplayTokens, searchedTokenByQuery, excludeTokens, queryConds]); + const wallet = useWallet(); - const displaySortedTokenList = useSortToken(availableToken); + useImperativeHandle(ref, () => ({ + openTokenModal: setTokenSelectorVisible, + })); - const displayTokenList = useMemo(() => { - if (type === 'swapTo') { - return availableToken; - } - return displaySortedTokenList; - }, [availableToken, displaySortedTokenList, type]); - - const isListLoading = queryConds.keyword - ? isSearchLoading - : useSwapTokenList - ? swapTokenListLoading - : isLoadingAllTokens; - - const handleSearchTokens = React.useCallback(async (ctx) => { - setQueryConds({ - keyword: ctx.keyword, - chainServerId: ctx.chainServerId, - }); - }, []); + const handleCurrentTokenChange = (token: TokenItem) => { + onChange && onChange(''); + onTokenChange(token); + setTokenSelectorVisible(false); + + // const chainItem = findChainByServerID(token.chain); + setQueryConds((prev) => ({ ...prev, chainServerId: token.chain })); + }; + + const handleTokenSelectorClose = () => { + setTokenSelectorVisible(false); + setQueryConds((prev) => ({ + ...prev, + chainServerId: chainId, + })); + }; + + const handleSelectToken = () => { + if (allDisplayTokens.length > 0) { + setUpdateNonce(updateNonce + 1); + } + setTokenSelectorVisible(true); + }; + + const isSwapType = isSwapTokenType(type); + + // when no any queryConds + const { tokens: allTokens, isLoading: isLoadingAllTokens } = useTokens( + useSwapTokenList ? undefined : currentAccount?.address, + undefined, + tokenSelectorVisible, + updateNonce, + queryConds.chainServerId + ); - const [input, setInput] = useState(''); + const { + value: swapTokenList, + loading: swapTokenListLoading, + } = useAsync(async () => { + if (!currentAccount || !useSwapTokenList || !tokenSelectorVisible) + return []; + const list = await wallet.openapi.getSwapTokenList( + currentAccount.address, + queryConds.chainServerId ? queryConds.chainServerId : undefined + ); + return list; + }, [ + queryConds.chainServerId, + currentAccount, + useSwapTokenList, + tokenSelectorVisible, + ]); + + const allDisplayTokens = useMemo(() => { + if (useSwapTokenList) return swapTokenList || []; + return allTokens.map(abstractTokenToTokenItem); + }, [allTokens, swapTokenList, useSwapTokenList]); + + const { + isLoading: isSearchLoading, + list: searchedTokenByQuery, + } = useSearchToken( + currentAccount?.address, + queryConds.keyword, + queryConds.chainServerId, + isSwapType || type === 'bridgeFrom' ? false : true + ); - const handleInput: React.ChangeEventHandler = (e) => { - const v = e.target.value; - if (!/^\d*(\.\d*)?$/.test(v)) { - return; + const availableToken = useMemo(() => { + const allTokens = queryConds.chainServerId + ? allDisplayTokens.filter( + (token) => token.chain === queryConds.chainServerId + ) + : allDisplayTokens; + return uniqBy( + queryConds.keyword + ? searchedTokenByQuery.map(abstractTokenToTokenItem) + : allTokens, + (token) => { + return `${token.chain}-${token.id}`; + } + ).filter((e) => !excludeTokens.includes(e.id)); + }, [allDisplayTokens, searchedTokenByQuery, excludeTokens, queryConds]); + + const displaySortedTokenList = useSortToken(availableToken); + + const displayTokenList = useMemo(() => { + if (type === 'swapTo') { + return availableToken; + } + return displaySortedTokenList; + }, [availableToken, displaySortedTokenList, type]); + + const isListLoading = queryConds.keyword + ? isSearchLoading + : useSwapTokenList + ? swapTokenListLoading + : isLoadingAllTokens; + + const handleSearchTokens = React.useCallback(async (ctx) => { + setQueryConds({ + keyword: ctx.keyword, + chainServerId: ctx.chainServerId, + }); + }, []); + + const [input, setInput] = useState(''); + + const handleInput: React.ChangeEventHandler = (e) => { + const v = e.target.value; + if (!/^\d*(\.\d*)?$/.test(v)) { + return; + } + setInput(v); + onChange && onChange(v); + }; + + useEffect(() => { + setQueryConds((prev) => ({ + ...prev, + chainServerId: chainId, + })); + }, [chainId]); + + if (tokenRender) { + return ( + <> + {typeof tokenRender === 'function' + ? tokenRender?.({ token, openTokenModal: handleSelectToken }) + : tokenRender} + {type === 'bridgeFrom' && !queryConds.chainServerId ? null : ( + + )} + + ); } - setInput(v); - onChange && onChange(v); - }; - useEffect(() => { - setQueryConds((prev) => ({ - ...prev, - chainServerId: chainId, - })); - }, [chainId]); - - if (tokenRender) { return ( <> - {typeof tokenRender === 'function' - ? tokenRender?.({ token, openTokenModal: handleSelectToken }) - : tokenRender} + +
+ {token ? ( + + + + {getTokenSymbol(token)} + + + + ) : ( + + Select Token + + + )} +
+ {loading ? ( + + ) : ( + + )} +
{type === 'bridgeFrom' && !queryConds.chainServerId ? null : ( )} ); } - - return ( - <> - -
- {token ? ( - - - {getTokenSymbol(token)} - - - ) : ( - - Select Token - - - )} -
- {loading ? ( - - ) : ( - - )} -
- {type === 'bridgeFrom' && !queryConds.chainServerId ? null : ( - - )} - - ); -}; +); const TokenWrapper = styled.div` /* width: 92px; */ diff --git a/src/ui/component/TokenSelector/index.tsx b/src/ui/component/TokenSelector/index.tsx index 7b67ac7f173..775fa4218e2 100644 --- a/src/ui/component/TokenSelector/index.tsx +++ b/src/ui/component/TokenSelector/index.tsx @@ -388,7 +388,7 @@ const TokenSelector = ({ > diff --git a/src/ui/component/TokenSelector/style.less b/src/ui/component/TokenSelector/style.less index c86dd1a3b5a..bdeedd9df01 100644 --- a/src/ui/component/TokenSelector/style.less +++ b/src/ui/component/TokenSelector/style.less @@ -190,14 +190,16 @@ justify-content: center; align-items: center; - border-radius: 4px; + border-radius: 6px; + font-size: 13px; + font-weight: 500; color: var(--r-neutral-body, #d3d8e0); background: var(--r-neutral-card-2, rgba(255, 255, 255, 0.06)); } img.filter-item__chain-logo { - width: 14px; - height: 14px; + width: 16px; + height: 16px; border-radius: 1000px; } diff --git a/src/ui/views/Swap/Component/Main.tsx b/src/ui/views/Swap/Component/Main.tsx index 85d32bd2ef8..92d439fb193 100644 --- a/src/ui/views/Swap/Component/Main.tsx +++ b/src/ui/views/Swap/Component/Main.tsx @@ -435,7 +435,7 @@ export const Main = () => {
-
+
{ valueLoading, currentQuote, } = props; + + const openTokenModalRef = useRef<{ + openTokenModal: React.Dispatch>; + }>(null); + const { t } = useTranslation(); const inputRef = useRef(); const isFrom = type === 'from'; + const handleTokenModalOpen = useCallback(() => { + if (!isFrom) { + openTokenModalRef?.current?.openTokenModal?.((e) => { + console.log('click', e); + return true; + }); + } + }, [isFrom]); + const [balance, usdValue] = useMemo(() => { if (token) { const amount = tokenAmountBn(token); @@ -171,7 +185,13 @@ export const SwapTokenItem = (props: SwapTokenItemProps) => { return (
-
+
{isFrom ? t('page.swap.from') : t('page.swap.to')} @@ -194,19 +214,32 @@ export const SwapTokenItem = (props: SwapTokenItemProps) => { )}
-
- +
+
{ + e.stopPropagation(); + }} + > + +
{valueLoading ? ( { ref={inputRef as any} readOnly={!isFrom} className={clsx( - !isFrom && 'cursor-default', + !isFrom && 'cursor-pointer', isFrom && inSufficient && 'text-r-red-default' )} /> diff --git a/src/ui/views/Swap/Component/TokenRender.tsx b/src/ui/views/Swap/Component/TokenRender.tsx index 0633e2449dd..51da129e123 100644 --- a/src/ui/views/Swap/Component/TokenRender.tsx +++ b/src/ui/views/Swap/Component/TokenRender.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components'; import { TokenItem } from '@rabby-wallet/rabby-api/dist/types'; import { getTokenSymbol } from '@/ui/utils/token'; import { useTranslation } from 'react-i18next'; -import { ReactComponent as RcImgArrowDown } from '@/ui/assets/swap/arrow-down.svg'; +import { ReactComponent as RcImgArrowDownCC } from '@/ui/assets/swap/arrow-down-cc.svg'; const TokenRenderWrapper = styled.div` width: auto; @@ -20,7 +20,7 @@ const TokenRenderWrapper = styled.div` border: 1px solid transparent; cursor: pointer; &:hover { - background: rgba(134, 151, 255, 0.2); + background: var(--r-blue-light1, #eef1ff); } .token { display: flex; @@ -79,17 +79,17 @@ export const TokenRender = ({ {getTokenSymbol(token)} -
) : (
{t('page.swap.select-token')} -
)} diff --git a/src/ui/views/Swap/hooks/quote.tsx b/src/ui/views/Swap/hooks/quote.tsx index 1fa2cdc89f7..5fc3ec542e1 100644 --- a/src/ui/views/Swap/hooks/quote.tsx +++ b/src/ui/views/Swap/hooks/quote.tsx @@ -341,35 +341,30 @@ export const useQuoteMethods = () => { }); const getData = () => - Promise.race([ - getQuote( - isSwapWrapToken(payToken.id, receiveToken.id, chain) - ? DEX_ENUM.WRAPTOKEN - : dexId, - { - fromToken: payToken.id, - toToken: receiveToken.id, - feeAddress: SWAP_FEE_ADDRESS, - fromTokenDecimals: payToken.decimals, - amount: new BigNumber(payAmount) - .times(10 ** payToken.decimals) - .toFixed(0, 1), - userAddress, - slippage: Number(slippage), - feeRate: - feeAfterDiscount === '0' && isOpenOcean - ? undefined - : Number(feeAfterDiscount) || 0, - chain, - gasPrice, - fee: true, - }, - walletOpenapi - ), - new Promise((_, reject) => - setTimeout(() => reject(new Error('timeout')), 5000) - ), - ]) as Promise; + getQuote( + isSwapWrapToken(payToken.id, receiveToken.id, chain) + ? DEX_ENUM.WRAPTOKEN + : dexId, + { + fromToken: payToken.id, + toToken: receiveToken.id, + feeAddress: SWAP_FEE_ADDRESS, + fromTokenDecimals: payToken.decimals, + amount: new BigNumber(payAmount) + .times(10 ** payToken.decimals) + .toFixed(0, 1), + userAddress, + slippage: Number(slippage), + feeRate: + feeAfterDiscount === '0' && isOpenOcean + ? undefined + : Number(feeAfterDiscount) || 0, + chain, + gasPrice, + fee: true, + }, + walletOpenapi + ); const data = await getData(); diff --git a/src/ui/views/Swap/hooks/token.tsx b/src/ui/views/Swap/hooks/token.tsx index ac89adf9b21..3c73720ba81 100644 --- a/src/ui/views/Swap/hooks/token.tsx +++ b/src/ui/views/Swap/hooks/token.tsx @@ -500,6 +500,7 @@ export const useTokenPair = (userAddress: string) => { inputAmount, feeRate, inSufficient, + slippageObj?.slippage, ]); useDebounce( diff --git a/yarn.lock b/yarn.lock index ec989bfc2d9..d63835ffd95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4873,10 +4873,10 @@ resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-action/-/rabby-action-0.1.8.tgz#05b258b628a224d51dc471e93117d4106024a916" integrity sha512-K0euVX55tW2mbnudm3bHPAbbLYlnDQD5PgAvM1mwPPFHhF/Ve/U5MYdg1XERJPxkoXKqiVti46idFFB+s8sv7A== -"@rabby-wallet/rabby-api@0.9.4": - version "0.9.4" - resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-api/-/rabby-api-0.9.4.tgz#d0de2903ffd9496c6248ce20b7213f13763ea431" - integrity sha512-NpWV9y46/+6zHG4jYg7gsz0gAbKIGiSw+AgWPmGc4EhBFPYk1a9ZkjzKimO6hoyE5ItUzY0de0hJ8MQdAWJSqg== +"@rabby-wallet/rabby-api@0.9.7": + version "0.9.7" + resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-api/-/rabby-api-0.9.7.tgz#af85e815adb36b4bba7e58ab87223c90709672cc" + integrity sha512-IvjGmIYMMziNPFl5qxCr5JQKSn19jJP782xj7D9XfjjFUIJrloywVXxbV/ZmaltYPFQ+E6/RzaAGuDeWO0pLGQ== dependencies: "@rabby-wallet/rabby-sign" "0.4.0" axios "^0.27.2"