diff --git a/_raw/_locales/en/messages.json b/_raw/_locales/en/messages.json index 227f0b62a48..be7e4def823 100644 --- a/_raw/_locales/en/messages.json +++ b/_raw/_locales/en/messages.json @@ -919,6 +919,24 @@ "QR__code": { "message": "QR code" }, + "Get__Signature": { + "message": "Get Signature" + }, + "KesytoneMismatchedSignId": { + "message": "Incongruent transaction data. Please check the transaction details." + }, + "KeystoneSignRequestSubtitle": { + "message": "Scan the QR code on the Keystone hardware wallet" + }, + "unknownQrCode": { + "message": "Error: We couldn't identify that QR code" + }, + "KeystoneUnknownWalletQRCode": { + "message": "Invalid QR code. Please scan the sync QR code of the hardware wallet." + }, + "KeystoneSignRequestDescription": { + "message": "After you’ve signed with your wallet, click on 'Get Signature' to receive the signature" + }, "URL": { "message": "URL" }, diff --git a/_raw/_locales/zh_CN/messages.json b/_raw/_locales/zh_CN/messages.json index 845f651d6d9..e15fd2daf1e 100644 --- a/_raw/_locales/zh_CN/messages.json +++ b/_raw/_locales/zh_CN/messages.json @@ -915,6 +915,24 @@ "QR__code": { "message": "二维码" }, + "Get__Signature": { + "message": "获取签名" + }, + "KesytoneMismatchedSignId": { + "message": "扫描的签名二维码不属于当前交易,请检查交易详情后重试。" + }, + "KeystoneSignRequestSubtitle": { + "message": "用硬件钱包扫描二维码" + }, + "unknownQrCode": { + "message": "错误:无法识别该二维码" + }, + "KeystoneUnknownWalletQRCode": { + "message": "请扫描硬件钱包的同步二维码。" + }, + "KeystoneSignRequestDescription": { + "message": "硬件钱包扫描上方二维码完成签名后,点击“获取签名”按钮扫描已签名的二维码" + }, "URL": { "message": "链接" }, diff --git a/package.json b/package.json index 718eb1a42ee..07fa31f3a19 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,10 @@ "@rabby-wallet/eth-simple-keyring": "^4.2.1", "@rabby-wallet/eth-trezor-keyring": "^1.0.12", "@rabby-wallet/eth-walletconnect-keyring": "^1.1.0", + "@keystonehq/metamask-airgapped-keyring": "^0.2.5-alpha.0", + "@zxing/browser": "^0.0.10", + "@ngraveio/bc-ur": "^1.1.6", + "uuid": "^8.3.2", "@rabby-wallet/eth-watch-keyring": "^1.0.0", "@rabby-wallet/gnosis-sdk": "^1.2.0", "@rabby-wallet/widgets": "^1.0.8", diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index d4a12fde0ca..cd36771ff41 100644 --- a/src/background/controller/wallet.ts +++ b/src/background/controller/wallet.ts @@ -22,10 +22,7 @@ import { ContactBookItem } from '../service/contactBook'; import { openIndexPage } from 'background/webapi/tab'; import { CacheState } from 'background/service/pageStateCache'; import i18n from 'background/service/i18n'; -import keyring, { - KEYRING_CLASS, - DisplayedKeryring, -} from 'background/service/keyring'; +import { KEYRING_CLASS, DisplayedKeryring } from 'background/service/keyring'; import providerController from './provider/controller'; import BaseController from './base'; import { @@ -54,6 +51,10 @@ import GnosisKeyring, { TransactionBuiltEvent, TransactionConfirmedEvent, } from '../service/keyring/eth-gnosis-keyring'; +import KeystoneKeyring, { + AcquireMemeStoreData, + MemStoreDataReady, +} from '../service/keyring/eth-keystone-keyring'; const stashKeyrings: Record = {}; @@ -973,6 +974,69 @@ export class WalletController extends BaseController { return stashKeyringId; }; + acquireKeystoneMemStoreData = async () => { + const keyringType = KEYRING_CLASS.QRCODE; + const keyring: KeystoneKeyring = this._getKeyringByType(keyringType); + if (keyring) { + keyring.getInteraction().on(MemStoreDataReady, (request) => { + eventBus.emit(EVENTS.broadcastToUI, { + method: EVENTS.QRHARDWARE.ACQUIRE_MEMSTORE_SUCCEED, + params: { + request, + }, + }); + }); + keyring.getInteraction().emit(AcquireMemeStoreData); + } + }; + + submitQRHardwareCryptoHDKey = async (cbor: string) => { + let keyring; + let stashKeyringId: number | null = null; + const keyringType = KEYRING_CLASS.QRCODE; + try { + keyring = this._getKeyringByType(keyringType); + } catch { + const keystoneKeyring = keyringService.getKeyringClassForType( + keyringType + ); + keyring = new keystoneKeyring(); + stashKeyringId = Object.values(stashKeyrings).length; + stashKeyrings[stashKeyringId] = keyring; + } + keyring.readKeyring(); + await keyring.submitCryptoHDKey(cbor); + return stashKeyringId; + }; + + submitQRHardwareCryptoAccount = async (cbor: string) => { + let keyring; + let stashKeyringId: number | null = null; + const keyringType = KEYRING_CLASS.QRCODE; + try { + keyring = this._getKeyringByType(keyringType); + } catch { + const keystoneKeyring = keyringService.getKeyringClassForType( + keyringType + ); + keyring = new keystoneKeyring(); + stashKeyringId = Object.values(stashKeyrings).length; + stashKeyrings[stashKeyringId] = keyring; + } + keyring.readKeyring(); + await keyring.submitCryptoAccount(cbor); + return stashKeyringId; + }; + + submitQRHardwareSignature = async (requestId: string, cbor: string) => { + const account = await preferenceService.getCurrentAccount(); + const keyring = await keyringService.getKeyringForAccount( + account!.address, + KEYRING_CLASS.QRCODE + ); + return await keyring.submitSignature(requestId, cbor); + }; + signPersonalMessage = async ( type: string, from: string, @@ -1004,7 +1068,7 @@ export class WalletController extends BaseController { options?: any ) => { const keyring = await keyringService.getKeyringForAccount(from, type); - return keyringService.signTransaction(keyring, data, options); + return keyringService.signTransaction(keyring, data, from, options); }; requestKeyring = (type, methodName, keyringId: number | null, ...params) => { diff --git a/src/background/service/keyring/eth-keystone-keyring.ts b/src/background/service/keyring/eth-keystone-keyring.ts new file mode 100644 index 00000000000..1494a64d515 --- /dev/null +++ b/src/background/service/keyring/eth-keystone-keyring.ts @@ -0,0 +1,64 @@ +import { MetaMaskKeyring } from '@keystonehq/metamask-airgapped-keyring'; +import { toChecksumAddress } from 'ethereumjs-util'; +import { Transaction as LegacyTransaction } from 'ethereumjs-tx'; +import { Transaction } from '@ethereumjs/tx'; + +const pathBase = 'm'; + +export const AcquireMemeStoreData = 'AcquireMemeStoreData'; +export const MemStoreDataReady = 'MemStoreDataReady'; + +export type RequestSignPayload = { + requestId: string; + payload: { + type: string; + cbor: string; + }; +}; + +export default class KeystoneKeyring extends MetaMaskKeyring { + perPage = 10; + memStoreData: RequestSignPayload | undefined; + constructor() { + super(); + this.getMemStore().subscribe((data) => { + const request = data.sign?.request; + this.getInteraction().once(AcquireMemeStoreData, () => { + if (request) { + this.getInteraction().emit(MemStoreDataReady, request); + } + }); + }); + } + + async getAddresses(start: number, end: number) { + if (!this.initialized) { + await this.readKeyring(); + } + const accounts: { + address: string; + balance: number | null; + index: number; + }[] = []; + for (let i = start; i < end; i++) { + const address = await this.__addressFromIndex(pathBase, i); + accounts.push({ + address, + balance: null, + index: i, + }); + this.indexes[toChecksumAddress(address)] = i; + } + return accounts; + } + + async signTransaction(address: string, tx: any): Promise { + let ethTx = tx; + if (tx.type === undefined) { + ethTx = Transaction.fromSerializedTx( + (tx as LegacyTransaction).serialize() + ); + } + return super.signTransaction(address, ethTx); + } +} diff --git a/src/background/service/keyring/index.ts b/src/background/service/keyring/index.ts index 26a2723d9bb..4fe80fe0c54 100644 --- a/src/background/service/keyring/index.ts +++ b/src/background/service/keyring/index.ts @@ -19,6 +19,7 @@ import TrezorKeyring from '@rabby-wallet/eth-trezor-keyring'; import OnekeyKeyring from './eth-onekey-keyring'; import LatticeKeyring from '@rabby-wallet/eth-lattice-keyring'; import WatchKeyring from '@rabby-wallet/eth-watch-keyring'; +import KeystoneKeyring from './eth-keystone-keyring'; import WalletConnectKeyring from '@rabby-wallet/eth-walletconnect-keyring'; import GnosisKeyring, { TransactionBuiltEvent, @@ -42,6 +43,7 @@ export const KEYRING_SDK_TYPES = { WalletConnectKeyring, GnosisKeyring, LatticeKeyring, + KeystoneKeyring, }; export const KEYRING_CLASS = { @@ -57,6 +59,7 @@ export const KEYRING_CLASS = { WATCH: WatchKeyring.type, WALLETCONNECT: WalletConnectKeyring.type, GNOSIS: GnosisKeyring.type, + QRCODE: KeystoneKeyring.type, }; interface MemStoreState { diff --git a/src/constant/index.ts b/src/constant/index.ts index 498c38f22e7..70703dc17c8 100644 --- a/src/constant/index.ts +++ b/src/constant/index.ts @@ -103,6 +103,8 @@ import IconMnemonicWhite from 'ui/assets/walletlogo/IconMnemonic-white.svg'; import LogoLedgerDark from 'ui/assets/walletlogo/ledgerdark.png'; import LogoLedgerWhite from 'ui/assets/walletlogo/ledgerwhite.png'; import IconGridPlus from 'ui/assets/walletlogo/gridplus.png'; +import LogoKeystone from 'ui/assets/walletlogo/keystone.png'; +import LogoKeystoneWithBorder from 'ui/assets/walletlogo/keystone-border.png'; export enum CHAINS_ENUM { ETH = 'ETH', @@ -592,6 +594,7 @@ export const KEYRING_CLASS = { LEDGER: 'Ledger Hardware', ONEKEY: 'Onekey Hardware', GRIDPLUS: 'GridPlus Hardware', + KEYSTONE: 'QR Hardware Wallet Device', }, WATCH: 'Watch Address', WALLETCONNECT: 'WalletConnect', @@ -608,6 +611,7 @@ export const SUPPORT_1559_KEYRING_TYPE = [ KEYRING_CLASS.HARDWARE.GRIDPLUS, KEYRING_CLASS.PRIVATE_KEY, KEYRING_CLASS.MNEMONIC, + KEYRING_CLASS.HARDWARE.KEYSTONE, ]; export const KEYRING_TYPE_TEXT = { @@ -631,6 +635,7 @@ export const BRAND_ALIAN_TYPE_TEXT = { [KEYRING_CLASS.HARDWARE.BITBOX02]: 'BitBox02', [KEYRING_CLASS.GNOSIS]: 'Gnosis', [KEYRING_CLASS.HARDWARE.GRIDPLUS]: 'GridPlus', + [KEYRING_CLASS.HARDWARE.KEYSTONE]: 'Keystone', }; export const HARDWARE_KEYRING_TYPES = { BitBox02: { @@ -653,6 +658,10 @@ export const HARDWARE_KEYRING_TYPES = { type: 'GridPlus Hardware', brandName: 'GridPlus', }, + Keystone: { + type: 'QR Hardware Wallet Device', + brandName: 'Keystone', + }, }; export enum TX_TYPE_ENUM { @@ -772,6 +781,7 @@ export enum BRAND_WALLET_CONNECT_TYPE { TrezorConnect = 'TrezorConnect', GnosisConnect = 'GnosisConnect', GridPlusConnect = 'GridPlusConnect', + QRCodeBase = 'QR Hardware Wallet Device', } export const WALLETCONNECT_STATUS_MAP = { @@ -806,6 +816,9 @@ export const EVENTS = { TX_BUILT: 'TransactionBuilt', TX_CONFIRMED: 'TransactionConfirmed', }, + QRHARDWARE: { + ACQUIRE_MEMSTORE_SUCCEED: 'ACQUIRE_MEMSTORE_SUCCEED', + }, }; export enum WALLET_BRAND_TYPES { @@ -824,6 +837,7 @@ export enum WALLET_BRAND_TYPES { GNOSIS = 'Gnosis', GRIDPLUS = 'GRIDPLUS', METAMASK = 'MetaMask', + KEYSTONE = 'Keystone', } export const WALLET_BRAND_CONTENT = { @@ -891,6 +905,14 @@ export const WALLET_BRAND_CONTENT = { image: LogoJade, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, }, + [WALLET_BRAND_TYPES.KEYSTONE]: { + id: 15, + name: 'Keystone', + brand: WALLET_BRAND_TYPES.KEYSTONE, + icon: LogoKeystone, + image: LogoKeystone, + connectType: BRAND_WALLET_CONNECT_TYPE.QRCodeBase, + }, [WALLET_BRAND_TYPES.LEDGER]: { id: 4, name: 'Ledger', @@ -958,6 +980,7 @@ export const KEYRING_ICONS = { [HARDWARE_KEYRING_TYPES.Onekey.type]: LogoOnekey, [HARDWARE_KEYRING_TYPES.Trezor.type]: IconTrezor24, [HARDWARE_KEYRING_TYPES.GridPlus.type]: IconGridPlus, + [HARDWARE_KEYRING_TYPES.Keystone.type]: LogoKeystone, }; export const KEYRING_ICONS_WHITE = { @@ -969,6 +992,7 @@ export const KEYRING_ICONS_WHITE = { [HARDWARE_KEYRING_TYPES.Onekey.type]: LogoOnekey, [HARDWARE_KEYRING_TYPES.Trezor.type]: IconTrezor24, [HARDWARE_KEYRING_TYPES.GridPlus.type]: IconGridPlus, + [HARDWARE_KEYRING_TYPES.Keystone.type]: LogoKeystone, }; export const KEYRING_PURPLE_LOGOS = { [KEYRING_CLASS.MNEMONIC]: IconMnemonicPurple, @@ -985,6 +1009,7 @@ export const KEYRINGS_LOGOS = { [HARDWARE_KEYRING_TYPES.Onekey.type]: IconOneKey18, [HARDWARE_KEYRING_TYPES.Trezor.type]: IconTrezor24Border, [HARDWARE_KEYRING_TYPES.GridPlus.type]: IconGridPlus, + [HARDWARE_KEYRING_TYPES.Keystone.type]: LogoKeystone, }; export const NOT_CLOSE_UNFOCUS_LIST: string[] = [ diff --git a/src/ui/assets/walletlogo/keystone-border.png b/src/ui/assets/walletlogo/keystone-border.png new file mode 100644 index 00000000000..3032595bae2 Binary files /dev/null and b/src/ui/assets/walletlogo/keystone-border.png differ diff --git a/src/ui/assets/walletlogo/keystone.png b/src/ui/assets/walletlogo/keystone.png new file mode 100644 index 00000000000..eb732721c69 Binary files /dev/null and b/src/ui/assets/walletlogo/keystone.png differ diff --git a/src/ui/component/AddAddressOptions/index.tsx b/src/ui/component/AddAddressOptions/index.tsx index ed5b60ab4d5..5e75e5eefb6 100644 --- a/src/ui/component/AddAddressOptions/index.tsx +++ b/src/ui/component/AddAddressOptions/index.tsx @@ -19,9 +19,11 @@ import { WALLET_BRAND_CONTENT, KEYRING_CLASS, BRAND_ALIAN_TYPE_TEXT, + BRAND_WALLET_CONNECT_TYPE, } from 'consts'; import clsx from 'clsx'; + const normaltype: string[] = [ 'createAddress', 'addWatchMode', @@ -36,6 +38,7 @@ const AddAddressOptions = () => { const [savedWallet, setSavedWallet] = useState([]); const [savedWalletData, setSavedWalletData] = useState([]); const [showMnemonic, setShowMnemonic] = useState(false); + const [keystoneInited, setKeystoneInited] = useState(false); const init = async () => { const walletSavedList = await wallet.getHighlightWalletList(); const filterdlist = walletSavedList.filter(Boolean); @@ -46,10 +49,17 @@ const AddAddressOptions = () => { if (accounts.length <= 0) { setShowMnemonic(true); } + const keystoneAccounts = await wallet.getTypedAccounts( + KEYRING_CLASS.HARDWARE.KEYSTONE + ); + if (keystoneAccounts.length > 0) { + setKeystoneInited(true); + } const savedTemp: [] = await renderSavedData(); setSavedWalletData(savedTemp); }; - const connectRouter = (item) => { + type Valueof = T[keyof T]; + const connectRouter = (item: Valueof) => { if (item.connectType === 'BitBox02Connect') { openInternalPageInTab('import/hardware?connectType=BITBOX02'); } else if (item.connectType === 'GridPlusConnect') { @@ -66,6 +76,22 @@ const AddAddressOptions = () => { history.push({ pathname: '/import/gnosis', }); + } else if (item.connectType === BRAND_WALLET_CONNECT_TYPE.QRCodeBase) { + if (keystoneInited) { + history.push({ + pathname: '/popup/import/select-address', + state: { + keyring: KEYRING_CLASS.HARDWARE.KEYSTONE, + }, + }); + } else { + history.push({ + pathname: '/import/qrcode', + state: { + brand: item.brand, + }, + }); + } } else { history.push({ pathname: '/import/wallet-connect', @@ -168,7 +194,7 @@ const AddAddressOptions = () => { brand: savedItem!.brand, image: savedItem!.image, connectType: savedItem!.connectType, - onClick: () => connectRouter(savedItem), + onClick: () => connectRouter(savedItem!), }); } }); diff --git a/src/ui/component/AddressList/index.tsx b/src/ui/component/AddressList/index.tsx index 923e4a9dfda..614f87e0bf9 100644 --- a/src/ui/component/AddressList/index.tsx +++ b/src/ui/component/AddressList/index.tsx @@ -11,7 +11,6 @@ import { findIndex } from 'lodash'; import { FixedSizeList, areEqual } from 'react-window'; import { DisplayedKeryring } from 'background/service/keyring'; import { KEYRING_TYPE } from 'consts'; -import { useWallet } from 'ui/utils'; import AddressItem, { AddressItemProps } from './AddressItem'; import './style.less'; @@ -93,7 +92,6 @@ const AddressList: any = forwardRef( }: AddressListProps, ref ) => { - const wallet = useWallet(); const [start, setStart] = useState(0); const [end, setEnd] = useState(10); const [editIndex, setEditIndex] = useState(0); diff --git a/src/ui/component/AddressViewer/index.tsx b/src/ui/component/AddressViewer/index.tsx index cfa4cd11d91..a5ce560b26b 100644 --- a/src/ui/component/AddressViewer/index.tsx +++ b/src/ui/component/AddressViewer/index.tsx @@ -33,7 +33,7 @@ export default ({ className={cx('address-viewer-text', className)} title={address?.toLowerCase()} > - {showIndex && index > 0 &&
{index}
} + {showIndex && index >= 0 &&
{index}
} {ellipsis ? `${address ?.toLowerCase() diff --git a/src/ui/component/QRCodeReader/index.tsx b/src/ui/component/QRCodeReader/index.tsx index 75badbb0c3d..94dee0232d3 100644 --- a/src/ui/component/QRCodeReader/index.tsx +++ b/src/ui/component/QRCodeReader/index.tsx @@ -1,12 +1,14 @@ -import React, { useEffect, useRef } from 'react'; -import { BrowserQRCodeReader } from '@zxing/library'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { BrowserQRCodeReader } from '@zxing/browser'; import './style.less'; +import { openInternalPageInTab } from 'ui/utils'; interface QRCodeReaderProps { onSuccess(text: string): void; onError?(): void; width?: number; height?: number; + isUR?: boolean; } const QRCodeReader = ({ @@ -15,39 +17,59 @@ const QRCodeReader = ({ width = 100, height = 100, }: QRCodeReaderProps) => { + const [canplay, setCanplay] = useState(false); + const codeReader = useMemo(() => { + return new BrowserQRCodeReader(); + }, []); const videoEl = useRef(null); - const controls = useRef(null); - const init = async () => { - try { - const reader = new BrowserQRCodeReader(); - controls.current = reader; - await reader.getVideoInputDevices(); - const result = await reader.decodeFromInputVideoDevice( - undefined, - videoEl.current! - ); - onSuccess(result.getText()); - } catch (e: any) { - if (!/ended/.test(e.message)) { - // Magic error message for Video stream has ended before any code could be detected - onError && onError(); - } + const checkCameraPermission = async () => { + const devices = await window.navigator.mediaDevices.enumerateDevices(); + const webcams = devices.filter((device) => device.kind === 'videoinput'); + const hasWebcamPermissions = webcams.some( + (webcam) => webcam.label && webcam.label.length > 0 + ); + if (!hasWebcamPermissions) { + openInternalPageInTab('request-permission?type=camera'); } }; - useEffect(() => { - init(); - return () => { - if (controls.current) { - controls.current.stopContinuousDecode(); - controls.current.reset(); - controls.current = null; + checkCameraPermission(); + }, []); + useEffect(() => { + const videoElem = document.getElementById('video'); + const canplayListener = () => { + setCanplay(true); + }; + videoElem!.addEventListener('canplay', canplayListener); + const promise = codeReader.decodeFromVideoDevice( + undefined, + 'video', + (result) => { + if (result) { + onSuccess(result.getText()); + } } + ); + return () => { + videoElem!.removeEventListener('canplay', canplayListener); + promise + .then((controls) => { + if (controls) { + controls.stop(); + } + }) + .catch(console.log); }; - }); + }, []); + return ( diff --git a/src/ui/views/Approval/components/QRHardWareWaiting/Player.tsx b/src/ui/views/Approval/components/QRHardWareWaiting/Player.tsx new file mode 100644 index 00000000000..0b50ff1cb5c --- /dev/null +++ b/src/ui/views/Approval/components/QRHardWareWaiting/Player.tsx @@ -0,0 +1,31 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import QRCode from 'qrcode.react'; +import { UR, UREncoder } from '@ngraveio/bc-ur'; +import { useTranslation } from 'react-i18next'; + +const Player = ({ type, cbor }) => { + const { t } = useTranslation(); + const urEncoder = useMemo( + () => new UREncoder(new UR(Buffer.from(cbor, 'hex'), type), 400), + [cbor, type] + ); + const [currentQRCode, setCurrentQRCode] = useState(urEncoder.nextPart()); + useEffect(() => { + const id = setInterval(() => { + setCurrentQRCode(urEncoder.nextPart()); + }, 100); + return () => { + clearInterval(id); + }; + }, [urEncoder]); + + return ( +
+ +

+ {t('KeystoneSignRequestDescription')} +

+
+ ); +}; +export default Player; diff --git a/src/ui/views/Approval/components/QRHardWareWaiting/QRHardWareWaiting.tsx b/src/ui/views/Approval/components/QRHardWareWaiting/QRHardWareWaiting.tsx new file mode 100644 index 00000000000..38efc05ea6c --- /dev/null +++ b/src/ui/views/Approval/components/QRHardWareWaiting/QRHardWareWaiting.tsx @@ -0,0 +1,129 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import Player from './Player'; +import Reader from './Reader'; +import { EVENTS, WALLET_BRAND_CONTENT, WALLET_BRAND_TYPES } from 'consts'; +import { useTranslation } from 'react-i18next'; +import { StrayPageWithButton } from 'ui/component'; +import eventBus from '@/eventBus'; +import { useApproval, useWallet } from 'ui/utils'; +import QRCodeCheckerDetail from '../../../QRCodeCheckerDetail'; +import { useHistory } from 'react-router-dom'; +import { RequestSignPayload } from '@/background/service/keyring/eth-keystone-keyring'; + +enum QRHARDWARE_STATUS { + SYNC, + SIGN, +} + +const QRHardWareWaiting = () => { + const [status, setStatus] = useState( + QRHARDWARE_STATUS.SYNC + ); + const [signPayload, setSignPayload] = useState(); + const [getApproval, resolveApproval, rejectApproval] = useApproval(); + const { t } = useTranslation(); + const [errorMessage, setErrorMessage] = useState(''); + const [isSignText, setIsSignText] = useState(false); + const history = useHistory(); + const wallet = useWallet(); + const init = useCallback(async () => { + const approval = await getApproval(); + setIsSignText(approval?.approvalType !== 'SignTx'); + eventBus.addEventListener( + EVENTS.QRHARDWARE.ACQUIRE_MEMSTORE_SUCCEED, + ({ request }) => { + setSignPayload(request); + } + ); + eventBus.addEventListener(EVENTS.SIGN_FINISHED, async (data) => { + if (data.success) { + resolveApproval(data.data, !isSignText); + } else { + rejectApproval(data.errorMsg); + } + history.push('/'); + }); + await wallet.acquireKeystoneMemStoreData(); + }, []); + + useEffect(() => { + init(); + return () => { + eventBus.removeAllEventListeners(EVENTS.SIGN_FINISHED); + eventBus.removeAllEventListeners( + EVENTS.QRHARDWARE.ACQUIRE_MEMSTORE_SUCCEED + ); + }; + }, [init]); + + const handleCancel = () => { + rejectApproval('User rejected the request.'); + }; + const handleRequestSignature = () => { + setErrorMessage(''); + setStatus(QRHARDWARE_STATUS.SIGN); + }; + + const showErrorChecker = useMemo(() => { + return errorMessage !== '' && status == QRHARDWARE_STATUS.SIGN; + }, [errorMessage]); + + const walletBrandContent = WALLET_BRAND_CONTENT[WALLET_BRAND_TYPES.KEYSTONE]; + return ( + +
+ rabby logo + +

+ {t(walletBrandContent.name)} +

+

+ {t('KeystoneSignRequestSubtitle')} +

+ +
+
+ {status === QRHARDWARE_STATUS.SYNC && signPayload && ( + + )} + {status === QRHARDWARE_STATUS.SIGN && ( + + )} + {showErrorChecker && ( + + )} +
+
+ ); +}; + +export default QRHardWareWaiting; diff --git a/src/ui/views/Approval/components/QRHardWareWaiting/Reader.tsx b/src/ui/views/Approval/components/QRHardWareWaiting/Reader.tsx new file mode 100644 index 00000000000..e7c2f787f9b --- /dev/null +++ b/src/ui/views/Approval/components/QRHardWareWaiting/Reader.tsx @@ -0,0 +1,58 @@ +import React, { useRef } from 'react'; +import { ETHSignature } from '@keystonehq/bc-ur-registry-eth'; +import * as uuid from 'uuid'; +import { useTranslation } from 'react-i18next'; +import QRCodeReader from 'ui/component/QRCodeReader'; +import { URDecoder } from '@ngraveio/bc-ur'; +import { openInternalPageInTab, useWallet } from 'ui/utils'; +import { useHistory } from 'react-router-dom'; +import { Form } from 'antd'; + +const Reader = ({ requestId, setErrorMessage }) => { + const { t } = useTranslation(); + const decoder = useRef(new URDecoder()); + const wallet = useWallet(); + const history = useHistory(); + const [form] = Form.useForm(); + + const handleSuccess = async (data) => { + decoder.current.receivePart(data); + if (decoder.current.isComplete()) { + const ur = decoder.current.resultUR(); + if (ur.type === 'eth-signature') { + const ethSignature = ETHSignature.fromCBOR(ur.cbor); + const buffer = ethSignature.getRequestId(); + const signId = uuid.stringify(buffer); + if (signId === requestId) { + return await wallet.submitQRHardwareSignature( + signId, + ur.cbor.toString('hex') + ); + } + setErrorMessage(t('KesytoneMismatchedSignId')); + } else { + setErrorMessage(t('unknownQrCode')); + } + } + }; + + const handleError = async () => { + await wallet.setPageStateCache({ + path: history.location.pathname, + params: {}, + states: form.getFieldsValue(), + }); + openInternalPageInTab('request-permission?type=camera'); + }; + + return ( + + ); +}; + +export default Reader; diff --git a/src/ui/views/Approval/components/QRHardWareWaiting/index.tsx b/src/ui/views/Approval/components/QRHardWareWaiting/index.tsx new file mode 100644 index 00000000000..802b4fec9b4 --- /dev/null +++ b/src/ui/views/Approval/components/QRHardWareWaiting/index.tsx @@ -0,0 +1,3 @@ +import QRHardWareWaiting from './QRHardWareWaiting'; + +export default QRHardWareWaiting; diff --git a/src/ui/views/Approval/components/SignText.tsx b/src/ui/views/Approval/components/SignText.tsx index 27c2841d692..9c6bbaf4ce8 100644 --- a/src/ui/views/Approval/components/SignText.tsx +++ b/src/ui/views/Approval/components/SignText.tsx @@ -35,6 +35,7 @@ export const WaitingSignComponent = { // [KEYRING_CLASS.WATCH]: 'WatchAdrressWaiting', [KEYRING_CLASS.WALLETCONNECT]: 'WatchAdrressWaiting', // [KEYRING_CLASS.GNOSIS]: 'GnosisWaiting', + [KEYRING_CLASS.HARDWARE.KEYSTONE]: 'QRHardWareWaiting', }; const SignText = ({ params }: { params: SignTextProps }) => { diff --git a/src/ui/views/Approval/components/TxComponents/Approve.tsx b/src/ui/views/Approval/components/TxComponents/Approve.tsx index 3ff64cd41c2..7a8ce4acf2c 100644 --- a/src/ui/views/Approval/components/TxComponents/Approve.tsx +++ b/src/ui/views/Approval/components/TxComponents/Approve.tsx @@ -1,4 +1,4 @@ -import { Button, Form, Input, message, Modal } from 'antd'; +import { Button, Form, Input, message } from 'antd'; import { ExplainTxResponse, TokenItem, Tx } from 'background/service/openapi'; import { Account } from 'background/service/preference'; import BigNumber from 'bignumber.js'; diff --git a/src/ui/views/Approval/components/index.ts b/src/ui/views/Approval/components/index.ts index ff46c026b0d..597e862e0a6 100644 --- a/src/ui/views/Approval/components/index.ts +++ b/src/ui/views/Approval/components/index.ts @@ -5,3 +5,4 @@ export { default as Connect } from './Connect'; export { default as HardwareWaiting } from './HardwareWaiting'; export { default as WatchAdrressWaiting } from './WatchAddressWaiting'; export { default as AddChain } from './AddChain'; +export { default as QRHardWareWaiting } from './QRHardWareWaiting'; diff --git a/src/ui/views/Approval/style.less b/src/ui/views/Approval/style.less index 136dff1a692..b78c93a2c6c 100644 --- a/src/ui/views/Approval/style.less +++ b/src/ui/views/Approval/style.less @@ -994,7 +994,7 @@ } } -.security-check-drawer { +.security-check-drawer, .signature-qr-checker-drawer { .ant-drawer-header { border: none; padding: 20px 20px 8px; @@ -1016,7 +1016,7 @@ } } -.security-check-detail { +.security-check-detail, .signature-qr-checker-detail { display: flex; flex-direction: column; height: 100%; diff --git a/src/ui/views/Dashboard/components/BalanceView.tsx b/src/ui/views/Dashboard/components/BalanceView.tsx index 36956790b6b..544c43a1867 100644 --- a/src/ui/views/Dashboard/components/BalanceView.tsx +++ b/src/ui/views/Dashboard/components/BalanceView.tsx @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Spin } from 'ui/component'; -import { sortBy } from 'lodash'; import { useCurrentBalance } from 'ui/component/AddressList/AddressItem'; import { splitNumberByStep, useWallet } from 'ui/utils'; diff --git a/src/ui/views/ImportQRCodeBase/index.tsx b/src/ui/views/ImportQRCodeBase/index.tsx new file mode 100644 index 00000000000..a71caaa4981 --- /dev/null +++ b/src/ui/views/ImportQRCodeBase/index.tsx @@ -0,0 +1,147 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Form } from 'antd'; +import { useHistory } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { URDecoder } from '@ngraveio/bc-ur'; +import QRCodeReader from 'ui/component/QRCodeReader'; +import { StrayPageWithButton } from 'ui/component'; +import { useWallet } from 'ui/utils'; +import { openInternalPageInTab } from 'ui/utils/webapi'; +import './style.less'; + +import KeystoneLogo from 'ui/assets/walletlogo/keystone.png'; +import { HARDWARE_KEYRING_TYPES } from 'consts'; +import QRCodeCheckerDetail from 'ui/views/QRCodeCheckerDetail'; + +const ImportQRCodeBase = () => { + const { t } = useTranslation(); + const history = useHistory(); + const wallet = useWallet(); + const [form] = Form.useForm(); + const decoder = useRef(new URDecoder()); + const [errorMessage, setErrorMessage] = useState(''); + const [scan, setScan] = useState(true); + + const showErrorChecker = useMemo(() => { + return errorMessage !== ''; + }, [errorMessage]); + + const handleScanQRCodeSuccess = async (data) => { + try { + decoder.current.receivePart(data); + if (decoder.current.isComplete()) { + const result = decoder.current.resultUR(); + let stashKeyringId; + if (result.type === 'crypto-hdkey') { + stashKeyringId = await wallet.submitQRHardwareCryptoHDKey( + result.cbor.toString('hex') + ); + } else if (result.type === 'crypto-account') { + stashKeyringId = await wallet.submitQRHardwareCryptoAccount( + result.cbor.toString('hex') + ); + } else { + setErrorMessage( + t( + 'Invalid QR code. Please scan the sync QR code of the hardware wallet.' + ) + ); + return; + } + history.push({ + pathname: '/popup/import/select-address', + state: { + keyring: HARDWARE_KEYRING_TYPES.Keystone.type, + keyringId: stashKeyringId, + }, + }); + } + } catch (e) { + setScan(false); + setErrorMessage( + t( + 'Invalid QR code. Please scan the sync QR code of the hardware wallet.' + ) + ); + } + }; + + const handleScanQRCodeError = async () => { + await wallet.setPageStateCache({ + path: history.location.pathname, + params: {}, + states: form.getFieldsValue(), + }); + openInternalPageInTab('request-permission?type=camera'); + }; + + const handleClickBack = () => { + if (history.length > 1) { + history.goBack(); + } else { + history.replace('/'); + } + }; + + useEffect(() => { + return () => { + wallet.clearPageStateCache(); + }; + }, []); + + const handleScan = () => { + setErrorMessage(''); + setScan(true); + }; + return ( + +
+ rabby logo + +

+ {t('Keystone')} +

+

+ {t('Scan the QR code on the Keystone hardware wallet')} +

+ +
+
+ {scan && ( + + )} + {showErrorChecker && ( + + )} +
+
+ ); +}; + +export default ImportQRCodeBase; diff --git a/src/ui/views/ImportQRCodeBase/style.less b/src/ui/views/ImportQRCodeBase/style.less new file mode 100644 index 00000000000..2c6a26a0d5c --- /dev/null +++ b/src/ui/views/ImportQRCodeBase/style.less @@ -0,0 +1,13 @@ +@import '../../style/var.less'; +.import-qrcode, .qr-hardware-sign { + .goback { + position: absolute; + color: #ffffff; + top: 38px; + left: 34px; + z-index: 100; + } + .qrcode-scanner { + padding-top: 35px; + } +} \ No newline at end of file diff --git a/src/ui/views/ImportSuccess/index.tsx b/src/ui/views/ImportSuccess/index.tsx index 4ff87411e96..f043635c5dd 100644 --- a/src/ui/views/ImportSuccess/index.tsx +++ b/src/ui/views/ImportSuccess/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { useTranslation, Trans } from 'react-i18next'; import { sortBy } from 'lodash'; diff --git a/src/ui/views/MainRoute.tsx b/src/ui/views/MainRoute.tsx index d2f8ba9f2e2..411fe6d38c4 100644 --- a/src/ui/views/MainRoute.tsx +++ b/src/ui/views/MainRoute.tsx @@ -11,6 +11,7 @@ import ImportPrivateKey from './ImportPrivateKey'; import ImportJson from './ImportJson'; import ImportMnemonics from './ImportMnemonics'; import ImportWatchAddress from './ImportWatchAddress'; +import ImportQRCodeBase from './ImportQRCodeBase'; import SelectAddress from './SelectAddress'; import ImportSuccess from './ImportSuccess'; import ImportHardware from './ImportHardware'; @@ -100,6 +101,9 @@ const Main = () => { + + + diff --git a/src/ui/views/QRCodeCheckerDetail.tsx b/src/ui/views/QRCodeCheckerDetail.tsx new file mode 100644 index 00000000000..e76a6fef9a2 --- /dev/null +++ b/src/ui/views/QRCodeCheckerDetail.tsx @@ -0,0 +1,63 @@ +import React, { useEffect, useRef } from 'react'; +import { Button, Drawer } from 'antd'; +import { useTranslation } from 'react-i18next'; +import IconArrowRight from 'ui/assets/arrow-right-gray.svg'; + +const QRCodeCheckerDetail = ({ + visible, + data, + okText = 'Try Again', + cancelText = 'Cancel', + onOk, + onCancel, +}: { + visible: boolean; + data: string; + okText?: string; + cancelText?: string; + onOk(): void; + onCancel(): void; +}) => { + const inputEl = useRef(null); + const { t } = useTranslation(); + useEffect(() => { + if (visible) { + setTimeout(() => { + if (inputEl.current) { + inputEl.current.focus(); + } + }, 100); + } + }, [visible]); + + return ( + + } + > +
+
{t(data)}
+
+
+ + +
+
+
+
+ ); +}; + +export default QRCodeCheckerDetail; diff --git a/yarn.lock b/yarn.lock index 53c2d8b5f53..17bcbbd36b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36,6 +36,11 @@ lodash "^4.17.21" resize-observer-polyfill "^1.5.0" +"@apocentre/alias-sampling@^0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@apocentre/alias-sampling/-/alias-sampling-0.5.3.tgz#897ff181b48ad7b2bcb4ecf29400214888244f08" + integrity sha512-7UDWIIF9hIeJqfKXkNIzkVandlwLf1FWTSdrb9iXvOP8oF544JRXQjCbiTmCv2c9n44n/FIWtehhBfNuAx2CZA== + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" @@ -1320,6 +1325,14 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@ethereumjs/common@^2.0.0", "@ethereumjs/common@^2.6.1": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.2.tgz#eb006c9329c75c80f634f340dc1719a5258244df" + integrity sha512-vDwye5v0SVeuDky4MtKsu+ogkH2oFUV8pBKzH/eNBzT8oI91pKa8WyzDuYuxOQsgNgv5R34LfFDh2aaw3H4HbQ== + dependencies: + crc-32 "^1.2.0" + ethereumjs-util "^7.1.4" + "@ethereumjs/common@^2.4.0", "@ethereumjs/common@^2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.0.tgz#feb96fb154da41ee2cc2c5df667621a440f36348" @@ -1336,13 +1349,13 @@ crc-32 "^1.2.0" ethereumjs-util "^7.1.1" -"@ethereumjs/common@^2.6.1": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.2.tgz#eb006c9329c75c80f634f340dc1719a5258244df" - integrity sha512-vDwye5v0SVeuDky4MtKsu+ogkH2oFUV8pBKzH/eNBzT8oI91pKa8WyzDuYuxOQsgNgv5R34LfFDh2aaw3H4HbQ== +"@ethereumjs/tx@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.0.0.tgz#8dfd91ed6e91e63996e37b3ddc340821ebd48c81" + integrity sha512-H9tfy6qgYxPXvt1TSObfVmVjlF43OoQqoPQ3PJsG2JiuqaMHj5ettV1pGFEC3FamENDBkl6vD6niQEvIlXv/VQ== dependencies: - crc-32 "^1.2.0" - ethereumjs-util "^7.1.4" + "@ethereumjs/common" "^2.0.0" + ethereumjs-util "^7.0.7" "@ethereumjs/tx@^3.1.1": version "3.4.0" @@ -1360,7 +1373,7 @@ "@ethereumjs/common" "^2.5.0" ethereumjs-util "^7.1.2" -"@ethereumjs/tx@^3.2.0": +"@ethereumjs/tx@^3.2.0", "@ethereumjs/tx@^3.3.0": version "3.5.0" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.5.0.tgz#783b0aeb08518b9991b23f5155763bbaf930a037" integrity sha512-/+ZNbnJhQhXC83Xuvy6I9k4jT5sXiV0tMR9C+AzSSpcCV64+NB8dTE1m3x98RYMqb8+TLYWA+HML4F5lfXTlJw== @@ -2307,6 +2320,48 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@keystonehq/base-eth-keyring@^0.3.5-alpha.0": + version "0.3.5-alpha.0" + resolved "https://registry.yarnpkg.com/@keystonehq/base-eth-keyring/-/base-eth-keyring-0.3.5-alpha.0.tgz#e42d2e40fb83caf68cd489a94a92d42e5198ece4" + integrity sha512-bjObp+QRPBSJWEpdfnzmUumLwKoqjNgaOn5TKHizgWjtkltRCFjRnSTGA/MHBnf+afRb1AP1FZdv9Pk0ogjtEQ== + dependencies: + "@ethereumjs/tx" "3.0.0" + "@keystonehq/bc-ur-registry-eth" "^0.8.1-alpha.0" + ethereumjs-util "^7.0.8" + hdkey "^2.0.1" + uuid "^8.3.2" + +"@keystonehq/bc-ur-registry-eth@^0.8.1-alpha.0": + version "0.8.1-alpha.0" + resolved "https://registry.yarnpkg.com/@keystonehq/bc-ur-registry-eth/-/bc-ur-registry-eth-0.8.1-alpha.0.tgz#652761176869f4f650daf0420ac3dda70a68f777" + integrity sha512-mNzGX5gfB2TkgNC6D+Cj5LTsc5m8FogJJh+u/nAa4x1slLAU+J8SePaRR8dtzCLOQhz1HLwXGGVag9h0Tch10w== + dependencies: + "@keystonehq/bc-ur-registry" "^0.5.0-alpha.4" + ethereumjs-util "^7.0.8" + hdkey "^2.0.1" + uuid "^8.3.2" + +"@keystonehq/bc-ur-registry@^0.5.0-alpha.4": + version "0.5.0-alpha.5" + resolved "https://registry.yarnpkg.com/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.5.0-alpha.5.tgz#3d1a7eab980e8445c1596cdde704215c96d6b88a" + integrity sha512-T80XI+c8pWnkq9ZbuadlhFq/+8o4TcHtq+LQsK1XfjkhBqH75tcwim0310gKxavOhaSoC1i8dSqAnrFpj+5dJw== + dependencies: + "@ngraveio/bc-ur" "^1.1.5" + base58check "^2.0.0" + tslib "^2.3.0" + +"@keystonehq/metamask-airgapped-keyring@^0.2.5-alpha.0": + version "0.2.5-alpha.2.1" + resolved "https://registry.yarnpkg.com/@keystonehq/metamask-airgapped-keyring/-/metamask-airgapped-keyring-0.2.5-alpha.2.1.tgz#999d80aa9a00db5485417e9cff2f4c6780032add" + integrity sha512-2w2PQ84vwznyK2u//Mt7ZeIn92qF55eodbnsz7iZCXPY33Xbk3c1CC0YCA7IHhDz3Wb9cZpWdQWn2jDh9nC1AQ== + dependencies: + "@ethereumjs/tx" "^3.3.0" + "@keystonehq/base-eth-keyring" "^0.3.5-alpha.0" + "@keystonehq/bc-ur-registry-eth" "^0.8.1-alpha.0" + "@metamask/obs-store" "^7.0.0" + rlp "^2.2.6" + uuid "^8.3.2" + "@ledgerhq/cryptoassets@^6.25.0": version "6.25.0" resolved "https://registry.yarnpkg.com/@ledgerhq/cryptoassets/-/cryptoassets-6.25.0.tgz#9e9307c69c436c938fafd27d5351526c21a2a114" @@ -2407,11 +2462,32 @@ readable-stream "^2.2.2" through2 "^2.0.3" +"@metamask/obs-store@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@metamask/obs-store/-/obs-store-7.0.0.tgz#6cae5f28306bb3e83a381bc9ae22682316095bd3" + integrity sha512-Tr61Uu9CGXkCg5CZwOYRMQERd+y6fbtrtLd/PzDTPHO5UJpmSbU+7MPcQK7d1DwZCOCeCIvhmZSUCvYliC8uGw== + dependencies: + "@metamask/safe-event-emitter" "^2.0.0" + through2 "^2.0.3" + "@metamask/safe-event-emitter@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c" integrity sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q== +"@ngraveio/bc-ur@^1.1.5", "@ngraveio/bc-ur@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@ngraveio/bc-ur/-/bc-ur-1.1.6.tgz#8f8c75fff22f6a5e4dfbc5a6b540d7fe8f42cd39" + integrity sha512-G+2XgjXde2IOcEQeCwR250aS43/Swi7gw0FuETgJy2c3HqF8f88SXDMsIGgJlZ8jXd0GeHR4aX0MfjXf523UZg== + dependencies: + "@apocentre/alias-sampling" "^0.5.3" + assert "^2.0.0" + bignumber.js "^9.0.1" + cbor-sync "^1.0.4" + crc "^3.8.0" + jsbi "^3.1.5" + sha.js "^2.4.11" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -3918,6 +3994,13 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@zxing/browser@^0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@zxing/browser/-/browser-0.0.10.tgz#63c0a762fc2fd4ee946a20953ef24fab225698a9" + integrity sha512-P2wQc5fs+cjSc39zFS4UDhejWqdikf4FjuWIlFrzXD8fOsZ4ASfmLDKGeg7mRgmJq11oMKcVXvFFI6kcIKtxuQ== + optionalDependencies: + "@zxing/text-encoding" "^0.9.0" + "@zxing/library@0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@zxing/library/-/library-0.8.0.tgz#accd9f3cd5c06fa40a95c2c1f61398c41548a9e3" @@ -3927,6 +4010,11 @@ optionalDependencies: text-encoding "^0.6.4" +"@zxing/text-encoding@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" + integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -4470,6 +4558,11 @@ base-x@3.0.9: dependencies: safe-buffer "^5.0.1" +base-x@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-1.1.0.tgz#42d3d717474f9ea02207f6d1aa1f426913eeb7ac" + integrity sha1-QtPXF0dPnqAiB/bRqh9CaRPut6w= + base-x@^3.0.2: version "3.0.8" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" @@ -4477,6 +4570,13 @@ base-x@^3.0.2: dependencies: safe-buffer "^5.0.1" +base58check@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base58check/-/base58check-2.0.0.tgz#8046652d14bc87f063bd16be94a39134d3b61173" + integrity sha1-gEZlLRS8h/BjvRa+lKORNNO2EXM= + dependencies: + bs58 "^3.0.0" + base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -4616,7 +4716,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.8, bn.js@^ resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2: +bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== @@ -4786,6 +4886,13 @@ bs58@^2.0.1: resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d" integrity sha1-VZCNWPGYKrogCPob7Y+RmYopv40= +bs58@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-3.1.0.tgz#d4c26388bf4804cac714141b1945aa47e5eb248e" + integrity sha1-1MJjiL9IBMrHFBQbGUWqR+XrJI4= + dependencies: + base-x "^1.1.0" + bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -4832,7 +4939,7 @@ buffer@5.6.0: base64-js "^1.0.2" ieee754 "^1.1.4" -buffer@^5.4.2, buffer@^5.5.0, buffer@^5.6.0: +buffer@^5.1.0, buffer@^5.4.2, buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -4920,6 +5027,11 @@ cashaddrjs@0.4.4: dependencies: big-integer "1.6.36" +cbor-sync@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cbor-sync/-/cbor-sync-1.0.4.tgz#5a11a1ab75c2a14d1af1b237fd84aa8c1593662f" + integrity sha512-GWlXN4wiz0vdWWXBU71Dvc1q3aBo0HytqwAZnXF1wOwjqNnDWA1vZ1gDMFLlqohak31VQzmhiYfiCX5QSSfagA== + cbor-web@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cbor-web/-/cbor-web-7.0.6.tgz#6e23a0c58db4c38e485e395de511b9e2f628961c" @@ -5352,6 +5464,13 @@ crc-32@^1.2.0: exit-on-epipe "~1.0.1" printj "~1.1.0" +crc@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -6487,27 +6606,27 @@ ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.9: ethjs-util "0.1.6" rlp "^2.2.4" -ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.2.tgz#cfd79a9a3f5cdc042d1abf29964de9caf10ec238" - integrity sha512-xCV3PTAhW8Q2k88XZn9VcO4OrjpeXAlDm5LQTaOLp81SjNSSY6+MwuGXrx6vafOMheWSmZGxIXUbue5e9UvUBw== +ethereumjs-util@^7.0.7, ethereumjs-util@^7.0.8, ethereumjs-util@^7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.4.tgz#a6885bcdd92045b06f596c7626c3e89ab3312458" + integrity sha512-p6KmuPCX4mZIqsQzXfmSx9Y0l2hqf+VkAiwSisW3UKUFdk8ZkAt+AYaor83z2nSi6CU2zSsXMlD80hAbNEGM0A== dependencies: "@types/bn.js" "^5.1.0" bn.js "^5.1.2" create-hash "^1.1.2" ethereum-cryptography "^0.1.3" - ethjs-util "0.1.6" rlp "^2.2.4" -ethereumjs-util@^7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.4.tgz#a6885bcdd92045b06f596c7626c3e89ab3312458" - integrity sha512-p6KmuPCX4mZIqsQzXfmSx9Y0l2hqf+VkAiwSisW3UKUFdk8ZkAt+AYaor83z2nSi6CU2zSsXMlD80hAbNEGM0A== +ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.2.tgz#cfd79a9a3f5cdc042d1abf29964de9caf10ec238" + integrity sha512-xCV3PTAhW8Q2k88XZn9VcO4OrjpeXAlDm5LQTaOLp81SjNSSY6+MwuGXrx6vafOMheWSmZGxIXUbue5e9UvUBw== dependencies: "@types/bn.js" "^5.1.0" bn.js "^5.1.2" create-hash "^1.1.2" ethereum-cryptography "^0.1.3" + ethjs-util "0.1.6" rlp "^2.2.4" ethereumjs-wallet@^0.6.0: @@ -7238,6 +7357,15 @@ hdkey@0.8.0: safe-buffer "^5.1.1" secp256k1 "^3.0.1" +hdkey@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-2.0.1.tgz#0a211d0c510bfc44fa3ec9d44b13b634641cad74" + integrity sha512-c+tl9PHG9/XkGgG0tD7CJpRVaE0jfZizDNmnErUAKQ4EjQSOcOUcV3EN9ZEZS8pZ4usaeiiK0H7stzuzna8feA== + dependencies: + bs58check "^2.1.2" + safe-buffer "^5.1.1" + secp256k1 "^4.0.0" + he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -8383,6 +8511,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbi@^3.1.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.2.5.tgz#b37bb90e0e5c2814c1c2a1bcd8c729888a2e37d6" + integrity sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ== + jsdom@^16.6.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" @@ -11194,6 +11327,13 @@ rlp@^2.0.0, rlp@^2.2.3, rlp@^2.2.4: dependencies: bn.js "^4.11.1" +rlp@^2.2.6: + version "2.2.7" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" + integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== + dependencies: + bn.js "^5.2.0" + rtl-css-js@^1.14.0: version "1.14.1" resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.14.1.tgz#f79781d6a0c510abe73fde60aa3cbe9dfd134a45" @@ -11354,6 +11494,15 @@ secp256k1@^3.0.1: nan "^2.14.0" safe-buffer "^5.1.2" +secp256k1@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" + integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -11467,7 +11616,7 @@ setprototypeof@1.1.1: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== -sha.js@^2.4.0, sha.js@^2.4.8: +sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== @@ -12300,7 +12449,7 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.3.1: +tslib@^2.0.0, tslib@^2.3.0, tslib@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==