diff --git a/_raw/locales/en/messages.json b/_raw/locales/en/messages.json index bc984655fb7..3f8467dd8e6 100644 --- a/_raw/locales/en/messages.json +++ b/_raw/locales/en/messages.json @@ -2546,6 +2546,11 @@ "fast": "Fast", "normal": "Normal", "doNotReserve": "Don't reserve Gas" + }, + "OpenExternalWebsiteModal": { + "title": "You're Leaving Rabby Wallet", + "content": "You're about to visit an external website. Rabby Wallet is not responsible for the content or security of this site.", + "button": "Continue" } }, "global": { diff --git a/package.json b/package.json index cedb4ccb0be..129c14f8bc6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rabby", - "version": "0.92.99", + "version": "0.92.100", "description": "A browser plugin for DeFi users", "scripts": { "clean": "mkdir -p dist && rm -rf dist/*", @@ -60,7 +60,7 @@ "@rabby-wallet/eth-watch-keyring": "1.0.0", "@rabby-wallet/gnosis-sdk": "1.3.9", "@rabby-wallet/page-provider": "0.4.2", - "@rabby-wallet/rabby-action": "0.1.4", + "@rabby-wallet/rabby-action": "0.1.8", "@rabby-wallet/rabby-api": "0.8.5", "@rabby-wallet/rabby-security-engine": "2.0.7", "@rabby-wallet/rabby-swap": "0.0.42", diff --git a/patches/@ledgerhq+hw-app-eth+6.39.0.patch b/patches/@ledgerhq+hw-app-eth+6.39.0.patch new file mode 100644 index 00000000000..8dbf7674b02 --- /dev/null +++ b/patches/@ledgerhq+hw-app-eth+6.39.0.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/contracts.js b/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/contracts.js +index 806a539..b488b25 100644 +--- a/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/contracts.js ++++ b/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/contracts.js +@@ -19,7 +19,7 @@ export const loadInfosForContractMethod = (contractAddress, selector, chainId, u + if (pluginBaseURL) { + const url = `${pluginBaseURL}/plugins/ethereum.json`; + data = yield axios +- .get(`${pluginBaseURL}/plugins/ethereum.json`) ++ .get(`${pluginBaseURL}/plugins/ethereum.json`, { timeout: 5000 }) + .then(r => r.data) + .catch(e => { + log("error", "could not fetch from " + url + ": " + String(e)); +diff --git a/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/erc20.js b/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/erc20.js +index 16a933e..a2a0f1a 100644 +--- a/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/erc20.js ++++ b/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/erc20.js +@@ -21,7 +21,7 @@ export const findERC20SignaturesInfo = (userLoadConfig, chainId) => __awaiter(vo + return null; + const url = `${cryptoassetsBaseURL}/evm/${chainId}/erc20-signatures.json`; + const blob = yield axios +- .get(url) ++ .get(url, { timeout: 5000 }) + .then(({ data }) => { + if (!data || typeof data !== "string") { + throw new Error(`ERC20 signatures for chainId ${chainId} file is malformed ${url}`); diff --git a/patches/@metamask+eth-sig-util+5.1.0.patch b/patches/@metamask+eth-sig-util+5.1.0.patch index 6e24543cc59..38062ce9957 100644 --- a/patches/@metamask+eth-sig-util+5.1.0.patch +++ b/patches/@metamask+eth-sig-util+5.1.0.patch @@ -1,3 +1,25 @@ +diff --git a/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.d.ts b/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.d.ts +index 45a660e..9b3f105 100644 +--- a/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.d.ts ++++ b/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.d.ts +@@ -20,3 +20,5 @@ export declare function parseNumber(arg: string | number | BN): BN; + * @param values + */ + export declare function rawEncode(types: string[], values: (BN | Buffer | string | number | string[] | number[])[]): Buffer; ++ ++export declare function encodeSingle(type: string, arg: BN | Buffer | string | number | string[] | number[]): Buffer; +diff --git a/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.js b/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.js +index 1a11fec..7c78143 100644 +--- a/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.js ++++ b/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.js +@@ -352,6 +352,7 @@ function encodeSingle(type, arg) { + } + throw new Error(`Unsupported or invalid type: ${JSON.stringify(type)}`); + } ++exports.encodeSingle = encodeSingle; + // Is a type dynamic? + /** + * @param type diff --git a/node_modules/@metamask/eth-sig-util/dist/index.d.ts b/node_modules/@metamask/eth-sig-util/dist/index.d.ts index 48ddf59..58c9ae7 100644 --- a/node_modules/@metamask/eth-sig-util/dist/index.d.ts diff --git a/src/background/service/keyring/eth-ledger-keyring.ts b/src/background/service/keyring/eth-ledger-keyring.ts index c7d4b178e3c..cffc3f56959 100644 --- a/src/background/service/keyring/eth-ledger-keyring.ts +++ b/src/background/service/keyring/eth-ledger-keyring.ts @@ -15,6 +15,12 @@ import { SignHelper, LedgerHDPathType } from './helper'; const type = 'Ledger Hardware'; import HDPathType = LedgerHDPathType; +import Browser from 'webextension-polyfill'; +import { + LedgerAction, + OffscreenCommunicationTarget, +} from '@/constant/offscreen-communication'; +import { isManifestV3 } from '@/utils/env'; const HD_PATH_BASE = { [HDPathType.BIP44]: "m/44'/60'/0'/0", @@ -70,6 +76,17 @@ class LedgerBridgeKeyring { this.app = null; this.usedHDPathTypeList = {}; this.deserialize(opts); + + if (isManifestV3) { + Browser.runtime.onMessage.addListener((request) => { + if ( + request.target === OffscreenCommunicationTarget.extension && + request.event === LedgerAction.ledgerDeviceDisconnect + ) { + this.cleanUp(); + } + }); + } } serialize() { @@ -145,7 +162,9 @@ class LedgerBridgeKeyring { async cleanUp() { this.app = null; - if (this.transport) this.transport.close(); + if (this.transport) { + await this.transport.close(); + } this.transport = null; } diff --git a/src/constant/offscreen-communication.ts b/src/constant/offscreen-communication.ts index ac0f369fdd1..03ae7bcc7df 100644 --- a/src/constant/offscreen-communication.ts +++ b/src/constant/offscreen-communication.ts @@ -49,3 +49,7 @@ export enum LatticeAction { export enum KnownOrigins { lattice = 'https://lattice.gridplus.io', } + +export enum LedgerAction { + ledgerDeviceDisconnect = 'ledger-device-disconnect', +} diff --git a/src/manifest/mv2/manifest.json b/src/manifest/mv2/manifest.json index 464958690bd..cc7f9aefaaf 100644 --- a/src/manifest/mv2/manifest.json +++ b/src/manifest/mv2/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "0.92.99", + "version": "0.92.100", "default_locale": "en", "description": "__MSG_appDescription__", "icons": { diff --git a/src/manifest/mv3/manifest.json b/src/manifest/mv3/manifest.json index 61e581f918f..5292b86322c 100644 --- a/src/manifest/mv3/manifest.json +++ b/src/manifest/mv3/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "0.92.99", + "version": "0.92.100", "default_locale": "en", "description": "__MSG_appDescription__", "icons": { diff --git a/src/offscreen/scripts/ledger.ts b/src/offscreen/scripts/ledger.ts new file mode 100644 index 00000000000..54f5f01b813 --- /dev/null +++ b/src/offscreen/scripts/ledger.ts @@ -0,0 +1,17 @@ +import { + LedgerAction, + OffscreenCommunicationTarget, +} from '@/constant/offscreen-communication'; +import { ledgerUSBVendorId } from '@ledgerhq/devices'; +import Browser from 'webextension-polyfill'; + +export function initLedger() { + navigator.hid.addEventListener('disconnect', ({ device }) => { + if (device.vendorId === ledgerUSBVendorId) { + Browser.runtime.sendMessage({ + target: OffscreenCommunicationTarget.extension, + event: LedgerAction.ledgerDeviceDisconnect, + }); + } + }); +} diff --git a/src/offscreen/scripts/offscreen.ts b/src/offscreen/scripts/offscreen.ts index f846b4a4885..3b6b7cc27fe 100644 --- a/src/offscreen/scripts/offscreen.ts +++ b/src/offscreen/scripts/offscreen.ts @@ -1,12 +1,14 @@ import { initBitBox02 } from './bitbox02'; import { initImKey } from './imkey'; import initLattice from './lattice'; +import { initLedger } from './ledger'; import { initOneKey } from './onekey'; initImKey(); initOneKey(); initBitBox02(); initLattice(); +initLedger(); // keep alive when ui page is open let pageCount = 0; diff --git a/src/ui/assets/component/external-link-alert.svg b/src/ui/assets/component/external-link-alert.svg new file mode 100644 index 00000000000..5efb37d4344 --- /dev/null +++ b/src/ui/assets/component/external-link-alert.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/component/Ecology/EcologyNavBar.tsx b/src/ui/component/Ecology/EcologyNavBar.tsx index 703bf680af8..6695afcf490 100644 --- a/src/ui/component/Ecology/EcologyNavBar.tsx +++ b/src/ui/component/Ecology/EcologyNavBar.tsx @@ -1,23 +1,22 @@ -import { findChain } from '@/utils/chain'; import clsx from 'clsx'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { ReactComponent as RcIconBack } from 'ui/assets/icon-back-cc.svg'; +import { EcoChains } from 'ui/views/Ecology/constants'; interface Props { className?: string; style?: React.CSSProperties; chainId: number; } + +const findChain = (id: number) => { + return EcoChains.find((chain) => chain.id === id); +}; + export const EcologyNavBar = ({ className, style, chainId }: Props) => { - const chain = useMemo( - () => - findChain({ - id: chainId, - }), - [chainId] - ); + const chain = useMemo(() => findChain(chainId), [chainId]); const history = useHistory(); const { t } = useTranslation(); diff --git a/src/ui/style/index.less b/src/ui/style/index.less index 9c59a8b3f60..af9c3e9b038 100644 --- a/src/ui/style/index.less +++ b/src/ui/style/index.less @@ -423,3 +423,19 @@ button:focus { background-clip: text; color: transparent; } + +.external-link-alert-modal { + background: var(--r-neutral-bg-1); + border-radius: 16px; + overflow: hidden; + padding-bottom: 0; + .ant-modal-confirm-content { + padding: 0; + } + .ant-modal-confirm-content, .ant-modal-content { + background: var(--r-neutral-bg-1); + } + .ant-modal-content { + box-shadow: none; + } +} \ No newline at end of file diff --git a/src/ui/utils/webapi.ts b/src/ui/utils/webapi.ts deleted file mode 100644 index 34cb1733902..00000000000 --- a/src/ui/utils/webapi.ts +++ /dev/null @@ -1,55 +0,0 @@ -import browser, { Tabs, Windows } from 'webextension-polyfill'; -import { WalletController, WalletControllerType } from './index'; -import { getOriginFromUrl } from '@/utils'; - -export const getCurrentTab = async (): Promise => { - const tabs = await browser.tabs.query({ active: true, currentWindow: true }); - - return tabs[0]; -}; - -export const getCurrentConnectSite = async ( - wallet: WalletController | WalletControllerType -) => { - const { id, url } = await getCurrentTab(); - if (!id || !url) return null; - - return (wallet as WalletControllerType).getCurrentConnectedSite( - id, - getOriginFromUrl(url) - ); -}; - -export const openInTab = async ( - url?: string, - needClose = true -): Promise => { - const tab = await browser.tabs.create({ - active: true, - url, - }); - - if (needClose) window.close(); - - return tab; -}; - -export const getCurrentWindow = async (): Promise => { - const { id } = await browser.windows.getCurrent({ - windowTypes: ['popup'], - } as Windows.GetInfo); - - return id; -}; - -export const openInternalPageInTab = ( - path: string, - useWebapi = true, - needClose = true -) => { - if (useWebapi) { - openInTab(`./index.html#/${path}`, needClose); - } else { - window.open(`./index.html#/${path}`); - } -}; diff --git a/src/ui/utils/webapi.tsx b/src/ui/utils/webapi.tsx new file mode 100644 index 00000000000..0edaf29cd17 --- /dev/null +++ b/src/ui/utils/webapi.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import browser, { Tabs, Windows } from 'webextension-polyfill'; +import { t } from 'i18next'; +import { Button } from 'antd'; +import { WalletController, WalletControllerType } from './index'; +import { getOriginFromUrl } from '@/utils'; +import Modal from '../component/Modal'; +import { ReactComponent as ExternalLinkAlert } from 'ui/assets/component/external-link-alert.svg'; + +export const getCurrentTab = async (): Promise => { + const tabs = await browser.tabs.query({ active: true, currentWindow: true }); + + return tabs[0]; +}; + +export const getCurrentConnectSite = async ( + wallet: WalletController | WalletControllerType +) => { + const { id, url } = await getCurrentTab(); + if (!id || !url) return null; + + return (wallet as WalletControllerType).getCurrentConnectedSite( + id, + getOriginFromUrl(url) + ); +}; + +export const openInTab = async ( + url?: string, + needClose = true +): Promise => { + const tab = await browser.tabs.create({ + active: true, + url, + }); + + if (needClose) window.close(); + + return tab; +}; + +export const getCurrentWindow = async (): Promise => { + const { id } = await browser.windows.getCurrent({ + windowTypes: ['popup'], + } as Windows.GetInfo); + + return id; +}; + +export const openInternalPageInTab = ( + path: string, + useWebapi = true, + needClose = true +) => { + if (useWebapi) { + openInTab(`./index.html#/${path}`, needClose); + } else { + window.open(`./index.html#/${path}`); + } +}; + +export const openExternalWebsiteInTab = async ( + url?: string, + needClose = true +) => { + return new Promise((resolve, reject) => { + const modal = Modal.info({ + closable: true, + className: 'external-link-alert-modal', + content: ( + + + + + + {t('component.OpenExternalWebsiteModal.title')} + + + {t('component.OpenExternalWebsiteModal.content')} + + + { + const tab = await browser.tabs.create({ + active: true, + url, + }); + if (needClose) window.close(); + resolve(tab); + modal.destroy(); + }} + > + {t('component.OpenExternalWebsiteModal.button')} + + + + ), + onCancel() { + reject('user cancel'); + }, + }); + }); +}; diff --git a/src/ui/views/AddressBackup/Mnemonics.tsx b/src/ui/views/AddressBackup/Mnemonics.tsx index 32a8b92d1f7..9931ee94315 100644 --- a/src/ui/views/AddressBackup/Mnemonics.tsx +++ b/src/ui/views/AddressBackup/Mnemonics.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useWallet } from 'ui/utils'; import './style.less'; import { InfoCircleOutlined } from '@ant-design/icons'; import { Button } from 'antd'; @@ -20,7 +19,6 @@ import QRCode from 'qrcode.react'; import { ReactComponent as RcIconQrCode } from 'ui/assets/qrcode-cc.svg'; const AddressBackup = () => { - const wallet = useWallet(); const { t } = useTranslation(); const history = useHistory(); @@ -91,11 +89,11 @@ const AddressBackup = () => { )} {t('page.backupSeedPhrase.title')} - + {t('page.backupSeedPhrase.alert')} - + setMasked(false)} @@ -106,6 +104,34 @@ const AddressBackup = () => { {t('page.backupSeedPhrase.clickToShow')} + + + + {t('page.backupSeedPhrase.showQrCode')} + + + + {t('page.backupSeedPhrase.copySeedPhrase')} + + { )} - - - - {t('page.backupSeedPhrase.showQrCode')} - - - - {t('page.backupSeedPhrase.copySeedPhrase')} - - - + { return [ { ...tx, - nonce: realNonce, + nonce: realNonce || tx.nonce, gasPrice: tx.gasPrice || tx.maxFeePerGas, gas: gasLimit, }, diff --git a/src/ui/views/Approval/components/TypedDataActions/utils.ts b/src/ui/views/Approval/components/TypedDataActions/utils.ts index 6aa0cfcbdbd..b2f912355d4 100644 --- a/src/ui/views/Approval/components/TypedDataActions/utils.ts +++ b/src/ui/views/Approval/components/TypedDataActions/utils.ts @@ -1,11 +1,11 @@ -import { getArrayType, isArrayType } from '@metamask/abi-utils/dist/parsers'; -import { BigNumber as EthersBigNumber } from 'ethers'; -import { isStrictHexString, add0x } from 'ui/utils/address'; import i18n from '@/i18n'; -import { parseNumber } from '@metamask/eth-sig-util'; -import { padStart } from 'lodash'; import { ParsedTypedDataActionData } from '@rabby-wallet/rabby-action'; import { getActionTypeText as getTransactionActionTypeText } from '../Actions/utils'; +import { encodeSingle } from '@metamask/eth-sig-util'; +import { bufferToHex } from 'ethereumjs-util'; +import { hexToString } from 'web3-utils'; +import BigNumber from 'bignumber.js'; + export const getActionTypeText = (data: ParsedTypedDataActionData | null) => { const { t } = i18n; @@ -74,47 +74,80 @@ export function normalizeTypeData(data: { message: Record; }) { try { - const { types, primaryType, domain, message } = data; - const domainTypes = types.EIP712Domain; - const messageTypes = types[primaryType]; - domainTypes.forEach((item) => { - const { name, type } = item; - domain[name] = normalizeValue(type, domain[name]); - }); - messageTypes.forEach((item) => { - const { name, type } = item; - message[name] = normalizeValue(type, message[name]); - }); - return { types, primaryType, domain, message }; + return parseSignTypedData(data); } catch (e) { return data; } } +function parseSignTypedData(typedData: { + primaryType: string; + types: Record; + domain: Record; + message: Record; +}) { + const { domain, message, types, primaryType } = typedData; -export function normalizeValue(type: string, value: unknown): any { - if (isArrayType(type) && Array.isArray(value)) { - const [innerType] = getArrayType(type); - return value.map((item) => normalizeValue(innerType, item)); - } + function parseAndDecode(data: any, dataType: string) { + if (dataType.endsWith('[]')) { + const elementType = dataType.slice(0, -2); + const decodedArray: any[] = []; + for (const element of data) { + decodedArray.push(parseAndDecode(element, elementType)); + } + return decodedArray; + } else if (types[dataType]) { + for (const field of types[dataType]) { + const { name, type } = field; + data[name] = parseAndDecode(data[name], type); + } + return data; + } else { + const encodedBuffer = encodeSingle(dataType, data); + let encodedHexValue = `0x${encodedBuffer.toString('hex')}`; + switch (dataType) { + case 'string': { + const encodedLengthSize = 32; // uint256 length + const lengthBuffer = encodedBuffer.slice(0, encodedLengthSize); + const originalArgLength = parseInt(lengthBuffer.toString('hex'), 16); + const fullArgWithPadding = encodedBuffer.slice(encodedLengthSize); + const originalArg = fullArgWithPadding.slice(0, originalArgLength); + encodedHexValue = bufferToHex(originalArg); + break; + } + case 'address': + encodedHexValue = `0x${encodedBuffer.slice(12).toString('hex')}`; + break; + case 'bool': + if (new BigNumber(encodedHexValue).eq(0)) { + return false; + } else if (new BigNumber(encodedHexValue).eq(1)) { + return true; + } + break; + default: + // NOTHING + } + if ( + dataType.startsWith('uint') || + dataType.startsWith('int') || + dataType.startsWith('ufixed') || + dataType.startsWith('fixed') + ) { + return new BigNumber(encodedHexValue).toFixed(); + } + if (dataType === 'string') { + return hexToString(encodedHexValue); + } - if (type === 'address') { - let address = value as string; - if (typeof value === 'string' && !/^(0x|0X)/.test(value)) { - address = EthersBigNumber.from(value).toHexString(); - } else if (isStrictHexString(value)) { - address = add0x(value); - } - try { - const parseAddress = padStart( - parseNumber(address).toString('hex'), - 40, - '0' - ); - return `0x${parseAddress}`; - } catch (e) { - return address; + return encodedHexValue; } } - return value; + for (const { name, type } of types.EIP712Domain) { + domain[name] = parseAndDecode(domain[name], type); + } + + typedData.message = parseAndDecode(message, primaryType); + + return typedData; } diff --git a/src/ui/views/Dashboard/components/EcologyPopup/index.tsx b/src/ui/views/Dashboard/components/EcologyPopup/index.tsx index 372602b89d5..a8262727ab3 100644 --- a/src/ui/views/Dashboard/components/EcologyPopup/index.tsx +++ b/src/ui/views/Dashboard/components/EcologyPopup/index.tsx @@ -1,13 +1,11 @@ -import { DBK_CHAIN_ID } from '@/constant'; import { Popup } from '@/ui/component'; import ThemeIcon from '@/ui/component/ThemeMode/ThemeIcon'; -import { findChain } from '@/utils/chain'; -import { Chain } from '@debank/common'; import clsx from 'clsx'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { ReactComponent as RcIconArrowRight } from 'ui/assets/dashboard/settings/icon-right-arrow.svg'; +import { EcoChains } from 'ui/views/Ecology/constants'; interface Props { visible?: boolean; @@ -16,13 +14,6 @@ interface Props { export const EcologyPopup = ({ visible, onClose }: Props) => { const { t } = useTranslation(); const history = useHistory(); - const chainList = [DBK_CHAIN_ID] - .map((chainId) => { - return findChain({ - id: chainId, - }); - }) - .filter((chain): chain is Chain => !!chain); return ( { push={false} title={t('page.dashboard.echologyPopup.title')} > - {chainList.map((item) => { + {EcoChains.map((item) => { return ( { const { address } = account!; + if (startTime) { + await sleep(500); + } const apiLevel = await wallet.getAPIConfig([], 'ApiLevel', false); if (apiLevel >= 1) { return { @@ -156,10 +159,11 @@ export const HistoryList = ({ ); }} endReached={loadMore} + increaseViewportBy={100} components={{ Footer: () => { if (loadingMore) { - return ; + return ; } return null; }, diff --git a/yarn.lock b/yarn.lock index dc66542e19e..e7476d6c0d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1610,16 +1610,11 @@ resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f" integrity sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ== -"@debank/common@0.3.60": +"@debank/common@0.3.60", "@debank/common@^0.3.0": version "0.3.60" resolved "https://registry.yarnpkg.com/@debank/common/-/common-0.3.60.tgz#17ca72e4cde1fb6487ca1ed0691d89cd6b6692f9" integrity sha512-SaFTXvnn2aYQTPB3B8YD/AhHYwdMRiqu7jCkYV6c/Sd3MmxzJR3v+Kd4jlQniKfEdB21+1vMb0NlzHw/gyemlw== -"@debank/common@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@debank/common/-/common-0.3.0.tgz#9c67288816d62e3548e0c2193ec4cd73758f5b26" - integrity sha512-PWdFuKuqDtGdYALFToDQQCyas3Tgz1tXXlir6UVTVHz2MPi4uK6ZK8UUNrWdU6lncHNvcRsNf3Xamk0Ga9jxeg== - "@debank/festats@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@debank/festats/-/festats-1.0.1.tgz#8f76b0919a6c8a7d938a42946272abaff2b971be" @@ -4776,10 +4771,10 @@ sinon-chrome "^3.0.1" webextension-polyfill "0.7.0" -"@rabby-wallet/rabby-action@0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-action/-/rabby-action-0.1.4.tgz#c82e7c8b538b7dfd94506c4f89f78aca7ca880ef" - integrity sha512-6ttnlpGHcO2v/qyYo8epBbSutfS9OZXfXr9mfapuveoBvzqUwvE6ej3bsmdtc+qFhFeH7HeiwASpY6xaTM4E1w== +"@rabby-wallet/rabby-action@0.1.8": + version "0.1.8" + 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.8.5": version "0.8.5"
+ {t('component.OpenExternalWebsiteModal.content')} +