From 5c2686de76fd0609cdd1789bd69dfa3e4b83a9f8 Mon Sep 17 00:00:00 2001 From: vvvvvv1vvvvvv Date: Wed, 19 Jan 2022 18:19:28 +0800 Subject: [PATCH 1/4] feat: Support sync for keystone --- .../controller/provider/controller.ts | 3 +- src/background/controller/wallet.ts | 23 ++++ .../service/keyring/eth-keystone-keyring.ts | 29 +++++ src/background/service/keyring/index.ts | 3 + src/constant/index.ts | 17 +++ src/ui/assets/walletlogo/keystone-border.png | Bin 0 -> 9019 bytes src/ui/assets/walletlogo/keystone.png | Bin 0 -> 8009 bytes src/ui/component/AddAddressOptions/index.tsx | 13 ++- src/ui/component/QRCodeReader/index.tsx | 21 ++-- src/ui/views/ImportQRCodeBase/index.tsx | 102 ++++++++++++++++++ src/ui/views/ImportQRCodeBase/style.less | 13 +++ src/ui/views/MainRoute.tsx | 4 + 12 files changed, 219 insertions(+), 9 deletions(-) create mode 100644 src/background/service/keyring/eth-keystone-keyring.ts create mode 100644 src/ui/assets/walletlogo/keystone-border.png create mode 100644 src/ui/assets/walletlogo/keystone.png create mode 100644 src/ui/views/ImportQRCodeBase/index.tsx create mode 100644 src/ui/views/ImportQRCodeBase/style.less diff --git a/src/background/controller/provider/controller.ts b/src/background/controller/provider/controller.ts index be2d1a0017f..0b05ea2b445 100644 --- a/src/background/controller/provider/controller.ts +++ b/src/background/controller/provider/controller.ts @@ -406,6 +406,7 @@ class ProviderController extends BaseController { cacheExplain ); } + console.log('e', e); const errMsg = e.message || JSON.stringify(e); notification.create(undefined, i18n.t('Transaction push failed'), errMsg); throw new Error(errMsg); @@ -634,7 +635,7 @@ class ProviderController extends BaseController { walletRequestPermissions = ({ data: { params: permissions } }) => { const result: Web3WalletPermission[] = []; - if (permissions && 'eth_accounts' in permissions[0]) { + if ('eth_accounts' in permissions?.[0]) { result.push({ parentCapability: 'eth_accounts' }); } return result; diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index d4a12fde0ca..735121ec928 100644 --- a/src/background/controller/wallet.ts +++ b/src/background/controller/wallet.ts @@ -45,6 +45,7 @@ import { ExplainTxResponse, TokenItem } from '../service/openapi'; import DisplayKeyring from '../service/keyring/display'; import provider from './provider'; import WalletConnectKeyring from '@rabby-wallet/eth-walletconnect-keyring'; +import QRHardwareKeyring from '../service/keyring/eth-keystone-keyring'; import eventBus from '@/eventBus'; import { setPageStateCacheWhenPopupClose, @@ -59,6 +60,12 @@ const stashKeyrings: Record = {}; export class WalletController extends BaseController { openapi = openapiService; + qrHardwareKeyring: QRHardwareKeyring; + + constructor() { + super(); + this.qrHardwareKeyring = new QRHardwareKeyring(); + } /* wallet */ boot = (password) => keyringService.boot(password); @@ -973,6 +980,22 @@ export class WalletController extends BaseController { return stashKeyringId; }; + submitQRHardwareCryptoHDKey = () => { + let keyring, isNewKey; + let stashKeyringId: number | null = null; + const keyringType = KEYRING_CLASS.QRCODE; + try { + keyring = this._getKeyringByType(keyringType); + } catch { + const QRCodeKeyring = keyringService.getKeyringClassForType(keyringType); + keyring = new QRCodeKeyring(); + stashKeyringId = Object.values(stashKeyrings).length; + stashKeyrings[stashKeyringId] = keyring; + } + // TODO + return stashKeyringId; + }; + signPersonalMessage = async ( type: string, from: string, 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..99fc855215b --- /dev/null +++ b/src/background/service/keyring/eth-keystone-keyring.ts @@ -0,0 +1,29 @@ +import { MetaMaskKeyring } from '@keystonehq/metamask-airgapped-keyring'; +import { toChecksumAddress } from 'ethereumjs-util'; + +const pathBase = 'm'; + +export default class KeystoneKeyring extends MetaMaskKeyring { + perPage = 10; + + 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; + } +} 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..2ff7367468e 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', @@ -653,6 +656,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 +779,7 @@ export enum BRAND_WALLET_CONNECT_TYPE { TrezorConnect = 'TrezorConnect', GnosisConnect = 'GnosisConnect', GridPlusConnect = 'GridPlusConnect', + QRCodeBase = 'QR Hardware Wallet Device', } export const WALLETCONNECT_STATUS_MAP = { @@ -824,6 +832,7 @@ export enum WALLET_BRAND_TYPES { GNOSIS = 'Gnosis', GRIDPLUS = 'GRIDPLUS', METAMASK = 'MetaMask', + KEYSTONE = 'Keystone', } export const WALLET_BRAND_CONTENT = { @@ -891,6 +900,14 @@ export const WALLET_BRAND_CONTENT = { image: LogoJade, connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, }, + [WALLET_BRAND_TYPES.KEYSTONE]: { + id: 14, + name: 'Keystone', + brand: WALLET_BRAND_TYPES.KEYSTONE, + icon: LogoKeystone, + image: LogoKeystoneWithBorder, + connectType: BRAND_WALLET_CONNECT_TYPE.QRCodeBase, + }, [WALLET_BRAND_TYPES.LEDGER]: { id: 4, name: 'Ledger', diff --git a/src/ui/assets/walletlogo/keystone-border.png b/src/ui/assets/walletlogo/keystone-border.png new file mode 100644 index 0000000000000000000000000000000000000000..3032595bae22290cb63c7c1f92a51fc3674e605c GIT binary patch literal 9019 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 { 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 +68,13 @@ const AddAddressOptions = () => { history.push({ pathname: '/import/gnosis', }); + } else if (item.connectType === BRAND_WALLET_CONNECT_TYPE.QRCodeBase) { + history.push({ + pathname: '/import/qrcode', + state: { + brand: item.brand, + }, + }); } else { history.push({ pathname: '/import/wallet-connect', @@ -168,7 +177,7 @@ const AddAddressOptions = () => { brand: savedItem!.brand, image: savedItem!.image, connectType: savedItem!.connectType, - onClick: () => connectRouter(savedItem), + onClick: () => connectRouter(savedItem!), }); } }); diff --git a/src/ui/component/QRCodeReader/index.tsx b/src/ui/component/QRCodeReader/index.tsx index 75badbb0c3d..6e50707beea 100644 --- a/src/ui/component/QRCodeReader/index.tsx +++ b/src/ui/component/QRCodeReader/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef } from 'react'; -import { BrowserQRCodeReader } from '@zxing/library'; +import { BrowserQRCodeReader } from '@zxing/browser'; import './style.less'; interface QRCodeReaderProps { @@ -7,6 +7,7 @@ interface QRCodeReaderProps { onError?(): void; width?: number; height?: number; + isUR?: boolean; } const QRCodeReader = ({ @@ -14,6 +15,7 @@ const QRCodeReader = ({ onError, width = 100, height = 100, + isUR = false, }: QRCodeReaderProps) => { const videoEl = useRef(null); const controls = useRef(null); @@ -21,12 +23,19 @@ const QRCodeReader = ({ try { const reader = new BrowserQRCodeReader(); controls.current = reader; - await reader.getVideoInputDevices(); - const result = await reader.decodeFromInputVideoDevice( - undefined, - videoEl.current! + const devices = await BrowserQRCodeReader.listVideoInputDevices(); + await reader.decodeFromVideoDevice( + devices[0].deviceId, + videoEl.current!, + (result, error) => { + if (error) return; + if (result) { + onSuccess(result.getText()); + } + } ); - onSuccess(result.getText()); + // console.log('result', result); + // 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 diff --git a/src/ui/views/ImportQRCodeBase/index.tsx b/src/ui/views/ImportQRCodeBase/index.tsx new file mode 100644 index 00000000000..a21502d740a --- /dev/null +++ b/src/ui/views/ImportQRCodeBase/index.tsx @@ -0,0 +1,102 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Form } from 'antd'; +import { useHistory } from 'react-router-dom'; +import { composeInitialProps, useTranslation } from 'react-i18next'; +import { URDecoder } from '@ngraveio/bc-ur'; +import QRCodeReader from 'ui/component/QRCodeReader'; +import { StrayPageWithButton } from 'ui/component'; +import { useWallet, useWalletRequest } from 'ui/utils'; +import { openInternalPageInTab } from 'ui/utils/webapi'; +import './style.less'; + +import KeystoneLogo from 'ui/assets/walletlogo/keystone.png'; + +const ImportQRCodeBase = () => { + const { t } = useTranslation(); + const history = useHistory(); + const wallet = useWallet(); + const [form] = Form.useForm(); + const decoder = useRef(new URDecoder()); + + const handleScanQRCodeSuccess = (data) => { + decoder.current.receivePart(data); + if (decoder.current.isComplete()) { + const result = decoder.current.resultUR(); + result.cbor.toString('hex'); + /* TODO: + const stashKeyringId = await wallet.submitQRHardwareCryptoHDKey(); + history.push({ + pathname: '/import/select-address', + state: { + keyring: HARDWARE_KEYRING_TYPES.KeyStone.type, + path: currentPath, + keyringId + }, + }); + */ + } + }; + + 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(); + }; + }, []); + + return ( + +

+ rabby logo + +

+ {t('Keystone')} +

+

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

+ +
+
+ +
+ + ); +}; + +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..8eca583f0d9 --- /dev/null +++ b/src/ui/views/ImportQRCodeBase/style.less @@ -0,0 +1,13 @@ +@import '../../style/var.less'; +.import-qrcode { + .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/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 = () => { + + + From f805306568c7708dabb20bed1da7e2cb404e9fbe Mon Sep 17 00:00:00 2001 From: vvvvvv1vvvvvv Date: Mon, 28 Feb 2022 19:49:16 +0800 Subject: [PATCH 2/4] feat: support sign for keystone wallet --- _raw/_locales/en/messages.json | 18 ++ _raw/_locales/zh_CN/messages.json | 18 ++ package.json | 4 + .../controller/provider/controller.ts | 5 +- src/background/controller/wallet.ts | 75 +++++-- .../service/keyring/eth-keystone-keyring.ts | 23 +++ src/constant/index.ts | 14 +- src/ui/component/AddAddressOptions/index.tsx | 29 ++- src/ui/component/AddressList/index.tsx | 2 - src/ui/component/AddressViewer/index.tsx | 2 +- src/ui/component/QRCodeReader/index.tsx | 81 ++++---- .../components/QRHardWareWaiting/Player.tsx | 31 +++ .../QRHardWareWaiting/QRHardWareWaiting.tsx | 129 ++++++++++++ .../components/QRHardWareWaiting/Reader.tsx | 58 ++++++ .../components/QRHardWareWaiting/index.tsx | 3 + src/ui/views/Approval/components/SignText.tsx | 1 + .../components/TxComponents/Approve.tsx | 2 +- src/ui/views/Approval/components/index.ts | 1 + src/ui/views/Approval/style.less | 4 +- .../Dashboard/components/BalanceView.tsx | 1 - src/ui/views/ImportQRCodeBase/index.tsx | 87 ++++++-- src/ui/views/ImportQRCodeBase/style.less | 2 +- src/ui/views/ImportSuccess/index.tsx | 2 +- src/ui/views/QRCodeCheckerDetail.tsx | 63 ++++++ src/utils/transaction.ts | 2 +- yarn.lock | 189 ++++++++++++++++-- 26 files changed, 732 insertions(+), 114 deletions(-) create mode 100644 src/ui/views/Approval/components/QRHardWareWaiting/Player.tsx create mode 100644 src/ui/views/Approval/components/QRHardWareWaiting/QRHardWareWaiting.tsx create mode 100644 src/ui/views/Approval/components/QRHardWareWaiting/Reader.tsx create mode 100644 src/ui/views/Approval/components/QRHardWareWaiting/index.tsx create mode 100644 src/ui/views/QRCodeCheckerDetail.tsx 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/provider/controller.ts b/src/background/controller/provider/controller.ts index 0b05ea2b445..93e74323739 100644 --- a/src/background/controller/provider/controller.ts +++ b/src/background/controller/provider/controller.ts @@ -1,8 +1,8 @@ import * as Sentry from '@sentry/browser'; -import Transaction from 'ethereumjs-tx'; import Common, { Hardfork } from '@ethereumjs/common'; import { TransactionFactory, + Transaction, FeeMarketEIP1559Transaction, } from '@ethereumjs/tx'; import { ethers } from 'ethers'; @@ -406,7 +406,6 @@ class ProviderController extends BaseController { cacheExplain ); } - console.log('e', e); const errMsg = e.message || JSON.stringify(e); notification.create(undefined, i18n.t('Transaction push failed'), errMsg); throw new Error(errMsg); @@ -635,7 +634,7 @@ class ProviderController extends BaseController { walletRequestPermissions = ({ data: { params: permissions } }) => { const result: Web3WalletPermission[] = []; - if ('eth_accounts' in permissions?.[0]) { + if (permissions && 'eth_accounts' in permissions[0]) { result.push({ parentCapability: 'eth_accounts' }); } return result; diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index 735121ec928..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 { @@ -45,7 +42,6 @@ import { ExplainTxResponse, TokenItem } from '../service/openapi'; import DisplayKeyring from '../service/keyring/display'; import provider from './provider'; import WalletConnectKeyring from '@rabby-wallet/eth-walletconnect-keyring'; -import QRHardwareKeyring from '../service/keyring/eth-keystone-keyring'; import eventBus from '@/eventBus'; import { setPageStateCacheWhenPopupClose, @@ -55,17 +51,15 @@ import GnosisKeyring, { TransactionBuiltEvent, TransactionConfirmedEvent, } from '../service/keyring/eth-gnosis-keyring'; +import KeystoneKeyring, { + AcquireMemeStoreData, + MemStoreDataReady, +} from '../service/keyring/eth-keystone-keyring'; const stashKeyrings: Record = {}; export class WalletController extends BaseController { openapi = openapiService; - qrHardwareKeyring: QRHardwareKeyring; - - constructor() { - super(); - this.qrHardwareKeyring = new QRHardwareKeyring(); - } /* wallet */ boot = (password) => keyringService.boot(password); @@ -980,22 +974,69 @@ export class WalletController extends BaseController { return stashKeyringId; }; - submitQRHardwareCryptoHDKey = () => { - let keyring, isNewKey; + 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 QRCodeKeyring = keyringService.getKeyringClassForType(keyringType); - keyring = new QRCodeKeyring(); + const keystoneKeyring = keyringService.getKeyringClassForType( + keyringType + ); + keyring = new keystoneKeyring(); stashKeyringId = Object.values(stashKeyrings).length; stashKeyrings[stashKeyringId] = keyring; } - // TODO + 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, @@ -1027,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 index 99fc855215b..a36fb0a5498 100644 --- a/src/background/service/keyring/eth-keystone-keyring.ts +++ b/src/background/service/keyring/eth-keystone-keyring.ts @@ -3,8 +3,31 @@ import { toChecksumAddress } from 'ethereumjs-util'; 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) { diff --git a/src/constant/index.ts b/src/constant/index.ts index 2ff7367468e..3b83241ad8b 100644 --- a/src/constant/index.ts +++ b/src/constant/index.ts @@ -611,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 = { @@ -634,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: { @@ -656,9 +658,9 @@ export const HARDWARE_KEYRING_TYPES = { type: 'GridPlus Hardware', brandName: 'GridPlus', }, - KeyStone: { + Keystone: { type: 'QR Hardware Wallet Device', - brandName: 'KeyStone', + brandName: 'Keystone', }, }; @@ -814,6 +816,9 @@ export const EVENTS = { TX_BUILT: 'TransactionBuilt', TX_CONFIRMED: 'TransactionConfirmed', }, + QRHARDWARE: { + ACQUIRE_MEMSTORE_SUCCEED: 'ACQUIRE_MEMSTORE_SUCCEED', + }, }; export enum WALLET_BRAND_TYPES { @@ -905,7 +910,7 @@ export const WALLET_BRAND_CONTENT = { name: 'Keystone', brand: WALLET_BRAND_TYPES.KEYSTONE, icon: LogoKeystone, - image: LogoKeystoneWithBorder, + image: LogoKeystone, connectType: BRAND_WALLET_CONNECT_TYPE.QRCodeBase, }, [WALLET_BRAND_TYPES.LEDGER]: { @@ -975,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 = { @@ -986,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, @@ -1002,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/component/AddAddressOptions/index.tsx b/src/ui/component/AddAddressOptions/index.tsx index c795f6dcea9..5e75e5eefb6 100644 --- a/src/ui/component/AddAddressOptions/index.tsx +++ b/src/ui/component/AddAddressOptions/index.tsx @@ -23,6 +23,7 @@ import { } from 'consts'; import clsx from 'clsx'; + const normaltype: string[] = [ 'createAddress', 'addWatchMode', @@ -37,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); @@ -47,6 +49,12 @@ 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); }; @@ -69,12 +77,21 @@ const AddAddressOptions = () => { pathname: '/import/gnosis', }); } else if (item.connectType === BRAND_WALLET_CONNECT_TYPE.QRCodeBase) { - history.push({ - pathname: '/import/qrcode', - state: { - brand: item.brand, - }, - }); + 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', 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 6e50707beea..94dee0232d3 100644 --- a/src/ui/component/QRCodeReader/index.tsx +++ b/src/ui/component/QRCodeReader/index.tsx @@ -1,6 +1,7 @@ -import React, { useEffect, useRef } from 'react'; +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; @@ -15,48 +16,60 @@ const QRCodeReader = ({ onError, width = 100, height = 100, - isUR = false, }: 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; - const devices = await BrowserQRCodeReader.listVideoInputDevices(); - await reader.decodeFromVideoDevice( - devices[0].deviceId, - videoEl.current!, - (result, error) => { - if (error) return; - if (result) { - onSuccess(result.getText()); - } - } - ); - // console.log('result', result); - // 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 index a21502d740a..a71caaa4981 100644 --- a/src/ui/views/ImportQRCodeBase/index.tsx +++ b/src/ui/views/ImportQRCodeBase/index.tsx @@ -1,15 +1,17 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Form } from 'antd'; import { useHistory } from 'react-router-dom'; -import { composeInitialProps, useTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import { URDecoder } from '@ngraveio/bc-ur'; import QRCodeReader from 'ui/component/QRCodeReader'; import { StrayPageWithButton } from 'ui/component'; -import { useWallet, useWalletRequest } from 'ui/utils'; +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(); @@ -17,23 +19,50 @@ const ImportQRCodeBase = () => { const wallet = useWallet(); const [form] = Form.useForm(); const decoder = useRef(new URDecoder()); + const [errorMessage, setErrorMessage] = useState(''); + const [scan, setScan] = useState(true); - const handleScanQRCodeSuccess = (data) => { - decoder.current.receivePart(data); - if (decoder.current.isComplete()) { - const result = decoder.current.resultUR(); - result.cbor.toString('hex'); - /* TODO: - const stashKeyringId = await wallet.submitQRHardwareCryptoHDKey(); + 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: '/import/select-address', + pathname: '/popup/import/select-address', state: { - keyring: HARDWARE_KEYRING_TYPES.KeyStone.type, - path: currentPath, - keyringId + 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.' + ) + ); } }; @@ -60,6 +89,10 @@ const ImportQRCodeBase = () => { }; }, []); + const handleScan = () => { + setErrorMessage(''); + setScan(true); + }; return ( {
- + {scan && ( + + )} + {showErrorChecker && ( + + )}
); diff --git a/src/ui/views/ImportQRCodeBase/style.less b/src/ui/views/ImportQRCodeBase/style.less index 8eca583f0d9..2c6a26a0d5c 100644 --- a/src/ui/views/ImportQRCodeBase/style.less +++ b/src/ui/views/ImportQRCodeBase/style.less @@ -1,5 +1,5 @@ @import '../../style/var.less'; -.import-qrcode { +.import-qrcode, .qr-hardware-sign { .goback { position: absolute; color: #ffffff; 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/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/src/utils/transaction.ts b/src/utils/transaction.ts index 552ccc77ec0..2ca6fe3380d 100644 --- a/src/utils/transaction.ts +++ b/src/utils/transaction.ts @@ -22,7 +22,7 @@ export const convert1559ToLegacy = (tx) => { to: tx.to, value: tx.value, data: tx.data, - gas: tx.gas, + gasLimit: tx.gas, gasPrice: tx.maxFeePerGas, nonce: tx.nonce, }; 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== From 7441c373f9947def009fe66f1f67fcc2b77ede14 Mon Sep 17 00:00:00 2001 From: amalia Date: Tue, 22 Mar 2022 16:34:45 +0800 Subject: [PATCH 3/4] Fix: fix keystone wallet id --- src/constant/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constant/index.ts b/src/constant/index.ts index 3b83241ad8b..70703dc17c8 100644 --- a/src/constant/index.ts +++ b/src/constant/index.ts @@ -906,7 +906,7 @@ export const WALLET_BRAND_CONTENT = { connectType: BRAND_WALLET_CONNECT_TYPE.WalletConnect, }, [WALLET_BRAND_TYPES.KEYSTONE]: { - id: 14, + id: 15, name: 'Keystone', brand: WALLET_BRAND_TYPES.KEYSTONE, icon: LogoKeystone, From e1c2bd6d755db0da94110fefe6140b209bae75d8 Mon Sep 17 00:00:00 2001 From: amalia Date: Tue, 22 Mar 2022 17:30:23 +0800 Subject: [PATCH 4/4] Fix: Rollback legacy transaction change --- src/background/controller/provider/controller.ts | 2 +- .../service/keyring/eth-keystone-keyring.ts | 12 ++++++++++++ src/utils/transaction.ts | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/background/controller/provider/controller.ts b/src/background/controller/provider/controller.ts index 93e74323739..be2d1a0017f 100644 --- a/src/background/controller/provider/controller.ts +++ b/src/background/controller/provider/controller.ts @@ -1,8 +1,8 @@ import * as Sentry from '@sentry/browser'; +import Transaction from 'ethereumjs-tx'; import Common, { Hardfork } from '@ethereumjs/common'; import { TransactionFactory, - Transaction, FeeMarketEIP1559Transaction, } from '@ethereumjs/tx'; import { ethers } from 'ethers'; diff --git a/src/background/service/keyring/eth-keystone-keyring.ts b/src/background/service/keyring/eth-keystone-keyring.ts index a36fb0a5498..1494a64d515 100644 --- a/src/background/service/keyring/eth-keystone-keyring.ts +++ b/src/background/service/keyring/eth-keystone-keyring.ts @@ -1,5 +1,7 @@ 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'; @@ -49,4 +51,14 @@ export default class KeystoneKeyring extends MetaMaskKeyring { } 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/utils/transaction.ts b/src/utils/transaction.ts index 2ca6fe3380d..552ccc77ec0 100644 --- a/src/utils/transaction.ts +++ b/src/utils/transaction.ts @@ -22,7 +22,7 @@ export const convert1559ToLegacy = (tx) => { to: tx.to, value: tx.value, data: tx.data, - gasLimit: tx.gas, + gas: tx.gas, gasPrice: tx.maxFeePerGas, nonce: tx.nonce, };