diff --git a/package.json b/package.json index cedb4ccb0be..b352cfc4d43 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@rabby-wallet/eth-watch-keyring": "1.0.0", "@rabby-wallet/gnosis-sdk": "1.3.9", "@rabby-wallet/page-provider": "0.4.2", - "@rabby-wallet/rabby-action": "0.1.4", + "@rabby-wallet/rabby-action": "0.1.8", "@rabby-wallet/rabby-api": "0.8.5", "@rabby-wallet/rabby-security-engine": "2.0.7", "@rabby-wallet/rabby-swap": "0.0.42", diff --git a/patches/@ledgerhq+hw-app-eth+6.39.0.patch b/patches/@ledgerhq+hw-app-eth+6.39.0.patch new file mode 100644 index 00000000000..8dbf7674b02 --- /dev/null +++ b/patches/@ledgerhq+hw-app-eth+6.39.0.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/contracts.js b/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/contracts.js +index 806a539..b488b25 100644 +--- a/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/contracts.js ++++ b/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/contracts.js +@@ -19,7 +19,7 @@ export const loadInfosForContractMethod = (contractAddress, selector, chainId, u + if (pluginBaseURL) { + const url = `${pluginBaseURL}/plugins/ethereum.json`; + data = yield axios +- .get(`${pluginBaseURL}/plugins/ethereum.json`) ++ .get(`${pluginBaseURL}/plugins/ethereum.json`, { timeout: 5000 }) + .then(r => r.data) + .catch(e => { + log("error", "could not fetch from " + url + ": " + String(e)); +diff --git a/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/erc20.js b/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/erc20.js +index 16a933e..a2a0f1a 100644 +--- a/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/erc20.js ++++ b/node_modules/@ledgerhq/hw-app-eth/lib-es/services/ledger/erc20.js +@@ -21,7 +21,7 @@ export const findERC20SignaturesInfo = (userLoadConfig, chainId) => __awaiter(vo + return null; + const url = `${cryptoassetsBaseURL}/evm/${chainId}/erc20-signatures.json`; + const blob = yield axios +- .get(url) ++ .get(url, { timeout: 5000 }) + .then(({ data }) => { + if (!data || typeof data !== "string") { + throw new Error(`ERC20 signatures for chainId ${chainId} file is malformed ${url}`); diff --git a/patches/@metamask+eth-sig-util+5.1.0.patch b/patches/@metamask+eth-sig-util+5.1.0.patch index 6e24543cc59..38062ce9957 100644 --- a/patches/@metamask+eth-sig-util+5.1.0.patch +++ b/patches/@metamask+eth-sig-util+5.1.0.patch @@ -1,3 +1,25 @@ +diff --git a/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.d.ts b/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.d.ts +index 45a660e..9b3f105 100644 +--- a/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.d.ts ++++ b/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.d.ts +@@ -20,3 +20,5 @@ export declare function parseNumber(arg: string | number | BN): BN; + * @param values + */ + export declare function rawEncode(types: string[], values: (BN | Buffer | string | number | string[] | number[])[]): Buffer; ++ ++export declare function encodeSingle(type: string, arg: BN | Buffer | string | number | string[] | number[]): Buffer; +diff --git a/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.js b/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.js +index 1a11fec..7c78143 100644 +--- a/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.js ++++ b/node_modules/@metamask/eth-sig-util/dist/ethereumjs-abi-utils.js +@@ -352,6 +352,7 @@ function encodeSingle(type, arg) { + } + throw new Error(`Unsupported or invalid type: ${JSON.stringify(type)}`); + } ++exports.encodeSingle = encodeSingle; + // Is a type dynamic? + /** + * @param type diff --git a/node_modules/@metamask/eth-sig-util/dist/index.d.ts b/node_modules/@metamask/eth-sig-util/dist/index.d.ts index 48ddf59..58c9ae7 100644 --- a/node_modules/@metamask/eth-sig-util/dist/index.d.ts diff --git a/src/background/service/keyring/eth-ledger-keyring.ts b/src/background/service/keyring/eth-ledger-keyring.ts index c7d4b178e3c..cffc3f56959 100644 --- a/src/background/service/keyring/eth-ledger-keyring.ts +++ b/src/background/service/keyring/eth-ledger-keyring.ts @@ -15,6 +15,12 @@ import { SignHelper, LedgerHDPathType } from './helper'; const type = 'Ledger Hardware'; import HDPathType = LedgerHDPathType; +import Browser from 'webextension-polyfill'; +import { + LedgerAction, + OffscreenCommunicationTarget, +} from '@/constant/offscreen-communication'; +import { isManifestV3 } from '@/utils/env'; const HD_PATH_BASE = { [HDPathType.BIP44]: "m/44'/60'/0'/0", @@ -70,6 +76,17 @@ class LedgerBridgeKeyring { this.app = null; this.usedHDPathTypeList = {}; this.deserialize(opts); + + if (isManifestV3) { + Browser.runtime.onMessage.addListener((request) => { + if ( + request.target === OffscreenCommunicationTarget.extension && + request.event === LedgerAction.ledgerDeviceDisconnect + ) { + this.cleanUp(); + } + }); + } } serialize() { @@ -145,7 +162,9 @@ class LedgerBridgeKeyring { async cleanUp() { this.app = null; - if (this.transport) this.transport.close(); + if (this.transport) { + await this.transport.close(); + } this.transport = null; } diff --git a/src/constant/offscreen-communication.ts b/src/constant/offscreen-communication.ts index ac0f369fdd1..03ae7bcc7df 100644 --- a/src/constant/offscreen-communication.ts +++ b/src/constant/offscreen-communication.ts @@ -49,3 +49,7 @@ export enum LatticeAction { export enum KnownOrigins { lattice = 'https://lattice.gridplus.io', } + +export enum LedgerAction { + ledgerDeviceDisconnect = 'ledger-device-disconnect', +} diff --git a/src/offscreen/scripts/ledger.ts b/src/offscreen/scripts/ledger.ts new file mode 100644 index 00000000000..54f5f01b813 --- /dev/null +++ b/src/offscreen/scripts/ledger.ts @@ -0,0 +1,17 @@ +import { + LedgerAction, + OffscreenCommunicationTarget, +} from '@/constant/offscreen-communication'; +import { ledgerUSBVendorId } from '@ledgerhq/devices'; +import Browser from 'webextension-polyfill'; + +export function initLedger() { + navigator.hid.addEventListener('disconnect', ({ device }) => { + if (device.vendorId === ledgerUSBVendorId) { + Browser.runtime.sendMessage({ + target: OffscreenCommunicationTarget.extension, + event: LedgerAction.ledgerDeviceDisconnect, + }); + } + }); +} diff --git a/src/offscreen/scripts/offscreen.ts b/src/offscreen/scripts/offscreen.ts index f846b4a4885..3b6b7cc27fe 100644 --- a/src/offscreen/scripts/offscreen.ts +++ b/src/offscreen/scripts/offscreen.ts @@ -1,12 +1,14 @@ import { initBitBox02 } from './bitbox02'; import { initImKey } from './imkey'; import initLattice from './lattice'; +import { initLedger } from './ledger'; import { initOneKey } from './onekey'; initImKey(); initOneKey(); initBitBox02(); initLattice(); +initLedger(); // keep alive when ui page is open let pageCount = 0; diff --git a/src/ui/views/Approval/components/TypedDataActions/utils.ts b/src/ui/views/Approval/components/TypedDataActions/utils.ts index 6aa0cfcbdbd..b2f912355d4 100644 --- a/src/ui/views/Approval/components/TypedDataActions/utils.ts +++ b/src/ui/views/Approval/components/TypedDataActions/utils.ts @@ -1,11 +1,11 @@ -import { getArrayType, isArrayType } from '@metamask/abi-utils/dist/parsers'; -import { BigNumber as EthersBigNumber } from 'ethers'; -import { isStrictHexString, add0x } from 'ui/utils/address'; import i18n from '@/i18n'; -import { parseNumber } from '@metamask/eth-sig-util'; -import { padStart } from 'lodash'; import { ParsedTypedDataActionData } from '@rabby-wallet/rabby-action'; import { getActionTypeText as getTransactionActionTypeText } from '../Actions/utils'; +import { encodeSingle } from '@metamask/eth-sig-util'; +import { bufferToHex } from 'ethereumjs-util'; +import { hexToString } from 'web3-utils'; +import BigNumber from 'bignumber.js'; + export const getActionTypeText = (data: ParsedTypedDataActionData | null) => { const { t } = i18n; @@ -74,47 +74,80 @@ export function normalizeTypeData(data: { message: Record; }) { try { - const { types, primaryType, domain, message } = data; - const domainTypes = types.EIP712Domain; - const messageTypes = types[primaryType]; - domainTypes.forEach((item) => { - const { name, type } = item; - domain[name] = normalizeValue(type, domain[name]); - }); - messageTypes.forEach((item) => { - const { name, type } = item; - message[name] = normalizeValue(type, message[name]); - }); - return { types, primaryType, domain, message }; + return parseSignTypedData(data); } catch (e) { return data; } } +function parseSignTypedData(typedData: { + primaryType: string; + types: Record; + domain: Record; + message: Record; +}) { + const { domain, message, types, primaryType } = typedData; -export function normalizeValue(type: string, value: unknown): any { - if (isArrayType(type) && Array.isArray(value)) { - const [innerType] = getArrayType(type); - return value.map((item) => normalizeValue(innerType, item)); - } + function parseAndDecode(data: any, dataType: string) { + if (dataType.endsWith('[]')) { + const elementType = dataType.slice(0, -2); + const decodedArray: any[] = []; + for (const element of data) { + decodedArray.push(parseAndDecode(element, elementType)); + } + return decodedArray; + } else if (types[dataType]) { + for (const field of types[dataType]) { + const { name, type } = field; + data[name] = parseAndDecode(data[name], type); + } + return data; + } else { + const encodedBuffer = encodeSingle(dataType, data); + let encodedHexValue = `0x${encodedBuffer.toString('hex')}`; + switch (dataType) { + case 'string': { + const encodedLengthSize = 32; // uint256 length + const lengthBuffer = encodedBuffer.slice(0, encodedLengthSize); + const originalArgLength = parseInt(lengthBuffer.toString('hex'), 16); + const fullArgWithPadding = encodedBuffer.slice(encodedLengthSize); + const originalArg = fullArgWithPadding.slice(0, originalArgLength); + encodedHexValue = bufferToHex(originalArg); + break; + } + case 'address': + encodedHexValue = `0x${encodedBuffer.slice(12).toString('hex')}`; + break; + case 'bool': + if (new BigNumber(encodedHexValue).eq(0)) { + return false; + } else if (new BigNumber(encodedHexValue).eq(1)) { + return true; + } + break; + default: + // NOTHING + } + if ( + dataType.startsWith('uint') || + dataType.startsWith('int') || + dataType.startsWith('ufixed') || + dataType.startsWith('fixed') + ) { + return new BigNumber(encodedHexValue).toFixed(); + } + if (dataType === 'string') { + return hexToString(encodedHexValue); + } - if (type === 'address') { - let address = value as string; - if (typeof value === 'string' && !/^(0x|0X)/.test(value)) { - address = EthersBigNumber.from(value).toHexString(); - } else if (isStrictHexString(value)) { - address = add0x(value); - } - try { - const parseAddress = padStart( - parseNumber(address).toString('hex'), - 40, - '0' - ); - return `0x${parseAddress}`; - } catch (e) { - return address; + return encodedHexValue; } } - return value; + for (const { name, type } of types.EIP712Domain) { + domain[name] = parseAndDecode(domain[name], type); + } + + typedData.message = parseAndDecode(message, primaryType); + + return typedData; } diff --git a/yarn.lock b/yarn.lock index dc66542e19e..e7476d6c0d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1610,16 +1610,11 @@ resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f" integrity sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ== -"@debank/common@0.3.60": +"@debank/common@0.3.60", "@debank/common@^0.3.0": version "0.3.60" resolved "https://registry.yarnpkg.com/@debank/common/-/common-0.3.60.tgz#17ca72e4cde1fb6487ca1ed0691d89cd6b6692f9" integrity sha512-SaFTXvnn2aYQTPB3B8YD/AhHYwdMRiqu7jCkYV6c/Sd3MmxzJR3v+Kd4jlQniKfEdB21+1vMb0NlzHw/gyemlw== -"@debank/common@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@debank/common/-/common-0.3.0.tgz#9c67288816d62e3548e0c2193ec4cd73758f5b26" - integrity sha512-PWdFuKuqDtGdYALFToDQQCyas3Tgz1tXXlir6UVTVHz2MPi4uK6ZK8UUNrWdU6lncHNvcRsNf3Xamk0Ga9jxeg== - "@debank/festats@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@debank/festats/-/festats-1.0.1.tgz#8f76b0919a6c8a7d938a42946272abaff2b971be" @@ -4776,10 +4771,10 @@ sinon-chrome "^3.0.1" webextension-polyfill "0.7.0" -"@rabby-wallet/rabby-action@0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-action/-/rabby-action-0.1.4.tgz#c82e7c8b538b7dfd94506c4f89f78aca7ca880ef" - integrity sha512-6ttnlpGHcO2v/qyYo8epBbSutfS9OZXfXr9mfapuveoBvzqUwvE6ej3bsmdtc+qFhFeH7HeiwASpY6xaTM4E1w== +"@rabby-wallet/rabby-action@0.1.8": + version "0.1.8" + resolved "https://registry.yarnpkg.com/@rabby-wallet/rabby-action/-/rabby-action-0.1.8.tgz#05b258b628a224d51dc471e93117d4106024a916" + integrity sha512-K0euVX55tW2mbnudm3bHPAbbLYlnDQD5PgAvM1mwPPFHhF/Ve/U5MYdg1XERJPxkoXKqiVti46idFFB+s8sv7A== "@rabby-wallet/rabby-api@0.8.5": version "0.8.5"