diff --git a/_raw/locales/en/messages.json b/_raw/locales/en/messages.json index 81928783b9c..fddb8d2459e 100644 --- a/_raw/locales/en/messages.json +++ b/_raw/locales/en/messages.json @@ -1250,7 +1250,13 @@ "switchChain": { "title": "Switching to {{chain}}", "chainNotSupport": "The requested chain is not supported by Rabby yet", - "testnetTip": "Please turn on \"Enable Testnets\" under \"More\" before connecting to testnets" + "testnetTip": "Please turn on \"Enable Testnets\" under \"More\" before connecting to testnets", + "chainNotSupportYet": "The requested chain is not supported by Rabby yet", + "chainId": "Chain ID:", + "unknownChain": "Unknown chain", + "requestsReceived": "1 request received", + "requestsReceivedPlural": "{{count}} requests received", + "requestRabbyToSupport": "Request Rabby to Support" }, "signText": { "title": "Sign Text", diff --git a/package.json b/package.json index 19d3c4b953d..34eddaf96cc 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@rabby-wallet/eth-watch-keyring": "^1.0.0", "@rabby-wallet/gnosis-sdk": "^1.3.5", "@rabby-wallet/page-provider": "^0.1.20", - "@rabby-wallet/rabby-api": "^0.6.21", + "@rabby-wallet/rabby-api": "^0.6.22", "@rabby-wallet/rabby-security-engine": "^1.1.16", "@rabby-wallet/rabby-swap": "^0.0.29", "@rabby-wallet/widgets": "^1.0.9", diff --git a/src/background/controller/provider/controller.ts b/src/background/controller/provider/controller.ts index 7e431367abb..4e958447cea 100644 --- a/src/background/controller/provider/controller.ts +++ b/src/background/controller/provider/controller.ts @@ -50,7 +50,7 @@ import { Account } from 'background/service/preference'; import { validateGasPriceRange, is1559Tx } from '@/utils/transaction'; import stats from '@/stats'; import BigNumber from 'bignumber.js'; -import { AddEthereumChainParams } from 'ui/views/Approval/components/AddChain'; +import { AddEthereumChainParams } from '@/ui/views/Approval/components/AddChain/type'; import { formatTxMetaForRpcResult } from 'background/utils/tx'; import { findChainByEnum } from '@/utils/chain'; import eventBus from '@/eventBus'; diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index 3de9bbe0884..1cbdb41008a 100644 --- a/src/background/controller/wallet.ts +++ b/src/background/controller/wallet.ts @@ -3296,6 +3296,8 @@ export class WalletController extends BaseController { isModuleEnabled, }; }; + + updateNotificationWinProps = notificationService.updateNotificationWinProps; } const wallet = new WalletController(); diff --git a/src/background/service/notification.ts b/src/background/service/notification.ts index 8d8127ecb7e..29e925c48e0 100644 --- a/src/background/service/notification.ts +++ b/src/background/service/notification.ts @@ -1,4 +1,4 @@ -import { browser } from 'webextension-polyfill-ts'; +import { Windows, browser } from 'webextension-polyfill-ts'; import Events from 'events'; import { ethErrors } from 'eth-rpc-errors'; import { v4 as uuidv4 } from 'uuid'; @@ -378,6 +378,12 @@ class NotificationService extends Events { }); }; + updateNotificationWinProps = (winProps: Windows.UpdateUpdateInfoType) => { + if (this.notifiWindowId !== null) { + browser.windows.update(this.notifiWindowId!, winProps); + } + }; + setCurrentRequestDeferFn = (fn: () => void) => { this.currentRequestDeferFn = fn; }; diff --git a/src/ui/assets/add-chain/email.svg b/src/ui/assets/add-chain/email.svg new file mode 100644 index 00000000000..9740adf6d58 --- /dev/null +++ b/src/ui/assets/add-chain/email.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/ui/assets/add-chain/info.svg b/src/ui/assets/add-chain/info.svg new file mode 100644 index 00000000000..71de12e0117 --- /dev/null +++ b/src/ui/assets/add-chain/info.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/ui/views/Approval/components/Actions/index.tsx b/src/ui/views/Approval/components/Actions/index.tsx index 1272b55bd54..03fabfcceb1 100644 --- a/src/ui/views/Approval/components/Actions/index.tsx +++ b/src/ui/views/Approval/components/Actions/index.tsx @@ -42,6 +42,7 @@ import IconArrowRight from 'ui/assets/approval/edit-arrow-right.svg'; import IconSpeedUp from 'ui/assets/sign/tx/speedup.svg'; import { ReactComponent as IconQuestionMark } from 'ui/assets/sign/tx/question-mark.svg'; import { TooltipWithMagnetArrow } from '@/ui/component/Tooltip/TooltipWithMagnetArrow'; +import { NoActionAlert } from '../NoActionAlert/NoActionAlert'; export const SignTitle = styled.div` display: flex; @@ -170,24 +171,24 @@ const Actions = ({ + {data.contractCall && ( + + )} -
-
{actionName}
-
- {data.contractCall && ( - - {t('page.signTx.unknownActionType')}{' '} - - - - - )} + {!data.contractCall && ( +
+
{actionName}
-
+ )}
{data.swap && ( { const wallet = useWallet(); const [, resolveApproval, rejectApproval] = useApproval(); @@ -182,6 +83,14 @@ const AddChain = ({ params }: { params: AddChainProps }) => { init(); }, []); + useEffect(() => { + if (state.isShowUnsupportAlert) { + wallet.updateNotificationWinProps({ + height: 388, + }); + } + }, [state.isShowUnsupportAlert]); + const handleConfirm = () => { resolveApproval(); }; @@ -200,26 +109,7 @@ const AddChain = ({ params }: { params: AddChainProps }) => { if (!inited) return <>; if (state.isShowUnsupportAlert) { - return ( - -
- -
- {t('page.switchChain.chainNotSupport')} -
-
-
- -
-
- ); + return ; } if (state.isSwitchToMainnet || state.isSwitchToTestnet) { diff --git a/src/ui/views/Approval/components/AddChain/UnsupportedAlert.tsx b/src/ui/views/Approval/components/AddChain/UnsupportedAlert.tsx new file mode 100644 index 00000000000..c27c9939351 --- /dev/null +++ b/src/ui/views/Approval/components/AddChain/UnsupportedAlert.tsx @@ -0,0 +1,123 @@ +import { Button } from 'antd'; +import React from 'react'; +import { ReactComponent as IconInfo } from '@/ui/assets/add-chain/info.svg'; +import { ReactComponent as IconEmail } from '@/ui/assets/add-chain/email.svg'; +import { noop, useApproval, useWallet } from '@/ui/utils'; +import { useTranslation } from 'react-i18next'; +import { Footer } from './style'; +import { AddEthereumChainParams } from './type'; +import IconUnknown from '@/ui/assets/token-default.svg'; +import clsx from 'clsx'; +import { useAccount } from '@/ui/store-hooks'; + +interface Props { + data: AddEthereumChainParams; +} + +export const UnsupportedAlert: React.FC = ({ data }) => { + const [, , rejectApproval] = useApproval(); + const { t } = useTranslation(); + const [imgUrl, setImgUrl] = React.useState(data.rpcUrls?.[0]); + const [isRequested, setIsRequested] = React.useState(false); + const wallet = useWallet(); + const [currentAccount] = useAccount(); + const [requestedCount, setRequestedCount] = React.useState(1); + const [isRequesting, setIsRequesting] = React.useState(false); + + React.useEffect(() => { + const img = new Image(); + img.src = data.rpcUrls?.[0]; + img.onload = () => { + setImgUrl(data.rpcUrls[0]); + }; + img.onerror = () => { + setImgUrl(IconUnknown); + }; + }, [data]); + + const handleRequest = React.useCallback(() => { + setIsRequesting(true); + wallet.openapi + .walletSupportChain({ + chain_id: data.chainId, + user_addr: currentAccount!.address, + }) + .then((res) => { + setRequestedCount(res.count ? res.count : 1); + setIsRequested(true); + }); + }, [data, currentAccount]); + + return ( +
+
+

+ {t('page.switchChain.chainId')} + {data.chainId} +

+
+ + {data.chainName ?? t('page.switchChain.unknownChain')} +
+
+
+ + {t('page.switchChain.chainNotSupportYet')} +
+
+
+ {isRequested ? ( +
+ {requestedCount > 1 + ? t('page.switchChain.requestsReceivedPlural', { + count: requestedCount, + }) + : t('page.switchChain.requestsReceived')} +
+ ) : ( +
+ + + {t('page.switchChain.requestRabbyToSupport')} + +
+ )} +
+
+
+
+ +
+
+ ); +}; diff --git a/src/ui/views/Approval/components/AddChain/style.ts b/src/ui/views/Approval/components/AddChain/style.ts new file mode 100644 index 00000000000..5bebef6f495 --- /dev/null +++ b/src/ui/views/Approval/components/AddChain/style.ts @@ -0,0 +1,89 @@ +import styled from 'styled-components'; + +export const OptionsWrapper = styled.div` + height: 100%; + display: flex; + flex-direction: column; + + .content { + padding: 20px; + } + + .title { + color: #13141a; + font-size: 20px; + font-style: normal; + font-weight: 500; + line-height: normal; + margin-bottom: 40px; + } + .connect-site-card { + border-radius: 8px; + background: #f5f6fa; + display: inline-flex; + padding: 28px 28px 32px 28px; + width: 100%; + flex-direction: column; + align-items: center; + gap: 14px; + margin-bottom: 40px; + + .site-origin { + color: #13141a; + text-align: center; + font-size: 22px; + font-style: normal; + font-weight: 500; + line-height: normal; + } + } + .switch-chain { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + } + .chain-card { + display: flex; + width: 260px; + padding: 12px; + justify-content: center; + align-items: center; + gap: 8px; + border-radius: 6px; + border: 1px solid #e5e9ef; + + color: #13141a; + font-size: 20px; + font-style: normal; + font-weight: 500; + line-height: normal; + + img { + width: 24px; + height: 24px; + border-radius: 50%; + flex-shrink: 0; + } + } +`; + +export const Footer = styled.div` + margin-top: auto; + height: 76px; + border-top: 1px solid #e5e9ef; + padding: 16px 20px; + display: flex; + justify-content: space-between; + .ant-btn-primary[disabled], + .ant-btn-primary[disabled]:hover, + .ant-btn-primary[disabled]:focus, + .ant-btn-primary[disabled]:active { + background-color: rgba(112, 132, 255, 0.4); + border: none; + &:before { + display: none; + } + } +`; diff --git a/src/ui/views/Approval/components/AddChain/type.ts b/src/ui/views/Approval/components/AddChain/type.ts new file mode 100644 index 00000000000..0ef2428fb59 --- /dev/null +++ b/src/ui/views/Approval/components/AddChain/type.ts @@ -0,0 +1,11 @@ +export interface AddEthereumChainParams { + chainId: string; + chainName: string; + nativeCurrency: { + name: string; + symbol: string; + decimals: number; + }; + rpcUrls: string[]; + blockExplorerUrls: string[]; +} diff --git a/src/ui/views/Approval/components/NoActionAlert/NoActionAlert.tsx b/src/ui/views/Approval/components/NoActionAlert/NoActionAlert.tsx new file mode 100644 index 00000000000..d0c9a2298a9 --- /dev/null +++ b/src/ui/views/Approval/components/NoActionAlert/NoActionAlert.tsx @@ -0,0 +1,109 @@ +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import IconAlert from '@/ui/assets/sign/tx/alert.svg'; +import React from 'react'; +import clsx from 'clsx'; +import { ReactComponent as IconEmail } from '@/ui/assets/add-chain/email.svg'; +import { useAccount } from '@/ui/store-hooks'; +import { noop, useWallet } from '@/ui/utils'; + +const NoActionAlertStyled = styled.div` + display: flex; + align-items: center; + flex-direction: column; + background: var(--r-neutral-card-1); + border-radius: 6px; + padding: 15px; + font-weight: 500; + font-size: 13px; + line-height: 18px; + color: var(--r-neutral-body); + margin-bottom: 15px; + .icon-alert { + margin-right: 4px; + width: 14px; + margin-top: 2px; + } +`; + +type SupportOrigin = { + origin: string; + text: string; +}; + +type SupportSelector = { + chainId: string; + contractAddress?: string; + selector?: string; +}; + +interface Props { + data: SupportOrigin | SupportSelector; +} + +export const NoActionAlert: React.FC = ({ data }) => { + const { t } = useTranslation(); + const [isRequested, setIsRequested] = React.useState(false); + const wallet = useWallet(); + const [currentAccount] = useAccount(); + const [requestedCount, setRequestedCount] = React.useState(1); + const [isRequesting, setIsRequesting] = React.useState(false); + + const handleRequest = React.useCallback(() => { + setIsRequesting(true); + let promise; + if ('origin' in data) { + promise = wallet.openapi.walletSupportOrigin({ + origin: data.origin, + user_addr: currentAccount!.address, + text: data.text, + }); + } else { + promise = wallet.openapi.walletSupportSelector({ + chain_id: data.chainId, + contract_id: data.contractAddress ?? '', + selector: (data.selector ?? '').slice(0, 10), + user_addr: currentAccount!.address, + }); + } + + promise.then((res) => { + setRequestedCount(res.count ? res.count : 1); + setIsRequested(true); + }); + }, [data, currentAccount]); + + return ( + +
+ + {t('page.signTx.sigCantDecode')} +
+
+
+ {isRequested ? ( +
+ {requestedCount > 1 + ? t('page.switchChain.requestsReceivedPlural', { + count: requestedCount, + }) + : t('page.switchChain.requestsReceived')} +
+ ) : ( +
+ + + {t('page.switchChain.requestRabbyToSupport')} + +
+ )} +
+ + ); +}; diff --git a/src/ui/views/Approval/components/SignText.tsx b/src/ui/views/Approval/components/SignText.tsx index 866c9c28c03..6e9133cf826 100644 --- a/src/ui/views/Approval/components/SignText.tsx +++ b/src/ui/views/Approval/components/SignText.tsx @@ -357,6 +357,7 @@ const SignText = ({ params }: { params: SignTextProps }) => { engineResults={engineResults} raw={hexData} message={signText} + origin={params.session.origin} /> )}
diff --git a/src/ui/views/Approval/components/SignTypedData.tsx b/src/ui/views/Approval/components/SignTypedData.tsx index 6d3f7812f8b..d0b5e956b06 100644 --- a/src/ui/views/Approval/components/SignTypedData.tsx +++ b/src/ui/views/Approval/components/SignTypedData.tsx @@ -520,6 +520,7 @@ const SignTypedData = ({ params }: { params: SignTypedDataProps }) => { engineResults={engineResults} raw={isSignTypedDataV1 ? data[0] : signTypedData || data[1]} message={parsedMessage} + origin={params.session.origin} /> )}
diff --git a/src/ui/views/Approval/components/TextActions/index.tsx b/src/ui/views/Approval/components/TextActions/index.tsx index f69b5232547..ed429c52bb4 100644 --- a/src/ui/views/Approval/components/TextActions/index.tsx +++ b/src/ui/views/Approval/components/TextActions/index.tsx @@ -10,6 +10,7 @@ import VerifyAddress from './VerifyAddress'; import IconAlert from 'ui/assets/sign/tx/alert.svg'; import clsx from 'clsx'; import { Popup } from 'ui/component'; +import { NoActionAlert } from '../NoActionAlert/NoActionAlert'; const { TabPane } = Tabs; @@ -87,24 +88,6 @@ export const ActionWrapper = styled.div` } `; -const NoActionAlert = styled.div` - display: flex; - align-items: flex-start; - background: #e8eaf3; - border-radius: 6px; - padding: 15px; - font-weight: 500; - font-size: 13px; - line-height: 18px; - color: #333333; - margin-bottom: 15px; - .icon-alert { - margin-right: 8px; - width: 15px; - margin-top: 1px; - } -`; - const MessageWrapper = styled.div` .title { position: relative; @@ -161,11 +144,13 @@ const Actions = ({ engineResults, raw, message, + origin, }: { data: TextActionData | null; engineResults: Result[]; raw: string; message: string; + origin: string; }) => { const actionName = useMemo(() => { if (!data) return ''; @@ -223,10 +208,12 @@ const Actions = ({ )} {!data && ( - - - {t('page.signTx.sigCantDecode')} - + )} ; message: string; + origin: string; }) => { const { t } = useTranslation(); @@ -327,11 +312,12 @@ const Actions = ({ )} {(!data || !data.actionType) && ( - - - This signature can't be decoded by Rabby, but it doesn't imply any - risk - + )}