From b3a7f2664a44a32cf98851902bd0754cb0f156a1 Mon Sep 17 00:00:00 2001 From: kim Date: Fri, 3 Jan 2025 16:01:49 +0800 Subject: [PATCH 1/7] Feat gas-account withdraw (#2704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: gasacoount withdraw balance custom * feat: rabby-api update * fix: update rabby-api * fix: update refresh history and code review * fix: update version * fix: update withdraw pending refresh logic * fix: update version * fix: update yarn lock * fix: account list refresh in gasAccount * fix: gas fee limit tips * fix: withdraw list default selector * fix: res error to show * fix: restore login popup --- _raw/locales/en/messages.json | 17 +- package.json | 2 +- .../GasAccount/components/DepositPopup.tsx | 170 ++++- .../views/GasAccount/components/History.tsx | 45 +- .../GasAccount/components/LoginPopup.tsx | 25 +- .../GasAccount/components/WithdrawPopup.tsx | 662 +++++++++++++++--- src/ui/views/GasAccount/hooks/index.ts | 18 +- src/ui/views/GasAccount/index.tsx | 58 +- yarn.lock | 8 +- 9 files changed, 823 insertions(+), 182 deletions(-) diff --git a/_raw/locales/en/messages.json b/_raw/locales/en/messages.json index 03de2481f4e..baac60d38e3 100644 --- a/_raw/locales/en/messages.json +++ b/_raw/locales/en/messages.json @@ -2276,6 +2276,7 @@ "title": "GasAccount", "deposit": "Deposit", "withdraw": "Withdraw", + "noBalance": "No balance", "gasExceed": "GasAccount balance cannot exceed $1000", "risk": "Your current address has been detected as risky, so this feature is unavailable.", "logout": "Log out current GasAccount", @@ -2294,21 +2295,33 @@ }, "logoutConfirmModal": { "title": "Log out current GasAccount", - "desc": "Once logged out, you will no longer be able to use GasAccount to pay for gas fees.", + "desc": "Logging out disables GasAccount. You can restore your GasAccount by logging in with this address.", "logout": "Log out" }, "depositPopup": { "title": "Deposit", "desc": "Make a deposit to Rabby's DeBank L2 Account with no extra fees—withdraw anytime.", + "invalidAmount": "Must be under 500", "amount": "Amount", "token": "Token", "selectToken": "Select Token to Deposit" }, "withdrawPopup": { + "noEnoughValuetBalance": "Vault Balance insufficient. Switch chain or try again later.", + "noEnoughGas":"Amount too low to cover gas fees", + "riskMessageFromChain": "Due to risk control, the withdrawal limit depends on the total amount deposited from this chain.", + "riskMessageFromAddress": "Due to risk control, the withdrawal limit depends on the total amount this address has deposited", + "selectDestinationChain":"Select destination chain", + "selectRecipientAddress": "Select recipient address", + "withdrawalLimit": "Withdrawal limit", "title": "Withdraw", "desc": "You can withdraw your GasAccount balance to your DeBank L2 Wallet. Log in to your DeBank L2 Wallet to transfer the funds to a supported blockchain as needed.", "amount": "Amount", - "to": "To" + "to": "To", + "selectChain": "Select Chain", + "recipientAddress": "Recipient address", + "destinationChain": "Destination chain", + "deductGasFees": "Received amount will deduct gas fees" }, "withdrawConfirmModal": { "title": "Transferred to your DeBank L2 Wallet", diff --git a/package.json b/package.json index da9e5b88b61..c8d78330f7e 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.7", + "@rabby-wallet/rabby-api": "0.9.9", "@rabby-wallet/rabby-security-engine": "2.0.7", "@rabby-wallet/rabby-swap": "0.0.45", "@rabby-wallet/widgets": "1.0.9", diff --git a/src/ui/views/GasAccount/components/DepositPopup.tsx b/src/ui/views/GasAccount/components/DepositPopup.tsx index addd11ffa35..494f3b1fc1c 100644 --- a/src/ui/views/GasAccount/components/DepositPopup.tsx +++ b/src/ui/views/GasAccount/components/DepositPopup.tsx @@ -1,11 +1,11 @@ -import React, { CSSProperties, useState } from 'react'; +import React, { CSSProperties, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Empty, Item, Popup, TokenWithChain } from '@/ui/component'; import { Button, Space, Tooltip } from 'antd'; import { PopupProps } from '@/ui/component/Popup'; import { SvgIconLoading } from 'ui/assets'; import { FixedSizeList } from 'react-window'; - +import styled from 'styled-components'; import { noop } from 'lodash'; import clsx from 'clsx'; import { useAsync } from 'react-use'; @@ -17,8 +17,38 @@ import { getTokenSymbol } from '@/ui/utils/token'; import { findChainByServerID } from '@/utils/chain'; import { L2_DEPOSIT_ADDRESS_MAP } from '@/constant/gas-account'; import { GasAccountCloseIcon } from './PopupCloseIcon'; +import { Input } from 'antd'; + +const amountList = [10, 100]; + +const Wrapper = styled.div` + .input { + font-weight: 500; + font-size: 18px; + border: none; + border-radius: 4px; + background: transparent; + + .ant-input { + width: min-content; + border-radius: 0; + text-align: center; + font-weight: 500; + font-size: 18px; + } + } -const amountList = [20, 100, 500]; + .warning { + color: var(--r-red-default, #e34935); + font-size: 13px; + height: 16px; + font-style: normal; + font-weight: 400; + line-height: normal; + position: relative; + margin-top: 8px; + } +`; const TokenSelector = ({ visible, @@ -163,17 +193,24 @@ const TokenSelector = ({ ); }; +const CUSTOM_AMOUNT = 0; + const GasAccountDepositContent = ({ onClose }: { onClose: () => void }) => { const { t } = useTranslation(); - const [selectedAmount, setAmount] = useState(100); + const [selectedAmount, setAmount] = useState(amountList[0]); const [tokenListVisible, setTokenListVisible] = useState(false); const [token, setToken] = useState(undefined); + const [formattedValue, setFormattedValue] = useState(''); + const [rawValue, setRawValue] = useState(0); const wallet = useWallet(); - const openTokenList = () => { - setTokenListVisible(true); - }; + const depositAmount = useMemo(() => { + if (selectedAmount === CUSTOM_AMOUNT && rawValue) { + return rawValue; + } + return selectedAmount; + }, [selectedAmount, rawValue]); const topUpGasAccount = () => { if (token) { @@ -182,8 +219,8 @@ const GasAccountDepositContent = ({ onClose }: { onClose: () => void }) => { to: L2_DEPOSIT_ADDRESS_MAP[chainEnum.enum], chainServerId: chainEnum.serverId, tokenId: token.id, - amount: selectedAmount, - rawAmount: new BigNumber(selectedAmount) + amount: depositAmount, + rawAmount: new BigNumber(depositAmount) .times(10 ** token.decimals) .toFixed(0), }); @@ -191,6 +228,57 @@ const GasAccountDepositContent = ({ onClose }: { onClose: () => void }) => { } }; + const onInputChange = (e: React.ChangeEvent) => { + let inputValue = e.target.value.replace(/[^0-9]/g, ''); + + // only integer and no string + if (inputValue === '' || /^\d*$/.test(inputValue)) { + // no only 0 + if (inputValue === '0') { + inputValue = ''; + } else { + inputValue = inputValue.replace(/^0+/, '') || ''; // remove 0 + } + + // add $ prefix + if (inputValue && !inputValue.startsWith('$')) { + inputValue = `$${inputValue}`; + } + + setFormattedValue(inputValue); + const numericValue = inputValue.replace(/[^0-9]/g, ''); + setRawValue(numericValue ? parseInt(numericValue, 10) : 0); + } + }; + + const selectCustomAmount = () => { + setAmount(CUSTOM_AMOUNT); + }; + + const errorTips = useMemo(() => { + if (selectedAmount === CUSTOM_AMOUNT && rawValue && rawValue > 500) { + return t('page.gasAccount.depositPopup.invalidAmount'); + } + }, [rawValue, selectedAmount]); + + const amountPass = useMemo(() => { + if (selectedAmount === CUSTOM_AMOUNT) { + return rawValue >= 1 && rawValue <= 500; + } + return true; + }, [rawValue, selectedAmount]); + + const openTokenList = () => { + if (!amountPass) return; + setTokenListVisible(true); + }; + + useEffect(() => { + if (token && depositAmount && token.amount < depositAmount) { + setToken(undefined); + } + }, [depositAmount]); + return (
@@ -204,27 +292,50 @@ const GasAccountDepositContent = ({ onClose }: { onClose: () => void }) => {
{t('page.gasAccount.depositPopup.amount')}
-
- {amountList.map((amount) => ( -
setAmount(amount)} + +
+ {amountList.map((amount) => ( +
setAmount(amount)} + className={clsx( + 'flex items-center justify-center cursor-pointer', + 'rounded-[6px] w-[114px] h-[52px]', + 'text-18 font-medium', + 'bg-r-neutral-card2', + 'border border-solid border-transparent', + 'hover:bg-r-blue-light-1 hover:border-rabby-blue-default', + selectedAmount === amount + ? 'bg-r-blue-light-1 border-rabby-blue-default text-r-blue-default' + : 'text-r-neutral-title1' + )} + > + ${amount} +
+ ))} + - ${amount} -
- ))} -
+ bordered={false} + value={formattedValue} + onChange={onInputChange} + onFocus={selectCustomAmount} + placeholder="$1-500" + // prefix={
$
} + /> +
+ {
{errorTips || ''}
} +
{t('page.gasAccount.depositPopup.token')} @@ -232,7 +343,10 @@ const GasAccountDepositContent = ({ onClose }: { onClose: () => void }) => { void }) => { size="large" type="primary" className="h-[48px] text-r-neutral-title2 text-15 font-medium" - disabled={!token} + disabled={!token || !amountPass} > {t('global.Confirm')} @@ -273,7 +387,7 @@ const GasAccountDepositContent = ({ onClose }: { onClose: () => void }) => { setTokenListVisible(false)} - cost={selectedAmount} + cost={depositAmount} onChange={setToken} />
@@ -284,7 +398,7 @@ export const GasAccountDepositPopup = (props: PopupProps) => { return ( { const { t } = useTranslation(); const gotoTxDetail = () => { - if (chainServerId && txId) { + if (chainServerId && txId && !isWithdraw) { const chain = findChainByServerID(chainServerId); if (chain && chain.scanLink) { const scanLink = chain.scanLink.replace('_s_', ''); @@ -65,8 +67,14 @@ const HistoryItem = ({ viewBox="0 0 16 16" className="w-16 h-16 animate-spin" /> -
{t('page.gasAccount.deposit')}
- +
+ {isWithdraw + ? t('page.gasAccount.withdraw') + : t('page.gasAccount.deposit')} +
+ {!isWithdraw && ( + + )}
) : (
{sinceTime(time)}
@@ -99,7 +107,12 @@ export const GasAccountHistory = () => { const { loading, txList, loadingMore, ref } = useGasAccountHistory(); - if (!loading && !txList?.rechargeList.length && !txList?.list.length) { + if ( + !loading && + !txList?.rechargeList.length && + !txList?.withdrawList.length && + !txList?.list.length + ) { return (
{ txId={item?.tx_id} /> ))} + {!loading && + txList?.withdrawList?.map((item, index) => ( + + ))} {!loading && txList?.list.map((item, index) => ( { time={item.create_at} value={item.usd_value} sign={item.history_type === 'recharge' ? '+' : '-'} - borderT={!txList?.rechargeList.length ? index !== 0 : true} + borderT={ + !txList?.rechargeList.length && !txList?.withdrawList.length + ? index !== 0 + : true + } /> ))} @@ -144,7 +175,9 @@ export const GasAccountHistory = () => { void }) => { - const [toConfirm, setToConfirm] = useState(false); +const GasAccountLoginContent = ({ + onClose, + showConfirm, + setToConfirm, +}: { + onClose: () => void; + showConfirm: boolean; + setToConfirm: (value: boolean) => void; +}) => { const { t } = useTranslation(); const { login } = useGasAccountMethods(); @@ -95,22 +102,17 @@ const GasAccountLoginContent = ({ onClose }: { onClose: () => void }) => { setToConfirm(true); }; - const currentAccount = useCurrentAccount(); - const confirmAddress = () => { login(); }; - if (toConfirm && currentAccount) { + if (showConfirm) { return (
{t('page.gasAccount.loginConfirmModal.title')}
-
- {t('page.gasAccount.loginConfirmModal.desc')} -
void }) => { }; export const GasAccountLoginPopup = (props: PopupProps) => { + const [toConfirm, setToConfirm] = useState(false); + const currentAccount = useCurrentAccount(); + return ( { > ); diff --git a/src/ui/views/GasAccount/components/WithdrawPopup.tsx b/src/ui/views/GasAccount/components/WithdrawPopup.tsx index 703acaabc69..14d366b3f58 100644 --- a/src/ui/views/GasAccount/components/WithdrawPopup.tsx +++ b/src/ui/views/GasAccount/components/WithdrawPopup.tsx @@ -1,108 +1,608 @@ -import React, { useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Popup } from '@/ui/component'; -import { Button, message } from 'antd'; +import { Popup, Item, TokenWithChain, AddressViewer } from '@/ui/component'; +import { Button, message, Skeleton, Tooltip } from 'antd'; import { PopupProps } from '@/ui/component/Popup'; -import { noop } from 'lodash'; +import { noop, set } from 'lodash'; +import { FixedSizeList } from 'react-window'; import clsx from 'clsx'; import { useGasAccountRefresh, useGasAccountSign } from '../hooks'; -import { GasACcountCurrentAddress } from './LoginPopup'; +import { ReactComponent as RcIconInfo } from 'ui/assets/info-cc.svg'; +import { TooltipWithMagnetArrow } from '@/ui/component/Tooltip/TooltipWithMagnetArrow'; -import { ReactComponent as RcIconOpenExternalCC } from '@/ui/assets/open-external-cc.svg'; - -import { ReactComponent as RcIconConfirm } from '@/ui/assets/gas-account/confirm.svg'; -import { formatUsdValue, openInTab, useWallet } from '@/ui/utils'; -import { useRabbySelector } from '@/ui/store'; +import { formatUsdValue, openInTab, useAlias, useWallet } from '@/ui/utils'; +import { useRabbyDispatch, useRabbySelector } from '@/ui/store'; import { GasAccountCloseIcon } from './PopupCloseIcon'; +import { findChainByEnum, findChainByServerID } from '@/utils/chain'; +import { useCurrentAccount } from '@/ui/hooks/backgroundState/useAccount'; +import { useAsync } from 'react-use'; +import { CSSProperties } from 'styled-components'; +import styled from 'styled-components'; +import { IDisplayedAccountWithBalance } from '@/ui/models/accountToDisplay'; +import { useBrandIcon } from '@/ui/hooks/useBrandIcon'; +import { CopyChecked } from '@/ui/component/CopyChecked'; +import { + RechargeChainItem, + WithdrawListAddressItem, +} from '@rabby-wallet/rabby-api/dist/types'; +import BigNumber from 'bignumber.js'; + +// export interface RechargeChainItem { +// chain_id: string; +// withdraw_limit: number; +// withdraw_fee: number; +// } + +// export interface WithdrawListAddressItem { +// recharge_addr: string; +// total_withdraw_limit: number; +// recharge_chain_list: RechargeChainItem[]; +// } + +enum SelectorStatus { + Hidden, + Address, + Chain, +} + +const WrapperDiv = styled.div` + .ant-btn-primary { + height: 48px; + } +`; + +const AddressRightAreaInItem = ({ + account, +}: { + account?: { + address: string; + type: string; + brandName: string; + }; +}) => { + const [alias] = useAlias(account?.address || ''); + + const addressTypeIcon = useBrandIcon({ + address: account?.address || '', + brandName: account?.brandName || '', + type: account?.type || '', + }); + + return ( +
+ +
+ + {alias} + +
+ + +
+
+
+ ); +}; + +const Selector = ({ + accountsList, + status, + onClose, + selectAddressChainList, + setChain, + setSelectAddressChainList, + withdrawList, +}: { + accountsList: IDisplayedAccountWithBalance[]; + status: SelectorStatus; + onClose: () => void; + selectAddressChainList: WithdrawListAddressItem; + setChain: (chain: RechargeChainItem) => void; + setSelectAddressChainList: (item: WithdrawListAddressItem) => void; + withdrawList: WithdrawListAddressItem[]; +}) => { + const { t } = useTranslation(); + + const isSelectChain = useMemo(() => status === SelectorStatus.Chain, [ + status, + ]); + + const sortedList = useMemo( + () => + isSelectChain + ? selectAddressChainList?.recharge_chain_list?.sort( + (a, b) => b.withdraw_limit - a.withdraw_limit + ) || [] + : withdrawList, + [selectAddressChainList, withdrawList, isSelectChain] + ); + + const ChainRow = React.useCallback( + ({ + index, + data, + style, + }: { + index: number; + data: RechargeChainItem[]; + style: CSSProperties; + }) => { + const item = data[index]; + const chainEnum = findChainByServerID(item.chain_id)!; + const disabled = !item.withdraw_limit; + + return ( +
+ + + + {chainEnum.name} + +
+ } + right={ + + {`$${item.withdraw_limit}`} + + } + onClick={() => { + if (!disabled) { + setChain(item); + onClose(); + } + }} + /> +
+ //
+ ); + }, + [isSelectChain] + ); + + const AddressRow = React.useCallback( + ({ + index, + data, + style, + }: { + index: number; + data: WithdrawListAddressItem[]; + style: CSSProperties; + }) => { + const item = data[index]; + const disabled = !item.total_withdraw_limit; + const account = accountsList.find( + (i) => i.address === item.recharge_addr + ); + + return ( +
+ } + right={ + + {`$${item.total_withdraw_limit}`} + + } + onClick={() => { + if (!disabled) { + setSelectAddressChainList(item); + onClose(); + } + }} + /> +
+ ); + }, + [isSelectChain, accountsList] + ); + + return ( + } + closable + > +
+
+
+
+ {isSelectChain + ? t('page.gasAccount.withdrawPopup.selectDestinationChain') + : t('page.gasAccount.withdrawPopup.selectRecipientAddress')} +
+
+
+
+
+ {isSelectChain + ? t('page.gasAccount.withdrawPopup.destinationChain') + : t('page.gasAccount.withdrawPopup.recipientAddress')} +
+
+ {t('page.gasAccount.withdrawPopup.withdrawalLimit')} + + + +
+
+
+
+
+ {isSelectChain ? ( + + width={'100%'} + height={328} + itemCount={sortedList?.length || 0} + itemData={sortedList as RechargeChainItem[]} + itemSize={68} + > + {ChainRow} + + ) : ( + + width={'100%'} + height={328} + itemCount={sortedList?.length || 0} + itemData={sortedList as WithdrawListAddressItem[]} + itemSize={68} + > + {AddressRow} + + )} +
+
+
+ ); +}; const WithdrawContent = ({ balance, onClose, - onAfterConfirm, + handleRefreshHistory, }: { balance: number; + handleRefreshHistory: () => void; onClose: () => void; - onAfterConfirm?: () => void; }) => { const { t } = useTranslation(); - + const [selectorStatus, setSelectorStatus] = useState( + SelectorStatus.Hidden + ); + const [chain, setChain] = useState(); + const [ + selectAddressChainList, + setSelectAddressChainList, + ] = useState(); const { sig, accountId } = useGasAccountSign(); const wallet = useWallet(); + const account = useCurrentAccount(); + const [btnLoading, setBtnLoading] = useState(false); const [loading, setLoading] = useState(false); - + const [withdrawList, setWithdrawList] = useState(); const { refresh } = useGasAccountRefresh(); - const gasAccount = useRabbySelector((s) => s.gasAccount.account); + const fetchData = async () => { + setLoading(true); + try { + const res = await wallet.openapi.getWithdrawList({ + sig: sig!, + id: accountId!, + }); + if (res) { + const data = res + ?.filter((item) => { + const { recharge_addr } = item; + const idx = accountsList.findIndex( + (i) => i.address === recharge_addr + ); + return idx > -1; + }) + .sort((a, b) => b.total_withdraw_limit - a.total_withdraw_limit); + + setWithdrawList(data); + setSelectAddressChainList(data[0]); + } + } catch (e) { + console.error(e?.message || String(e)); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (sig && accountId) { + fetchData(); + } + }, [sig, accountId]); + + const { accountsList } = useRabbySelector((s) => ({ + ...s.accountToDisplay, + })); const withdraw = async () => { - if (balance <= 0) { - onClose(); - onAfterConfirm?.(); + if (balance <= 0 || !selectAddressChainList || !chain) { return; } + try { - setLoading(true); - await wallet.openapi.withdrawGasAccount({ + setBtnLoading(true); + + const amount = Math.min(balance, chain.withdraw_limit); + const res: any = await wallet.openapi.withdrawGasAccount({ sig: sig!, account_id: accountId!, - amount: balance, + amount, + user_addr: selectAddressChainList.recharge_addr, + fee: chain.withdraw_fee, + chain_id: chain.chain_id, }); + if (!res.success) { + throw new Error(res?.msg || 'withdraw failed'); + } refresh(); + handleRefreshHistory(); onClose(); - onAfterConfirm?.(); } catch (error) { message.error(error?.message || String(error)); + console.error(error?.message || String(error)); } finally { - setLoading(false); + setBtnLoading(false); + } + }; + + const chainInfo = React.useMemo(() => { + return chain ? findChainByServerID(chain!.chain_id) : undefined; + }, [chain]); + + const openChainList = () => { + if (loading) { + return; } + setSelectorStatus(SelectorStatus.Chain); }; + const openAddreesList = () => { + if (loading) { + return; + } + setSelectorStatus(SelectorStatus.Address); + }; + + const BalanceSuffix = useMemo(() => { + if (!chain) { + return ''; + } else { + const withdrawTotal = Math.min(balance, chain.withdraw_limit); + const usdValue = formatUsdValue(withdrawTotal, BigNumber.ROUND_DOWN); + return ` ${usdValue}`; + } + }, [balance, chain]); + + const withdrawBtnDisabledTips = useMemo(() => { + if (!chain) { + return ''; + } + + const withdrawTotal = Math.min(balance, chain.withdraw_limit); + if (withdrawTotal < chain.withdraw_fee) { + return t('page.gasAccount.withdrawPopup.noEnoughGas'); + } + + if (withdrawTotal > chain.l1_balance) { + return t('page.gasAccount.withdrawPopup.noEnoughValuetBalance'); + } + + return ''; + }, [chain, balance]); + + const selectedAccount = useMemo(() => { + return accountsList.find( + (i) => i.address === selectAddressChainList?.recharge_addr + ); + }, [selectAddressChainList, accountsList]); + return (
{t('page.gasAccount.withdrawPopup.title')}
-
- {t('page.gasAccount.withdrawPopup.desc')} -
- {t('page.gasAccount.withdrawPopup.amount')} -
-
- {formatUsdValue(balance)} + {t('page.gasAccount.withdrawPopup.recipientAddress')}
+ + +
+ + +
+
+ ) : ( + + ) + } + hoverBorder={!loading} + right={loading ? () => null : undefined} + onClick={openAddreesList} + />
- {t('page.gasAccount.withdrawPopup.to')} + {t('page.gasAccount.withdrawPopup.destinationChain')}
- + + + + {chainInfo?.name} + +
+ ) : ( + + {t('page.gasAccount.withdrawPopup.selectChain')} + + ) + } + onClick={openChainList} + />
-
-
+ )} + - {t('global.confirm')} - -
+ + + + {withdrawList && selectAddressChainList && ( + setSelectorStatus(SelectorStatus.Hidden)} + selectAddressChainList={selectAddressChainList} + setChain={setChain} + setSelectAddressChainList={setSelectAddressChainList} + withdrawList={withdrawList} + /> + )} ); }; -export const WithdrawPopup = (props: PopupProps & { balance: number }) => { - const [visible, setVisible] = useState(false); +export const WithdrawPopup = ( + props: PopupProps & { balance: number; handleRefreshHistory: () => void } +) => { return ( <> { }} closable destroyOnClose + push={false} closeIcon={} {...props} > setVisible(true)} - /> - - setVisible(false)} - /> - - ); -}; - -const WithdrawConfirmContent = ({ onClose }: { onClose: () => void }) => { - const { t } = useTranslation(); - - const gasAccount = useRabbySelector((s) => s.gasAccount.account); - - const gotoDeBankL2 = async () => { - openInTab('https://debank.com/account'); - }; - - return ( -
- -
- {t('page.gasAccount.withdrawConfirmModal.title')} -
- -
- -
- -
-
- {t('page.gasAccount.withdrawConfirmModal.button')} - -
-
-
- ); -}; - -export const WithdrawConfirmPopup = (props: PopupProps) => { - return ( - <> - - diff --git a/src/ui/views/GasAccount/hooks/index.ts b/src/ui/views/GasAccount/hooks/index.ts index b93d8e9aaf5..e77f006e90b 100644 --- a/src/ui/views/GasAccount/hooks/index.ts +++ b/src/ui/views/GasAccount/hooks/index.ts @@ -125,6 +125,7 @@ export const useGasAccountHistory = () => { mutate, } = useInfiniteScroll<{ rechargeList: History['recharge_list']; + withdrawList: History['withdraw_list']; list: History['history_list']; totalCount: number; }>( @@ -138,9 +139,11 @@ export const useGasAccountHistory = () => { const rechargeList = data.recharge_list; const historyList = data.history_list; + const withdrawList = data.withdraw_list; return { rechargeList: rechargeList || [], + withdrawList: withdrawList || [], list: historyList, totalCount: data.pagination.total, }; @@ -152,7 +155,9 @@ export const useGasAccountHistory = () => { if (data) { return ( data.totalCount <= - (data.list.length || 0) + (data?.rechargeList?.length || 0) + (data.list.length || 0) + + (data?.rechargeList?.length || 0) + + (data?.withdrawList?.length || 0) ); } return true; @@ -179,10 +184,14 @@ export const useGasAccountHistory = () => { return; } - if (value?.recharge_list?.length !== d.rechargeList.length) { + if ( + value?.recharge_list?.length !== d.rechargeList.length || + value?.withdraw_list?.length !== d.withdrawList.length + ) { refreshGasAccountBalance(); } return { + withdrawList: value?.withdraw_list, rechargeList: value?.recharge_list, totalCount: value.pagination.total, list: uniqBy( @@ -206,7 +215,10 @@ export const useGasAccountHistory = () => { useEffect(() => { let timer: NodeJS.Timeout; - if (!loading && !loadingMore && !!txList?.rechargeList?.length) { + const hasSomePending = Boolean( + txList?.rechargeList?.length || txList?.withdrawList?.length + ); + if (!loading && !loadingMore && hasSomePending) { timer = setTimeout(refreshListTx, 2000); } return () => { diff --git a/src/ui/views/GasAccount/index.tsx b/src/ui/views/GasAccount/index.tsx index f12a80393df..05189ae9c26 100644 --- a/src/ui/views/GasAccount/index.tsx +++ b/src/ui/views/GasAccount/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { PageHeader } from '@/ui/component'; import { ReactComponent as RcIconMore } from '@/ui/assets/gas-account/more.svg'; @@ -20,8 +20,9 @@ import { GasAccountWrapperBg } from './components/WrapperBg'; import { GasAccountBlueLogo } from './components/GasAccountBlueLogo'; import BigNumber from 'bignumber.js'; import { useCurrentAccount } from '@/ui/hooks/backgroundState/useAccount'; -import { useRabbySelector } from '@/ui/store'; +import { useRabbyDispatch, useRabbySelector } from '@/ui/store'; import { SwitchLoginAddrBeforeDepositModal } from './components/SwitchLoginAddrModal'; +import clsx from 'clsx'; const DEPOSIT_LIMIT = 1000; @@ -33,6 +34,8 @@ const GasAccountInner = () => { const [depositVisible, setDepositVisible] = useState(false); + const [refreshHistoryKey, setRefreshHistoryKey] = useState(0); + const [withdrawVisible, setWithdrawVisible] = useState(false); const history = useHistory(); @@ -40,6 +43,10 @@ const GasAccountInner = () => { history.push('/dashboard'); }; + const handleRefreshHistory = useCallback(() => { + setRefreshHistoryKey((prevKey) => prevKey + 1); + }, [setRefreshHistoryKey]); + const { value, loading } = useGasAccountInfo(); const { isLogin } = useGasAccountLogin({ value, loading }); @@ -55,14 +62,22 @@ const GasAccountInner = () => { const isRisk = useAml(); + const dispatch = useRabbyDispatch(); + + useEffect(() => { + dispatch.addressManagement.getHilightedAddressesAsync().then(() => { + dispatch.accountToDisplay.getAllAccountsToDisplay(); + }); + }, []); + const openDepositPopup = () => { - if ( - gasAccount?.address && - currentAccount?.address !== gasAccount?.address - ) { - setSwitchAddrVisible(true); - return; - } + // if ( + // gasAccount?.address && + // currentAccount?.address !== gasAccount?.address + // ) { + // setSwitchAddrVisible(true); + // return; + // } setDepositVisible(true); }; @@ -136,12 +151,24 @@ const GasAccountInner = () => {
- setWithdrawVisible(true)} + - {t('page.gasAccount.withdraw')} - + { + if (!balance) { + return; + } + setWithdrawVisible(true); + }} + > + {t('page.gasAccount.withdraw')} + + = DEPOSIT_LIMIT ? undefined : false} @@ -165,7 +192,7 @@ const GasAccountInner = () => {
- + { setWithdrawVisible(false)} + handleRefreshHistory={handleRefreshHistory} balance={balance} /> diff --git a/yarn.lock b/yarn.lock index dff1ebd1fed..7d090d3556f 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.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== +"@rabby-wallet/rabby-api@0.9.9": + version "0.9.9" + resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-api/-/rabby-api-0.9.9.tgz#b71c68aa0215d4c8ad00d0a6980f387c58cbe61f" + integrity sha512-9u1z5CbtrIRhTEssXQS9NTUQYsI2y2GxC+tejt38OLzkcOTR5ZmUVOKGVRhh/v/zvipzLIpFL5rNVaXQp/xBZA== dependencies: "@rabby-wallet/rabby-sign" "0.4.0" axios "^0.27.2" From fdde2547905acba08d95d97827c76a08483bbec9 Mon Sep 17 00:00:00 2001 From: DMY <147dmy@gmail.com> Date: Fri, 3 Jan 2025 16:02:19 +0800 Subject: [PATCH 2/7] fix: slider NaN (#2703) * fix: slider NaN * fix: CHIANS --- src/ui/models/bridge.ts | 5 ++++- src/ui/views/Swap/hooks/token.tsx | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/ui/models/bridge.ts b/src/ui/models/bridge.ts index bf0f1ace121..bbd682629cb 100644 --- a/src/ui/models/bridge.ts +++ b/src/ui/models/bridge.ts @@ -8,6 +8,7 @@ import { DEFAULT_BRIDGE_AGGREGATOR, DEFAULT_BRIDGE_SUPPORTED_CHAIN, } from '@/constant/bridge'; +import { findChainByServerID } from '@/utils/chain'; export const bridge = createModel()({ name: 'bridge', @@ -122,7 +123,9 @@ export const bridge = createModel()({ return acc; }, {} as Record); this.setField({ - supportedChains: chains.map((item) => mappings[item]), + supportedChains: chains.map( + (item) => findChainByServerID(item)?.enum || mappings[item] + ), }); } }, diff --git a/src/ui/views/Swap/hooks/token.tsx b/src/ui/views/Swap/hooks/token.tsx index 634f7877afc..d32e6101a59 100644 --- a/src/ui/views/Swap/hooks/token.tsx +++ b/src/ui/views/Swap/hooks/token.tsx @@ -265,13 +265,18 @@ export const useTokenPair = (userAddress: string) => { } setPayAmount(v); if (payToken) { - const slider = Number( - new BigNumber(v || 0) - .div(tokenAmountBn(payToken)) - .times(100) - .toFixed(0) - ); + const slider = v + ? Number( + new BigNumber(v || 0) + .div(tokenAmountBn(payToken)) + .times(100) + .toFixed(0) + ) + : 0; setSlider(slider < 0 ? 0 : slider > 100 ? 100 : slider); + if (!payToken?.amount) { + setSlider(0); + } } setUseGasPrice(false); setSwapUseSlider(false); @@ -295,7 +300,7 @@ export const useTokenPair = (userAddress: string) => { const nativeTokenDecimals = useMemo( () => findChain({ enum: chain })?.nativeTokenDecimals || 1e18, - [CHAINS?.[chain]] + [chain] ); const gasLimit = useMemo( From 347c45a73782a56d50e2399b9fd01b3de2bffc46 Mon Sep 17 00:00:00 2001 From: heisenberg <110591045+heisenberg-2077@users.noreply.github.com> Date: Fri, 3 Jan 2025 18:06:10 +0800 Subject: [PATCH 3/7] feat: forgot password (#2694) * feat: store unencryptedKeyringData * fix: undefined * feat: new unlock screen * feat: init ui * feat: ui * fix: reset tip * chore: text * fix: text * fix: ui * fix: unlock ui * fix: ui * fix: private keyring if empty * Update PasswordCard.tsx * fix: reset password log out gas account * fix: release all transport before lock wallet --------- Co-authored-by: kim12322222 Co-authored-by: vvvvvv1vvvvvv --- _raw/locales/en/messages.json | 44 +++++- _raw/locales/zh-CN/messages.json | 42 +++++- src/background/controller/wallet.ts | 17 +++ src/background/index.ts | 14 ++ src/background/service/keyring/index.ts | 118 +++++++++++++-- src/ui/assets/forgot/lock-success.svg | 5 + src/ui/assets/forgot/lock.svg | 5 + src/ui/assets/forgot/recycle.svg | 35 +++++ src/ui/assets/forgot/time-cc.svg | 12 ++ src/ui/assets/forgot/trash-cc.svg | 14 ++ src/ui/assets/new-user-import/background.svg | 39 +++++ src/ui/assets/unlock/background.svg | 49 +++++++ src/ui/assets/unlock/rabby.svg | 24 ++++ src/ui/component/NewUserImport/index.tsx | 24 +++- .../ForgotPassword/CommonConfirmCard.tsx | 60 ++++++++ src/ui/views/ForgotPassword/CommonEntry.tsx | 31 ++++ .../views/ForgotPassword/ForgotPassword.tsx | 131 +++++++++++++++++ src/ui/views/ForgotPassword/ResetConfirm.tsx | 135 ++++++++++++++++++ src/ui/views/ForgotPassword/ResetSuccess.tsx | 21 +++ src/ui/views/ForgotPassword/ResetTip.tsx | 31 ++++ src/ui/views/NewUserImport/Guide.tsx | 16 ++- src/ui/views/NewUserImport/PasswordCard.tsx | 17 ++- src/ui/views/NewUserImport/Success.tsx | 4 +- src/ui/views/Unlock/index.tsx | 99 ++++++++++--- src/ui/views/Unlock/style.less | 33 ----- src/ui/views/index.tsx | 5 + 26 files changed, 946 insertions(+), 79 deletions(-) create mode 100644 src/ui/assets/forgot/lock-success.svg create mode 100644 src/ui/assets/forgot/lock.svg create mode 100644 src/ui/assets/forgot/recycle.svg create mode 100644 src/ui/assets/forgot/time-cc.svg create mode 100644 src/ui/assets/forgot/trash-cc.svg create mode 100644 src/ui/assets/new-user-import/background.svg create mode 100644 src/ui/assets/unlock/background.svg create mode 100644 src/ui/assets/unlock/rabby.svg create mode 100644 src/ui/views/ForgotPassword/CommonConfirmCard.tsx create mode 100644 src/ui/views/ForgotPassword/CommonEntry.tsx create mode 100644 src/ui/views/ForgotPassword/ForgotPassword.tsx create mode 100644 src/ui/views/ForgotPassword/ResetConfirm.tsx create mode 100644 src/ui/views/ForgotPassword/ResetSuccess.tsx create mode 100644 src/ui/views/ForgotPassword/ResetTip.tsx delete mode 100644 src/ui/views/Unlock/style.less diff --git a/_raw/locales/en/messages.json b/_raw/locales/en/messages.json index baac60d38e3..d4e1647eb09 100644 --- a/_raw/locales/en/messages.json +++ b/_raw/locales/en/messages.json @@ -1741,7 +1741,10 @@ "required": "Enter the Password to Unlock", "placeholder": "Enter the Password to Unlock", "error": "incorrect password" - } + }, + "title": "Rabby Wallet", + "description": "The game-changing wallet for Ethereum and all EVM chains", + "btnForgotPassword": "Forgot Password?" }, "addToken": { "noTokenFound": "No token found", @@ -2451,6 +2454,45 @@ "placeholder": "Input safe address", "loading": "Searching the deployed chain of this address" } + }, + "forgotPassword": { + "home": { + "title": "Forgot Password", + "description": "Rabby Wallet doesn't store your password and can't help you retrieve it. Reset your wallet to set up a new one.", + "button": "Begin Reset Process", + "descriptionNoData": "Rabby Wallet doesn't store your password and can't help you retrieve it. Set a new password if you forgot", + "buttonNoData": "Set Password" + }, + "reset": { + "title": "Reset Rabby Wallet", + "button": "Confirm Reset", + "alert": { + "title": "Data will be deleted and unrecoverable:", + "seed": "Seed Phrase", + "privateKey": "Private Key" + }, + "tip": { + "title": "Data will be kept:", + "hardware": "Imported Hardware Wallets", + "whitelist": "Whitelist Settings", + "records": "Signature Records", + "safe": "Imported Safe Wallets", + "watch": "Contacts and Watch-Only Addresses" + }, + "confirm": "Type <1>RESET in the box to confirm and proceed" + }, + "tip": { + "title": "Rabby Wallet Reset Completed", + "description": "Create a new password to continue", + "button": "Set Password", + "descriptionNoData": "Add your address to get started", + "buttonNoData": "Add Address" + }, + "success": { + "title": "Password Successfully Set", + "description": "You're all set to use Rabby Wallet", + "button": "Done" + } } }, "component": { diff --git a/_raw/locales/zh-CN/messages.json b/_raw/locales/zh-CN/messages.json index 404349bd001..bce66fa4b9d 100644 --- a/_raw/locales/zh-CN/messages.json +++ b/_raw/locales/zh-CN/messages.json @@ -1702,7 +1702,8 @@ "error": "密码错误", "placeholder": "输入密码解锁", "required": "输入密码解锁" - } + }, + "btnForgotPassword": "忘记密码?" }, "addressDetail": { "add-to-whitelist": "添加到白名单", @@ -2038,6 +2039,45 @@ }, "sign": { "transactionSpeed": "交易速度" + }, + "forgotPassword": { + "home": { + "buttonNoData": "设置新密码", + "button": "开始重置流程", + "title": "忘记密码", + "description": "Rabby不会保存你的密码,因此丢失密码无法找回 如果忘记密码只能重置钱包并重新设置密码", + "descriptionNoData": "Rabby不会保存你的密码,因此丢失密码无法找回 如果忘记密码只能重新设置密码" + }, + "reset": { + "title": "重置 Rabby Wallet", + "alert": { + "title": "重置后会清除以下数据,且无法帮你找回", + "seed": "助记词", + "privateKey": "私钥" + }, + "tip": { + "title": "会保留以下数据", + "hardware": "已导入的硬件钱包地址", + "safe": "已导入的 Safe 地址", + "watch": "已导入的观察地址", + "whitelist": "转账白名单", + "records": "签名记录" + }, + "confirm": "如确认重置钱包, 请输入: <1>RESET", + "button": "确认重置" + }, + "success": { + "title": "密码创建成功", + "description": "请继续使用 Rabby", + "button": "完成" + }, + "tip": { + "title": "Rabby Wallet 已经重置", + "button": "设置新密码", + "buttonNoData": "重新导入地址", + "description": "请设定一个新的密码继续使用钱包", + "descriptionNoData": "请重新导入地址后使用钱包" + } } }, "component": { diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index 333da424135..c61e8a6eddf 100644 --- a/src/background/controller/wallet.ts +++ b/src/background/controller/wallet.ts @@ -4980,6 +4980,23 @@ export class WalletController extends BaseController { tx, }); }; + + savedUnencryptedKeyringData = async () => + keyringService.savedUnencryptedKeyringData(); + + hasEncryptedKeyringData = async () => + keyringService.hasEncryptedKeyringData(); + + hasUnencryptedKeyringData = async () => + keyringService.hasUnencryptedKeyringData(); + + resetPassword = async (password: string) => + keyringService.resetPassword(password); + + resetBooted = async () => keyringService.resetBooted(); + + getUnencryptedKeyringTypes = async () => + keyringService.getUnencryptedKeyringTypes(); } const wallet = new WalletController(); diff --git a/src/background/index.ts b/src/background/index.ts index 8b29de69137..f6784aafa9d 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -242,6 +242,20 @@ restoreAppState(); ); } +keyringService.on('resetPassword', async () => { + const gasAccount = gasAccountService.getGasAccountData() as GasAccountServiceStore; + + if ( + gasAccount?.account?.type === KEYRING_TYPE.SimpleKeyring || + gasAccount?.account?.type === KEYRING_TYPE.HdKeyring + ) { + gasAccountService.setGasAccountSig(); + eventBus.emit(EVENTS.broadcastToUI, { + method: EVENTS.GAS_ACCOUNT.LOG_OUT, + }); + } +}); + // for page provider browser.runtime.onConnect.addListener((port) => { if ( diff --git a/src/background/service/keyring/index.ts b/src/background/service/keyring/index.ts index d2f1d01bc0a..5093edf3d68 100644 --- a/src/background/service/keyring/index.ts +++ b/src/background/service/keyring/index.ts @@ -47,6 +47,11 @@ import { } from 'background/utils/password'; import uninstalledMetricService from '../uninstalled'; +const UNENCRYPTED_IGNORE_KEYRING = [ + KEYRING_TYPE.SimpleKeyring, + KEYRING_TYPE.HdKeyring, +]; + export const KEYRING_SDK_TYPES = { SimpleKeyring, HdKeyring, @@ -64,6 +69,11 @@ export const KEYRING_SDK_TYPES = { EthImKeyKeyring, }; +export type KeyringSerializedData = { + type: string; + data: T; +}; + interface MemStoreState { isUnlocked: boolean; keyringTypes: any[]; @@ -312,6 +322,12 @@ export class KeyringService extends EventEmitter { */ async setLocked(): Promise { // set locked + // release all transport before lock wallet + this.keyrings.forEach((keyring) => { + if (keyring.cleanUp) { + keyring.cleanUp(); + } + }); this.password = null; passwordClearKey(); this.memStore.updateState({ isUnlocked: false }); @@ -346,6 +362,11 @@ export class KeyringService extends EventEmitter { this.setUnlocked(); } + // force store unencrypted keyring data if not exist + if (!this.store.getState().unencryptedKeyringData) { + await this.persistAllKeyrings(); + } + return this.fullUpdate(); } @@ -786,14 +807,14 @@ export class KeyringService extends EventEmitter { * @param {string} password - The keyring controller password. * @returns {Promise} Resolves to true once keyrings are persisted. */ - persistAllKeyrings(): Promise { + async persistAllKeyrings(): Promise { if (!this.isUnlocked()) { return Promise.reject( new Error('KeyringController - password is not a string') ); } - return Promise.all( + const serializedKeyrings = await Promise.all( this.keyrings.map((keyring) => { return Promise.all([keyring.type, keyring.serialize()]).then( (serializedKeyringArray) => { @@ -801,21 +822,42 @@ export class KeyringService extends EventEmitter { return { type: serializedKeyringArray[0], data: serializedKeyringArray[1], - }; + } as KeyringSerializedData; } ); }) - ) - .then((serializedKeyrings) => { - return passwordEncrypt({ - data: serializedKeyrings, - password: this.password, - }); + ); + + let hasEncryptedKeyringData = false; + const unencryptedKeyringData = serializedKeyrings + .map(({ type, data }) => { + if (!UNENCRYPTED_IGNORE_KEYRING.includes(type as any)) { + return { type, data }; + } + + // maybe empty keyring + // TODO: maybe need remove simple keyring if empty + if (type === KEYRING_TYPE.SimpleKeyring && !data.length) { + return undefined; + } + + hasEncryptedKeyringData = true; + return undefined; }) - .then((encryptedString) => { - this.store.updateState({ vault: encryptedString }); - return true; - }); + .filter(Boolean) as KeyringSerializedData[]; + + const encryptedString = await passwordEncrypt({ + data: serializedKeyrings, + password: this.password, + }); + + this.store.updateState({ + vault: encryptedString, + unencryptedKeyringData, + hasEncryptedKeyringData, + }); + + return true; } /** @@ -1235,6 +1277,56 @@ export class KeyringService extends EventEmitter { isUnlocked(): boolean { return this.memStore.getState().isUnlocked; } + + /** + * unencryptedKeyringData is saved in the store + */ + savedUnencryptedKeyringData(): boolean { + return 'unencryptedKeyringData' in this.store.getState(); + } + + /** + * has seed phrase or private key in the store + */ + hasEncryptedKeyringData(): boolean { + return this.store.getState().hasEncryptedKeyringData; + } + + /** + * has unencrypted keyring data (not seed phrase or private key) in the store + */ + hasUnencryptedKeyringData(): boolean { + return this.store.getState().unencryptedKeyringData?.length > 0; + } + + async resetPassword(password: string) { + // update vault and booted with new password + const unencryptedKeyringData = this.store.getState().unencryptedKeyringData; + const booted = await passwordEncrypt({ + data: 'true', + password, + }); + const vault = await passwordEncrypt({ + data: unencryptedKeyringData, + password, + }); + + this.store.updateState({ vault, booted, hasEncryptedKeyringData: false }); + + this.emit('resetPassword'); + // lock wallet + this.setLocked(); + } + + async resetBooted() { + this.store.updateState({ booted: undefined }); + } + + async getUnencryptedKeyringTypes() { + return (this.store + .getState() + .unencryptedKeyringData?.map((item) => item.type) ?? []) as string[]; + } } export default new KeyringService(); diff --git a/src/ui/assets/forgot/lock-success.svg b/src/ui/assets/forgot/lock-success.svg new file mode 100644 index 00000000000..357fe19e063 --- /dev/null +++ b/src/ui/assets/forgot/lock-success.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/ui/assets/forgot/lock.svg b/src/ui/assets/forgot/lock.svg new file mode 100644 index 00000000000..7bf6acfd430 --- /dev/null +++ b/src/ui/assets/forgot/lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/ui/assets/forgot/recycle.svg b/src/ui/assets/forgot/recycle.svg new file mode 100644 index 00000000000..ab86aa58891 --- /dev/null +++ b/src/ui/assets/forgot/recycle.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/forgot/time-cc.svg b/src/ui/assets/forgot/time-cc.svg new file mode 100644 index 00000000000..931db09d3f0 --- /dev/null +++ b/src/ui/assets/forgot/time-cc.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/ui/assets/forgot/trash-cc.svg b/src/ui/assets/forgot/trash-cc.svg new file mode 100644 index 00000000000..c8eb35af591 --- /dev/null +++ b/src/ui/assets/forgot/trash-cc.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/ui/assets/new-user-import/background.svg b/src/ui/assets/new-user-import/background.svg new file mode 100644 index 00000000000..f88b16dc3c9 --- /dev/null +++ b/src/ui/assets/new-user-import/background.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/unlock/background.svg b/src/ui/assets/unlock/background.svg new file mode 100644 index 00000000000..e54872da032 --- /dev/null +++ b/src/ui/assets/unlock/background.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/unlock/rabby.svg b/src/ui/assets/unlock/rabby.svg new file mode 100644 index 00000000000..4453f0fe32e --- /dev/null +++ b/src/ui/assets/unlock/rabby.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/component/NewUserImport/index.tsx b/src/ui/component/NewUserImport/index.tsx index 27fdd9c4e92..6e883b44336 100644 --- a/src/ui/component/NewUserImport/index.tsx +++ b/src/ui/component/NewUserImport/index.tsx @@ -3,9 +3,15 @@ import styled from 'styled-components'; import { ReactComponent as IconBackCC } from '@/ui/assets/new-user-import/back-cc.svg'; import { ReactComponent as IconDotCC } from '@/ui/assets/new-user-import/dot-cc.svg'; import clsx from 'clsx'; +import { useThemeMode } from '@/ui/hooks/usePreference'; -const StyedBg = styled.div` - background: var(--r-blue-default, #7084ff); +const StyedBg = styled.div<{ + isDarkTheme: boolean; +}>` + background: ${(props) => + props.isDarkTheme + ? 'linear-gradient(0deg, rgba(0, 0, 0, 0.60) 0%, rgba(0, 0, 0, 0.60) 100%), var(--r-blue-default, #7084FF)' + : 'var(--r-blue-default, #7084ff)'}; overflow-x: auto; min-height: 100vh; display: flex; @@ -17,7 +23,7 @@ const StyledCard = styled.div` width: 400px; min-height: 520px; border-radius: 16px; - background: var(--r-neutral-bg1, #fff); + background-color: var(--r-neutral-bg1, #fff); box-shadow: 0px 40px 80px 0px rgba(43, 57, 143, 0.4); padding: 20px; padding-top: 0px; @@ -85,19 +91,25 @@ export const Card = ({ onBack, className, headerClassName, + headerBlock, + cardStyle, }: React.PropsWithChildren<{ title?: React.ReactNode; step?: 1 | 2; onBack?: () => void; className?: string; headerClassName?: string; + headerBlock?: boolean; + cardStyle?: React.CSSProperties; }>) => { + const { isDarkTheme } = useThemeMode(); + return ( - - + +
void; + logoClassName?: string; + logo?: React.ReactNode; + titleText?: string; + descriptionText?: string; + buttonText?: string; +}> = ({ + hasStep = false, + onNext, + logoClassName, + logo, + titleText, + descriptionText, + buttonText, +}) => { + return ( + +
+
{logo}
+

+ {titleText} +

+

+ {descriptionText} +

+
+ +
+
+
+ ); +}; diff --git a/src/ui/views/ForgotPassword/CommonEntry.tsx b/src/ui/views/ForgotPassword/CommonEntry.tsx new file mode 100644 index 00000000000..6d9caabbb98 --- /dev/null +++ b/src/ui/views/ForgotPassword/CommonEntry.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { ReactComponent as LockSVG } from '@/ui/assets/forgot/lock.svg'; +import { useTranslation } from 'react-i18next'; +import { CommonConfirmCard } from './CommonConfirmCard'; + +export const CommonEntry: React.FC<{ + hasEncryptedKeyringData: boolean; + onNext: () => void; +}> = ({ hasEncryptedKeyringData = false, onNext }) => { + const { t } = useTranslation(); + + return ( + } + logoClassName="p-16 rounded-full bg-r-neutral-card2" + titleText={t('page.forgotPassword.home.title')} + descriptionText={ + hasEncryptedKeyringData + ? t('page.forgotPassword.home.description') + : t('page.forgotPassword.home.descriptionNoData') + } + /> + ); +}; diff --git a/src/ui/views/ForgotPassword/ForgotPassword.tsx b/src/ui/views/ForgotPassword/ForgotPassword.tsx new file mode 100644 index 00000000000..c0eea0bfa6d --- /dev/null +++ b/src/ui/views/ForgotPassword/ForgotPassword.tsx @@ -0,0 +1,131 @@ +import React from 'react'; +import Browser from 'webextension-polyfill'; +import { useWallet } from '@/ui/utils'; +import { CommonEntry } from './CommonEntry'; +import { ResetConfirm } from './ResetConfirm'; +import { PasswordCard } from '../NewUserImport/PasswordCard'; +import { ResetTip } from './ResetTip'; +import { useHistory } from 'react-router-dom'; +import { ResetSuccess } from './ResetSuccess'; +import { message } from 'antd'; + +export const ForgotPassword = () => { + const history = useHistory(); + const wallet = useWallet(); + // seed phrase, private key, etc. + const [hasEncryptedKeyringData, setHasEncryptedKeyringData] = React.useState( + false + ); + // hardware wallet, watch address, etc. + const [ + hasUnencryptedKeyringData, + setHasUnencryptedKeyringData, + ] = React.useState(false); + const [keyringTypes, setKeyringTypes] = React.useState([]); + const [step, setStep] = React.useState< + 'entry' | 'reset-confirm' | 'reset-tip' | 'reset-password' | 'reset-success' + >('entry'); + const [prevStep, setPrevStep] = React.useState(step); + + const handleSetStep = React.useCallback( + (newStep: typeof step) => { + setPrevStep(step); + setStep(newStep); + }, + [step] + ); + + const handleBack = React.useCallback(() => { + handleSetStep(prevStep); + }, [handleSetStep, prevStep]); + const onEntryNext = React.useCallback(() => { + if (hasEncryptedKeyringData) { + handleSetStep('reset-confirm'); + } else { + handleSetStep('reset-password'); + } + }, [hasEncryptedKeyringData, handleSetStep]); + const onResetConfirmNext = React.useCallback(async () => { + // If there is no unencrypted keyring data, reset the booted status + if (!hasUnencryptedKeyringData) { + await wallet.resetBooted(); + } + handleSetStep('reset-tip'); + }, [hasUnencryptedKeyringData, handleSetStep]); + const onRestTipNext = React.useCallback(() => { + if (hasUnencryptedKeyringData) { + handleSetStep('reset-password'); + } else { + wallet.tryOpenOrActiveUserGuide(); + } + }, [handleSetStep, hasUnencryptedKeyringData]); + const onPasswordSubmit = React.useCallback( + async (password: string) => { + try { + await wallet.resetPassword(password); + handleSetStep('reset-success'); + } catch (e) { + message.error(e.message); + } + }, + [handleSetStep] + ); + const onResultSuccessNext = React.useCallback(() => { + window.close(); + }, []); + + // This useEffect is used to close the window when the extension icon is clicked + React.useEffect(() => { + wallet.tryOpenOrActiveUserGuide(); + const handleWindowClose = (request: any) => { + if (request.type === 'pageOpened') { + window.close(); + } + }; + Browser.runtime.onMessage.addListener(handleWindowClose); + + return () => { + Browser.runtime.onMessage.removeListener(handleWindowClose); + }; + }, []); + + React.useEffect(() => { + wallet.hasEncryptedKeyringData().then(setHasEncryptedKeyringData); + wallet.hasUnencryptedKeyringData().then(setHasUnencryptedKeyringData); + wallet.getUnencryptedKeyringTypes().then(setKeyringTypes); + }, []); + + return ( + <> + {step === 'entry' && ( + + )} + {step === 'reset-confirm' && ( + + )} + {step === 'reset-password' && ( + + )} + {step === 'reset-tip' && ( + + )} + {step === 'reset-success' && ( + + )} + + ); +}; diff --git a/src/ui/views/ForgotPassword/ResetConfirm.tsx b/src/ui/views/ForgotPassword/ResetConfirm.tsx new file mode 100644 index 00000000000..fb9b756d14a --- /dev/null +++ b/src/ui/views/ForgotPassword/ResetConfirm.tsx @@ -0,0 +1,135 @@ +import { Card } from '@/ui/component/NewUserImport'; +import React from 'react'; +import { ReactComponent as TrashSVG } from '@/ui/assets/forgot/trash-cc.svg'; +import { ReactComponent as TimeSVG } from '@/ui/assets/forgot/time-cc.svg'; +import { Trans, useTranslation } from 'react-i18next'; +import { Button, Input } from 'antd'; +import clsx from 'clsx'; +import { KEYRING_CLASS } from '@/constant'; + +const HARDWARE_TYPES = Object.values(KEYRING_CLASS.HARDWARE); + +export const ResetConfirm: React.FC<{ + onConfirm: () => void; + onBack: () => void; + keyringTypes: string[]; +}> = ({ onConfirm, onBack, keyringTypes }) => { + const { t } = useTranslation(); + const [disabled, setDisabled] = React.useState(true); + const [input, setInput] = React.useState(''); + + return ( + +
+
+ +

+ {t('page.forgotPassword.reset.alert.title')} +

+
+
    + {[ + t('page.forgotPassword.reset.alert.seed'), + t('page.forgotPassword.reset.alert.privateKey'), + ].map((text, index) => ( +
  • + {text} +
  • + ))} +
+
+ +
+
+ +

+ {t('page.forgotPassword.reset.tip.title')} +

+
+
    + {[ + keyringTypes.some((type) => HARDWARE_TYPES.includes(type as any)) && + t('page.forgotPassword.reset.tip.hardware'), + keyringTypes.some((type) => type === KEYRING_CLASS.GNOSIS) && + t('page.forgotPassword.reset.tip.safe'), + keyringTypes.some((type) => type === KEYRING_CLASS.WATCH) && + t('page.forgotPassword.reset.tip.watch'), + t('page.forgotPassword.reset.tip.whitelist'), + t('page.forgotPassword.reset.tip.records'), + ] + .filter(Boolean) + .map((text, index) => ( +
  • + {text} +
  • + ))} +
+
+ +
+ + Type + RESET + in the box to confirm and proceed + +
+ +
+ { + setInput(e.target.value); + setDisabled(e.target.value !== 'RESET'); + }} + value={input} + placeholder="RESET" + className={clsx( + 'h-[52px] rounded-[8px]', + 'border-rabby-neutral-line bg-r-neutral-bg1 hover:border-rabby-blue-default focus:border-rabby-blue-default', + 'text-r-neutral-title1 text-15 placeholder-r-neutral-foot placeholder-opacity-70' + )} + size="large" + /> +
+ +
+ +
+
+ ); +}; diff --git a/src/ui/views/ForgotPassword/ResetSuccess.tsx b/src/ui/views/ForgotPassword/ResetSuccess.tsx new file mode 100644 index 00000000000..d7ea948ca35 --- /dev/null +++ b/src/ui/views/ForgotPassword/ResetSuccess.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { ReactComponent as LockSVG } from '@/ui/assets/forgot/lock-success.svg'; +import { useTranslation } from 'react-i18next'; +import { CommonConfirmCard } from './CommonConfirmCard'; + +export const ResetSuccess: React.FC<{ + onNext: () => void; +}> = ({ onNext }) => { + const { t } = useTranslation(); + + return ( + } + logoClassName="p-16 rounded-full bg-r-green-light" + titleText={t('page.forgotPassword.success.title')} + descriptionText={t('page.forgotPassword.success.description')} + buttonText={t('page.forgotPassword.success.button')} + /> + ); +}; diff --git a/src/ui/views/ForgotPassword/ResetTip.tsx b/src/ui/views/ForgotPassword/ResetTip.tsx new file mode 100644 index 00000000000..534086c93ab --- /dev/null +++ b/src/ui/views/ForgotPassword/ResetTip.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { ReactComponent as RecycleSVG } from '@/ui/assets/forgot/recycle.svg'; +import { useTranslation } from 'react-i18next'; +import { CommonConfirmCard } from './CommonConfirmCard'; + +export const ResetTip: React.FC<{ + hasUnencryptedKeyringData: boolean; + onNext: () => void; +}> = ({ hasUnencryptedKeyringData = false, onNext }) => { + const { t } = useTranslation(); + + return ( + } + logoClassName="flex justify-center" + titleText={t('page.forgotPassword.tip.title')} + descriptionText={ + hasUnencryptedKeyringData + ? t('page.forgotPassword.tip.description') + : t('page.forgotPassword.tip.descriptionNoData') + } + /> + ); +}; diff --git a/src/ui/views/NewUserImport/Guide.tsx b/src/ui/views/NewUserImport/Guide.tsx index e77e3b4b217..d7960732bde 100644 --- a/src/ui/views/NewUserImport/Guide.tsx +++ b/src/ui/views/NewUserImport/Guide.tsx @@ -5,6 +5,8 @@ import { Button } from 'antd'; import clsx from 'clsx'; import { useHistory } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import BackgroundSVG from '@/ui/assets/new-user-import/background.svg'; +import { useThemeMode } from '@/ui/hooks/usePreference'; export const Guide = () => { const { t } = useTranslation(); @@ -18,8 +20,20 @@ export const Guide = () => { history.push('/new-user/import-list'); }, []); + const { isDarkTheme } = useThemeMode(); + return ( - +
= ({ onSubmit, step, onBack }) => { ]} > = ({ onSubmit, step, onBack }) => { ]} > { /> ) : (
- {name} + + {name} +
)} diff --git a/src/ui/views/Unlock/index.tsx b/src/ui/views/Unlock/index.tsx index ba9c2921f9e..2e9f30909dc 100644 --- a/src/ui/views/Unlock/index.tsx +++ b/src/ui/views/Unlock/index.tsx @@ -2,9 +2,28 @@ import React, { useEffect, useRef } from 'react'; import { Input, Form, Button } from 'antd'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; -import { useWallet, useApproval, useWalletRequest, getUiType } from 'ui/utils'; +import { + useWallet, + useApproval, + useWalletRequest, + getUiType, + openInternalPageInTab, +} from 'ui/utils'; +import { ReactComponent as RabbySVG } from '@/ui/assets/unlock/rabby.svg'; +import { ReactComponent as BackgroundSVG } from '@/ui/assets/unlock/background.svg'; +import clsx from 'clsx'; +import styled from 'styled-components'; -import './style.less'; +const InputFormStyled = styled(Form.Item)` + .ant-form-item-explain { + font-size: 13px; + line-height: 16px; + margin-top: 12px; + margin-bottom: 24px; + min-height: 0px; + color: var(--r-red-default); + } +`; const Unlock = () => { const wallet = useWallet(); @@ -15,6 +34,7 @@ const Unlock = () => { const { t } = useTranslation(); const history = useHistory(); const isUnlockingRef = useRef(false); + const [hasForgotPassword, setHasForgotPassword] = React.useState(false); useEffect(() => { if (!inputEl.current) return; @@ -47,19 +67,39 @@ const Unlock = () => { isUnlockingRef.current = false; }; + useEffect(() => { + wallet.savedUnencryptedKeyringData().then(setHasForgotPassword); + }, []); + return ( -
-
- +
+ +
+ +

+ {t('page.unlock.title')} +

+

+ {t('page.unlock.description')} +

-
- + { > - - + + + +
+ {hasForgotPassword && ( + + )} +
); }; diff --git a/src/ui/views/Unlock/style.less b/src/ui/views/Unlock/style.less deleted file mode 100644 index fd9332bcecc..00000000000 --- a/src/ui/views/Unlock/style.less +++ /dev/null @@ -1,33 +0,0 @@ -.unlock { - height: 100vh; - display: flex; - flex-direction: column; - overflow: hidden; - .header { - height: 400px; - width: 100%; - background: linear-gradient(101.58deg, #8bb2ff 1.04%, #7a7cff 90.78%); - background-size: 100%; - position: relative; - .image { - position: absolute; - width: 337px; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - } - .slogan { - position: absolute; - bottom: 42px; - color: white; - text-align: center; - width: 100%; - } - } - - .ant-input { - border-radius: 6px; - border: 1px solid var(--r-neutral-line, #D3D8E0); - background: var(--r-neutral-card-2, #F2F4F7); - } -} diff --git a/src/ui/views/index.tsx b/src/ui/views/index.tsx index 263d919abc3..1caee9d0bfe 100644 --- a/src/ui/views/index.tsx +++ b/src/ui/views/index.tsx @@ -18,6 +18,7 @@ import { useMount } from 'react-use'; import { useMemoizedFn } from 'ahooks'; import { useThemeModeOnMain } from '../hooks/usePreference'; import { useSubscribeCurrentAccountChanged } from '../hooks/backgroundState/useAccount'; +import { ForgotPassword } from './ForgotPassword/ForgotPassword'; const AsyncMainRoute = lazy(() => import('./MainRoute')); const useAutoLock = () => { @@ -76,6 +77,10 @@ const Main = () => { + + + + From 203e1797dc182504bcc070a2d95754fc132b5215 Mon Sep 17 00:00:00 2001 From: DMY <147dmy@gmail.com> Date: Fri, 3 Jan 2025 18:20:00 +0800 Subject: [PATCH 4/7] feat: rabby fee display (#2709) * feat: update rabby fee display * style tuning * style tuning --- _raw/locales/en/messages.json | 2 +- .../views/Bridge/Component/BridgeContent.tsx | 16 +++++++-- .../Bridge/Component/BridgeQuoteItem.tsx | 9 ----- .../views/Bridge/Component/BridgeShowMore.tsx | 34 ++++++++++++++----- src/ui/views/Bridge/Component/BridgeToken.tsx | 10 ------ src/ui/views/Swap/Component/Main.tsx | 30 ++++++++++++++-- src/ui/views/Swap/Component/QuoteItem.tsx | 29 ++-------------- src/ui/views/Swap/Component/Token.tsx | 30 +--------------- 8 files changed, 71 insertions(+), 89 deletions(-) diff --git a/_raw/locales/en/messages.json b/_raw/locales/en/messages.json index d4e1647eb09..a5a123efbf8 100644 --- a/_raw/locales/en/messages.json +++ b/_raw/locales/en/messages.json @@ -942,7 +942,7 @@ "process-with-two-step-approve": "Proceed with two step approve", "fetch-best-quote": "Fetching the Best quote", "usd-after-fees": "≈ {{usd}} ", - "no-fees-for-wrap": "No Rabby fees for Wrap", + "no-fees-for-wrap": "No Rabby fee for Wrap", "No-available-quote": "No available quote", "price-impact": "Price Impact", "loss-tips": "You're losing {{usd}}. Try a smaller amount in a small market.", diff --git a/src/ui/views/Bridge/Component/BridgeContent.tsx b/src/ui/views/Bridge/Component/BridgeContent.tsx index 29190b8d6e3..3001d82f4ff 100644 --- a/src/ui/views/Bridge/Component/BridgeContent.tsx +++ b/src/ui/views/Bridge/Component/BridgeContent.tsx @@ -6,7 +6,12 @@ import BigNumber from 'bignumber.js'; import { useWallet } from '@/ui/utils'; import clsx from 'clsx'; import { QuoteList } from './BridgeQuotes'; -import { useQuoteVisible, useSetQuoteVisible, useSetRefreshId } from '../hooks'; +import { + useQuoteVisible, + useSetQuoteVisible, + useSetRefreshId, + useSetSettingVisible, +} from '../hooks'; import { useRbiSource } from '@/ui/utils/ga-event'; import { useCss } from 'react-use'; import { findChainByEnum } from '@/utils/chain'; @@ -366,6 +371,12 @@ export const BridgeContent = () => { const [showMoreOpen, setShowMoreOpen] = useState(false); + const switchFeePopup = useSetSettingVisible(); + + const openFeePopup = useCallback(() => { + switchFeePopup(true); + }, [switchFeePopup]); + return (
{ /> ) : null} -
+
{selectedBridgeQuote && ( { const openSwapQuote = useSetQuoteVisible(); - const openFeePopup = useSetSettingVisible(); - const aggregatorsList = useRabbySelector( (s) => s.bridge.aggregatorsList || [] ); @@ -208,13 +206,6 @@ export const BridgeQuoteItem = (props: QuoteItemProps) => { ), })} - { - e.stopPropagation(); - openFeePopup(true); - }} - />
diff --git a/src/ui/views/Bridge/Component/BridgeShowMore.tsx b/src/ui/views/Bridge/Component/BridgeShowMore.tsx index 04bbbd8f45a..feccb1aabf0 100644 --- a/src/ui/views/Bridge/Component/BridgeShowMore.tsx +++ b/src/ui/views/Bridge/Component/BridgeShowMore.tsx @@ -7,7 +7,6 @@ import React, { Dispatch, PropsWithChildren, SetStateAction, - useEffect, useMemo, } from 'react'; import { Trans, useTranslation } from 'react-i18next'; @@ -19,9 +18,6 @@ import imgBestQuoteSharpBg from '@/ui/assets/swap/best-quote-sharp-bg.svg'; import styled from 'styled-components'; import { useDebounce } from 'react-use'; -const dottedClassName = - 'h-0 flex-1 border-b-[1px] border-solid border-rabby-neutral-line opacity-50'; - const PreferMEVGuardSwitch = styled(Switch)` min-width: 20px; height: 12px; @@ -67,6 +63,7 @@ export const BridgeShowMore = ({ originPreferMEVGuarded, switchPreferMEV, recommendValue, + openFeePopup, }: { open: boolean; setOpen: Dispatch>; @@ -97,9 +94,12 @@ export const BridgeShowMore = ({ originPreferMEVGuarded?: boolean; switchPreferMEV?: (b: boolean) => void; recommendValue?: number; + openFeePopup: () => void; }) => { const { t } = useTranslation(); + const RABBY_FEE = '0.25%'; + const data = useMemo(() => { if (quoteLoading || (!sourceLogo && !sourceName)) { return { @@ -147,8 +147,7 @@ export const BridgeShowMore = ({ return (
-
-
+
-
@@ -275,6 +276,23 @@ export const BridgeShowMore = ({ isWrapToken={isWrapToken} recommendValue={recommendValue} /> + + +
+ {isWrapToken && type === 'swap' + ? t('page.swap.no-fees-for-wrap') + : RABBY_FEE} +
+
+ {showMEVGuardedSwitch && type === 'swap' ? ( (); @@ -335,13 +332,6 @@ export const BridgeToken = ({ ) : ( {useValue} )} - {!valueLoading && isToToken && !!value && ( - openFeePopup(true)} - viewBox="0 0 14 14" - className="w-14 h-14 text-r-neutral-foot cursor-pointer" - /> - )}
diff --git a/src/ui/views/Swap/Component/Main.tsx b/src/ui/views/Swap/Component/Main.tsx index 44a35f9c898..4f05eea919b 100644 --- a/src/ui/views/Swap/Component/Main.tsx +++ b/src/ui/views/Swap/Component/Main.tsx @@ -14,7 +14,12 @@ import BigNumber from 'bignumber.js'; import { useWallet } from '@/ui/utils'; import clsx from 'clsx'; import { QuoteList } from './Quotes'; -import { useQuoteVisible, useSetQuoteVisible, useSetRefreshId } from '../hooks'; +import { + useQuoteVisible, + useSetQuoteVisible, + useSetRabbyFee, + useSetRefreshId, +} from '../hooks'; import { DEX_ENUM, DEX_SPENDER_WHITELIST } from '@rabby-wallet/rabby-swap'; import { useDispatch } from 'react-redux'; import { useRbiSource } from '@/ui/utils/ga-event'; @@ -431,11 +436,29 @@ export const Main = () => { ] ); + const setRabbyFeeVisible = useSetRabbyFee(); + + const openFeePopup = useCallback(() => { + if (isWrapToken) { + return; + } + setRabbyFeeVisible({ + visible: true, + dexName: activeProvider?.name || undefined, + feeDexDesc: activeProvider?.quote?.dexFeeDesc || undefined, + }); + }, [ + isWrapToken, + setRabbyFeeVisible, + activeProvider?.name, + activeProvider?.quote, + ]); + return (
-
+
{ !!amountAvailable && !!payToken && !!receiveToken && ( -
+
{ - e.stopPropagation(); - setRabbyFeeVisible({ - visible: true, - dexName: dexId, - feeDexDesc: quote?.dexFeeDesc || undefined, - }); - } - } > {disabled ? ( {bestQuotePercent} @@ -522,14 +505,6 @@ export const DexQuoteItem = ( usd: receivedTokenUsd, })} - - - )}
diff --git a/src/ui/views/Swap/Component/Token.tsx b/src/ui/views/Swap/Component/Token.tsx index c055986538f..55f56d66ec5 100644 --- a/src/ui/views/Swap/Component/Token.tsx +++ b/src/ui/views/Swap/Component/Token.tsx @@ -14,9 +14,7 @@ import { ReactComponent as RcIconWalletCC } from '@/ui/assets/swap/wallet-cc.svg import { tokenAmountBn } from '@/ui/utils/token'; import clsx from 'clsx'; import SkeletonInput from 'antd/lib/skeleton/Input'; -import { ReactComponent as RcIconInfoCC } from 'ui/assets/info-cc.svg'; -import { QuoteProvider, useSetRabbyFee } from '../hooks'; -import { TooltipWithMagnetArrow } from '@/ui/component/Tooltip/TooltipWithMagnetArrow'; +import { QuoteProvider } from '../hooks'; const StyledInput = styled(Input)` &, @@ -143,23 +141,10 @@ export const SwapTokenItem = (props: SwapTokenItemProps) => { openTokenModal: () => void; }) => React.ReactNode = useCallback((p) => , []); - const setRabbyFeeVisible = useSetRabbyFee(); - const isWrapQuote = useMemo(() => { return currentQuote?.name === 'WrapToken'; }, [currentQuote?.name]); - const openFeePopup = useCallback(() => { - if (isWrapQuote) { - return; - } - setRabbyFeeVisible({ - visible: true, - dexName: currentQuote?.name || undefined, - feeDexDesc: currentQuote?.quote?.dexFeeDesc || undefined, - }); - }, [isWrapQuote, currentQuote?.name, currentQuote?.quote]); - const onInputChange: React.ChangeEventHandler = useCallback( (e) => { onValueChange?.(e.target.value); @@ -281,19 +266,6 @@ export const SwapTokenItem = (props: SwapTokenItemProps) => { ) : ( {usdValue} )} - {!isFrom && !valueLoading && !!value && ( - - - - )}
From 7fbf2257d9ee1b0eec1300fdcc472bcb42d61d43 Mon Sep 17 00:00:00 2001 From: vvvvvv1vvvvvv Date: Fri, 3 Jan 2025 18:24:59 +0800 Subject: [PATCH 5/7] feat: + changelog --- changeLogs/0938.md | 2 ++ changeLogs/index.ts | 2 ++ changeLogs/zh-CN/0938.md | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 changeLogs/0938.md create mode 100644 changeLogs/zh-CN/0938.md diff --git a/changeLogs/0938.md b/changeLogs/0938.md new file mode 100644 index 00000000000..529e4a3047b --- /dev/null +++ b/changeLogs/0938.md @@ -0,0 +1,2 @@ +- Added support for resetting password +- Optimized withdrawal process for GasAccount \ No newline at end of file diff --git a/changeLogs/index.ts b/changeLogs/index.ts index 2c2aa4df58d..500ef828c29 100644 --- a/changeLogs/index.ts +++ b/changeLogs/index.ts @@ -99,6 +99,7 @@ import version0933 from './0933.md'; import version0935 from './0935.md'; import version0936 from './0936.md'; import version0937 from './0937.md'; +import version0938 from './0938.md'; const version = process.env.release || '0'; const versionMap = { @@ -204,6 +205,7 @@ const versionMap = { '0.93.5': version0935, '0.93.6': version0936, '0.93.7': version0937, + '0.93.8': version0938, }; export const getUpdateContent = () => { return versionMap[version]; diff --git a/changeLogs/zh-CN/0938.md b/changeLogs/zh-CN/0938.md new file mode 100644 index 00000000000..021c9cdbf52 --- /dev/null +++ b/changeLogs/zh-CN/0938.md @@ -0,0 +1,2 @@ +- 支持重置密码 +- 优化 GasAccount 提现流程 \ No newline at end of file From 21ce5d7aa5f316e78525a4b9bf1cc684f4b7602a Mon Sep 17 00:00:00 2001 From: vvvvvv1vvvvvv Date: Fri, 3 Jan 2025 18:31:52 +0800 Subject: [PATCH 6/7] 0.93.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c8d78330f7e..e109ce5b784 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rabby", - "version": "0.93.7", + "version": "0.93.8", "description": "A browser plugin for DeFi users", "scripts": { "clean": "node build/clean.js", From 5fb19e5ccf09eef9a88ed69a2ad751e868ea55c9 Mon Sep 17 00:00:00 2001 From: vvvvvv1vvvvvv Date: Fri, 3 Jan 2025 18:31:53 +0800 Subject: [PATCH 7/7] [release] 0.93.8 --- src/manifest/chrome-mv2/manifest.json | 2 +- src/manifest/chrome-mv3/manifest.json | 2 +- src/manifest/firefox-mv2/manifest.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/manifest/chrome-mv2/manifest.json b/src/manifest/chrome-mv2/manifest.json index 8bcd79e57aa..1281498c04f 100644 --- a/src/manifest/chrome-mv2/manifest.json +++ b/src/manifest/chrome-mv2/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "0.93.7", + "version": "0.93.8", "default_locale": "en", "description": "__MSG_appDescription__", "icons": { diff --git a/src/manifest/chrome-mv3/manifest.json b/src/manifest/chrome-mv3/manifest.json index f533d7ed67e..79edd0bfcb3 100644 --- a/src/manifest/chrome-mv3/manifest.json +++ b/src/manifest/chrome-mv3/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "0.93.7", + "version": "0.93.8", "default_locale": "en", "description": "__MSG_appDescription__", "icons": { diff --git a/src/manifest/firefox-mv2/manifest.json b/src/manifest/firefox-mv2/manifest.json index 17b9c3db87b..b817560f3e0 100644 --- a/src/manifest/firefox-mv2/manifest.json +++ b/src/manifest/firefox-mv2/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "0.93.7", + "version": "0.93.8", "default_locale": "en", "description": "__MSG_appDescription__", "icons": {