From ec02eda2bfe52f186e078a2c7290ce9ce89e8e93 Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Wed, 30 Jul 2025 22:47:16 +0800 Subject: [PATCH 01/10] feat: support onekey --- packages/keyring-eth-onekey/LICENSE | 15 + packages/keyring-eth-onekey/README.md | 41 ++ packages/keyring-eth-onekey/jest.config.js | 32 + packages/keyring-eth-onekey/package.json | 107 +++ packages/keyring-eth-onekey/src/constants.ts | 1 + packages/keyring-eth-onekey/src/index.ts | 4 + .../keyring-eth-onekey/src/onekey-bridge.ts | 67 ++ .../keyring-eth-onekey/src/onekey-keyring.ts | 672 ++++++++++++++++++ .../src/onekey-web-bridge.ts | 324 +++++++++ .../keyring-eth-onekey/tsconfig.build.json | 16 + packages/keyring-eth-onekey/tsconfig.json | 14 + packages/keyring-eth-onekey/typedoc.json | 6 + yarn.lock | 328 ++++++++- 13 files changed, 1620 insertions(+), 7 deletions(-) create mode 100644 packages/keyring-eth-onekey/LICENSE create mode 100644 packages/keyring-eth-onekey/README.md create mode 100644 packages/keyring-eth-onekey/jest.config.js create mode 100644 packages/keyring-eth-onekey/package.json create mode 100644 packages/keyring-eth-onekey/src/constants.ts create mode 100644 packages/keyring-eth-onekey/src/index.ts create mode 100644 packages/keyring-eth-onekey/src/onekey-bridge.ts create mode 100644 packages/keyring-eth-onekey/src/onekey-keyring.ts create mode 100644 packages/keyring-eth-onekey/src/onekey-web-bridge.ts create mode 100644 packages/keyring-eth-onekey/tsconfig.build.json create mode 100644 packages/keyring-eth-onekey/tsconfig.json create mode 100644 packages/keyring-eth-onekey/typedoc.json diff --git a/packages/keyring-eth-onekey/LICENSE b/packages/keyring-eth-onekey/LICENSE new file mode 100644 index 00000000..b5ed1b9c --- /dev/null +++ b/packages/keyring-eth-onekey/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2020 MetaMask + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/packages/keyring-eth-onekey/README.md b/packages/keyring-eth-onekey/README.md new file mode 100644 index 00000000..34cb0357 --- /dev/null +++ b/packages/keyring-eth-onekey/README.md @@ -0,0 +1,41 @@ +# eth-onekey-bridge-keyring + +An implementation of MetaMask's [Keyring interface](https://github.com/MetaMask/eth-simple-keyring#the-keyring-class-protocol), that uses a OneKey hardware wallet for all cryptographic operations. + +In most regards, it works in the same way as +[eth-hd-keyring](https://github.com/MetaMask/eth-hd-keyring), but using a OneKey +device. However there are a number of differences: + +- Because the keys are stored in the device, operations that rely on the device + will fail if there is no OneKey device attached, or a different OneKey device + is attached. + +- It does not support the `signMessage`, `signTypedData` or `exportAccount` + methods, because OneKey devices do not support these operations. + +- Because extensions have limited access to browser features, there's no easy way to interact wth the OneKey Hardware wallet from the MetaMask extension. This library implements a workaround to those restrictions by injecting (on demand) an iframe to the background page of the extension, + +## Usage + +In addition to all the known methods from the [Keyring class protocol](https://github.com/MetaMask/eth-simple-keyring#the-keyring-class-protocol), +there are a few others: + +- **isUnlocked** : Returns true if we have the public key in memory, which allows to generate the list of accounts at any time + +- **unlock** : Connects to the OneKey device and exports the extended public key, which is later used to read the available ethereum addresses inside the OneKey account. + +- **setAccountToUnlock** : the index of the account that you want to unlock in order to use with the signTransaction and signPersonalMessage methods + +- **getFirstPage** : returns the first ordered set of accounts from the OneKey account + +- **getNextPage** : returns the next ordered set of accounts from the OneKey account based on the current page + +- **getPreviousPage** : returns the previous ordered set of accounts from the OneKey account based on the current page + +- **forgetDevice** : removes all the device info from memory so the next interaction with the keyring will prompt the user to connect the OneKey device and export the account information + +## Testing and Linting + +Run `yarn test` to run the tests once. To run tests on file changes, run `yarn test:watch`. + +Run `yarn lint` to run the linter, or run `yarn lint:fix` to run the linter and fix any automatically fixable issues. diff --git a/packages/keyring-eth-onekey/jest.config.js b/packages/keyring-eth-onekey/jest.config.js new file mode 100644 index 00000000..0bb9dd48 --- /dev/null +++ b/packages/keyring-eth-onekey/jest.config.js @@ -0,0 +1,32 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +const merge = require('deepmerge'); +const path = require('path'); + +const baseConfig = require('../../jest.config.packages'); + +const displayName = path.basename(__dirname); + +module.exports = merge(baseConfig, { + // The display name when running multiple projects + displayName, + + // An array of regexp pattern strings used to skip coverage collection + coveragePathIgnorePatterns: ['./src/tests'], + + // The glob patterns Jest uses to detect test files + testMatch: ['**/*.test.[jt]s?(x)'], + + // An object that configures minimum threshold enforcement for coverage results + coverageThreshold: { + global: { + branches: 52.38, + functions: 91.22, + lines: 90.15, + statements: 90.35, + }, + }, +}); diff --git a/packages/keyring-eth-onekey/package.json b/packages/keyring-eth-onekey/package.json new file mode 100644 index 00000000..36bd6194 --- /dev/null +++ b/packages/keyring-eth-onekey/package.json @@ -0,0 +1,107 @@ +{ + "name": "@metamask/eth-onekey-keyring", + "version": "1.0.0", + "description": "A MetaMask compatible keyring, for onekey hardware wallets", + "keywords": [ + "ethereum", + "keyring", + "onekey", + "metamask" + ], + "homepage": "https://github.com/MetaMask/accounts/blob/main/packages/keyring-eth-onekey/README.md", + "bugs": { + "url": "https://github.com/MetaMask/accounts/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/accounts.git" + }, + "license": "ISC", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.cts", + "files": [ + "dist/" + ], + "scripts": { + "build": "ts-bridge --project tsconfig.build.json --no-references", + "build:clean": "yarn build --clean", + "build:docs": "typedoc", + "changelog:update": "../../scripts/update-changelog.sh @metamask/eth-trezor-keyring", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eth-trezor-keyring", + "publish:preview": "yarn npm publish --tag preview", + "test": "jest && jest-it-up", + "test:clean": "jest --clearCache", + "test:watch": "jest --watch" + }, + "dependencies": { + "@ethereumjs/tx": "^5.4.0", + "@ethereumjs/util": "^9.1.0", + "@metamask/eth-sig-util": "^8.2.0", + "@metamask/utils": "^11.1.0", + "@noble/hashes": "^1.7.0", + "@onekeyfe/hd-core": "^1.1.1", + "@onekeyfe/hd-shared": "^1.1.1", + "@onekeyfe/hd-transport": "^1.1.1", + "@onekeyfe/hd-web-sdk": "^1.1.1", + "bytebuffer": "^5.0.1", + "tslib": "^2.6.2" + }, + "devDependencies": { + "@ethereumjs/common": "^4.4.0", + "@lavamoat/allow-scripts": "^3.2.1", + "@lavamoat/preinstall-always-fail": "^2.1.0", + "@metamask/auto-changelog": "^3.4.4", + "@metamask/keyring-utils": "workspace:^", + "@ts-bridge/cli": "^0.6.3", + "@types/ethereumjs-tx": "^1.0.1", + "@types/jest": "^29.5.12", + "@types/node": "^20.12.12", + "@types/sinon": "^17.0.3", + "@types/w3c-web-usb": "^1.0.6", + "deepmerge": "^4.2.2", + "depcheck": "^1.4.7", + "ethereumjs-tx": "^1.3.7", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.7.0", + "jest-it-up": "^3.1.0", + "sinon": "^19.0.2", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.2", + "typedoc": "^0.25.13", + "typescript": "~5.6.3" + }, + "engines": { + "node": "^18.18 || >=20" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "installConfig": { + "hoistingLimits": "workspaces" + }, + "lavamoat": { + "allowScripts": { + "@lavamoat/preinstall-always-fail": false, + "ethereumjs-tx>ethereumjs-util>keccak": false, + "ethereumjs-tx>ethereumjs-util>secp256k1": false, + "hdkey>secp256k1": false, + "ethereumjs-tx>ethereumjs-util>ethereum-cryptography>keccak": false, + "ethereumjs-tx>ethereumjs-util>ethereum-cryptography>secp256k1": false, + "@onekeyfe/hd-core>@onekeyfe/hd-transport>protobufjs": false, + "@onekeyfe/hd-web-sdk>@onekeyfe/hd-core>@onekeyfe/hd-transport>protobufjs": false + } + } +} diff --git a/packages/keyring-eth-onekey/src/constants.ts b/packages/keyring-eth-onekey/src/constants.ts new file mode 100644 index 00000000..e0114f0f --- /dev/null +++ b/packages/keyring-eth-onekey/src/constants.ts @@ -0,0 +1 @@ +export const ONEKEY_HARDWARE_UI_EVENT = 'onekey-hardware-ui-event'; diff --git a/packages/keyring-eth-onekey/src/index.ts b/packages/keyring-eth-onekey/src/index.ts new file mode 100644 index 00000000..9311de0e --- /dev/null +++ b/packages/keyring-eth-onekey/src/index.ts @@ -0,0 +1,4 @@ +export * from './onekey-keyring'; +export type * from './onekey-bridge'; +export * from './onekey-web-bridge'; +export * from './constants'; diff --git a/packages/keyring-eth-onekey/src/onekey-bridge.ts b/packages/keyring-eth-onekey/src/onekey-bridge.ts new file mode 100644 index 00000000..6f6756f0 --- /dev/null +++ b/packages/keyring-eth-onekey/src/onekey-bridge.ts @@ -0,0 +1,67 @@ +/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { + ConnectSettings, + Params, + EVMSignedTx, + EVMSignTransactionParams, + EVMSignMessageParams, + EVMSignTypedDataParams, + EVMGetPublicKeyParams, + Features, +} from '@onekeyfe/hd-core'; +import type { EthereumMessageSignature } from '@onekeyfe/hd-transport'; + +type Unsuccessful = { + success: false; + payload: { + error: string; + code?: string | number; + }; +}; +type Success = { + success: true; + payload: T; +}; +type Response = Promise | Unsuccessful>; + +export type OneKeyBridge = { + model?: string; + + on(event: string, callback: (event: any) => void): void; + + off(event: string): void; + + init(settings: Partial): Promise; + + dispose(): Promise; + + updateTransportMethod(transportType: string): Promise; + + getDeviceFeatures(): Response; + + // OneKeySdk.getPublicKey has two overloads + // It is not possible to extract them from the library using utility types + getPublicKey( + params: Params, + ): Response<{ publicKey: string; chainCode: string }>; + + batchGetPublicKey( + params: Params & { bundle: EVMGetPublicKeyParams[] }, + ): Response<{ pub: string }[]>; + + getPassphraseState(): Response; + + ethereumSignTransaction( + params: Params, + ): Response; + + ethereumSignMessage( + params: Params, + ): Response; + + ethereumSignTypedData( + params: Params, + ): Response; +}; diff --git a/packages/keyring-eth-onekey/src/onekey-keyring.ts b/packages/keyring-eth-onekey/src/onekey-keyring.ts new file mode 100644 index 00000000..8d729f04 --- /dev/null +++ b/packages/keyring-eth-onekey/src/onekey-keyring.ts @@ -0,0 +1,672 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { TypedTransaction, TypedTxData } from '@ethereumjs/tx'; +import { TransactionFactory } from '@ethereumjs/tx'; +import * as ethUtil from '@ethereumjs/util'; +import type { MessageTypes, TypedMessage } from '@metamask/eth-sig-util'; +import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; +import type { + ConnectSettings, + EthereumSignTypedDataMessage, + EthereumSignTypedDataTypes, + EVMGetPublicKeyParams, + EVMSignedTx, + EVMSignTransactionParams, +} from '@onekeyfe/hd-core'; +// eslint-disable-next-line @typescript-eslint/no-shadow, n/prefer-global/buffer +import { Buffer } from 'buffer'; +import type OldEthJsTransaction from 'ethereumjs-tx'; +import { EventEmitter } from 'events'; + +import { ONEKEY_HARDWARE_UI_EVENT } from './constants'; +import type { OneKeyBridge } from './onekey-bridge'; + +const pathBase = 'm'; +const defaultHdPath = `${pathBase}/44'/60'/0'/0`; +const keyringType = 'OneKey Hardware'; + +enum NetworkApiUrls { + Ropsten = 'https://api-ropsten.etherscan.io', + Kovan = 'https://api-kovan.etherscan.io', + Rinkeby = 'https://api-rinkeby.etherscan.io', + Mainnet = `https://api.etherscan.io`, +} + +export type AccountDetails = { + index?: number; + hdPath: string; + passphraseState?: string; +}; + +export type AccountPageEntry = { + address: string; + balance: number | null; + index: number; +}; + +export type AccountPage = AccountPageEntry[]; + +export type OneKeyControllerOptions = { + hdPath?: string; + accounts?: string[]; + accountDetails?: Readonly>; + page?: number; + passphraseState?: string; +}; + +export type OneKeyControllerState = { + hdPath: string; + accounts: readonly string[]; + accountDetails: Readonly>; + page: number; + passphraseState?: string; +}; + +/** + * Check if the given transaction is made with ethereumjs-tx or @ethereumjs/tx + * + * Transactions built with older versions of ethereumjs-tx have a + * getChainId method that newer versions do not. + * Older versions are mutable + * while newer versions default to being immutable. + * Expected shape and type + * of data for v, r and s differ (Buffer (old) vs BN (new)). + * + * @param tx - Transaction to check, instance of either ethereumjs-tx or @ethereumjs/tx. + * @returns Returns `true` if tx is an old-style ethereumjs-tx transaction. + */ +function isOldStyleEthereumjsTx( + tx: TypedTransaction | OldEthJsTransaction, +): tx is OldEthJsTransaction { + return 'getChainId' in tx && typeof tx.getChainId === 'function'; +} + +/** + * Check if the given value has a hex prefix. + * + * @param value - The value to check. + * @returns Returns `true` if the value has a hex prefix. + */ +function hasHexPrefix(value: string): boolean { + return value.startsWith('0x'); +} + +/** + * Add a hex prefix to the given value. + * + * @param value - The value to add a hex prefix to. + * @returns Returns the value with a hex prefix. + */ +function addHexPrefix(value: string): string { + if (hasHexPrefix(value)) { + return value; + } + return `0x${value}`; +} + +/** + * Check if the passphrase state is empty. + * + * @param passphraseState - The passphrase state to check. + * @returns Returns `true` if the passphrase state is empty. + */ +function isEmptyPassphrase(passphraseState: string | undefined): boolean { + return ( + passphraseState === null || + passphraseState === undefined || + passphraseState === '' + ); +} + +export class OneKeyKeyring extends EventEmitter { + readonly type: string = keyringType; + + static type: string = keyringType; + + page = 0; + + perPage = 5; + + unlockedAccount = 0; + + accounts: readonly string[] = []; + + accountDetails: Record = {}; + + passphraseState: string | undefined; + + hdPath = defaultHdPath; + + network: NetworkApiUrls = NetworkApiUrls.Mainnet; + + implementFullBIP44 = false; + + bridge: OneKeyBridge; + + constructor({ bridge }: { bridge: OneKeyBridge }) { + super(); + + if (!bridge) { + throw new Error('Bridge is a required dependency for the keyring'); + } + + this.bridge = bridge; + this.bridge.on(ONEKEY_HARDWARE_UI_EVENT, (_event: any) => { + this.emit(ONEKEY_HARDWARE_UI_EVENT, _event); + }); + } + + async init(settings: Partial): Promise { + return this.bridge.init(settings); + } + + async destroy(): Promise { + this.bridge.off(ONEKEY_HARDWARE_UI_EVENT); + return this.bridge.dispose(); + } + + async serialize(): Promise { + return Promise.resolve({ + hdPath: this.hdPath, + accounts: this.accounts, + accountDetails: this.accountDetails, + page: this.page, + }); + } + + async deserialize(opts: OneKeyControllerOptions = {}): Promise { + this.hdPath = opts.hdPath ?? defaultHdPath; + this.accounts = opts.accounts ?? []; + this.accountDetails = opts.accountDetails ?? {}; + this.page = opts.page ?? 0; + return Promise.resolve(); + } + + getModel(): string | undefined { + return this.bridge.model; + } + + setAccountToUnlock(index: number): void { + this.unlockedAccount = index; + } + + setHdPath(hdPath: string): void { + this.hdPath = hdPath; + } + + isUnlocked(): boolean { + return false; + } + + async unlock(): Promise { + const features = await this.bridge.getDeviceFeatures(); + if (features.success) { + if (features.payload.unlocked) { + return 'already unlocked'; + } + } + + return new Promise((resolve, reject) => { + // eslint-disable-next-line no-void + void this.bridge + .getPassphraseState() + .then((passphraseResponse) => { + if (passphraseResponse.success) { + this.passphraseState = passphraseResponse.payload; + } + if (!passphraseResponse.success) { + reject(new Error('getPassphraseState failed')); + return; + } + this.passphraseState = passphraseResponse.payload; + resolve('just unlocked'); + }) + .catch((error) => { + reject(new Error(error?.toString() || 'Unknown error')); + }); + }); + } + + async addAccounts(numberOfAccounts = 1): Promise { + await this.unlock().catch((error) => { + throw new Error(error?.toString() || 'Unknown error'); + }); + + return new Promise((resolve, reject) => { + const from = this.unlockedAccount; + const to = from + numberOfAccounts; + const newAccounts: string[] = []; + + const paths: string[] = []; + for (let i = from; i < to; i++) { + paths.push(this.#getPathForIndex(i)); + } + + // eslint-disable-next-line no-void + void this.#batchGetAddress(paths, this.passphraseState) + .then((addresses) => { + if (addresses.length !== paths.length) { + throw new Error('Unknown error'); + } + + for (let i = 0; i < paths.length; i++) { + const address = addresses[i]; + if (typeof address === 'undefined') { + throw new Error('Unknown error'); + } + if (!this.accounts.includes(address)) { + this.accounts = [...this.accounts, address]; + newAccounts.push(address); + } + if (!this.accountDetails[address]) { + this.accountDetails[address] = { + index: i, + hdPath: paths[i] ?? '', + passphraseState: this.passphraseState, + }; + } + this.page = 0; + } + resolve(newAccounts); + }) + .catch((error: Error) => { + reject(error); + }); + }); + } + + getName(): string { + return keyringType; + } + + async getFirstPage(): Promise { + this.page = 0; + return this.#getPage(1); + } + + async getNextPage(): Promise { + return this.#getPage(1); + } + + async getPreviousPage(): Promise { + return this.#getPage(-1); + } + + async getAccounts(): Promise { + return Promise.resolve(this.accounts.slice()); + } + + removeAccount(address: string): void { + const filteredAccounts = this.accounts.filter( + (a) => a.toLowerCase() !== address.toLowerCase(), + ); + + if (filteredAccounts.length === this.accounts.length) { + throw new Error(`Address ${address} not found in this keyring`); + } + + this.accounts = filteredAccounts; + delete this.accountDetails[ethUtil.toChecksumAddress(address)]; + } + + async updateTransportMethod( + transportType: ConnectSettings['env'], + ): Promise { + return this.bridge.updateTransportMethod(transportType); + } + + #normalize(buffer: Buffer): string { + return ethUtil.bytesToHex(buffer); + } + + /** + * Signs a transaction using OneKey. + * + * Accepts either an ethereumjs-tx or @ethereumjs/tx transaction, and returns + * the same type. + * + * @param address - Hex string address. + * @param tx - Instance of either new-style or old-style ethereumjs transaction. + * @returns The signed transaction, an instance of either new-style or old-style + * ethereumjs transaction. + */ + async signTransaction( + address: string, + tx: TypedTransaction | OldEthJsTransaction, + ): Promise { + if (isOldStyleEthereumjsTx(tx)) { + // In this version of ethereumjs-tx we must add the chainId in hex format + // to the initial v value. The chainId must be included in the serialized + // transaction which is only communicated to ethereumjs-tx in this + // value. In newer versions the chainId is communicated via the 'Common' + // object. + return this.#signTransaction( + address, + // @types/ethereumjs-tx and old ethereumjs-tx versions document + // this function return value as Buffer, but the actual + // Transaction._chainId will always be a number. + // See https://github.com/ethereumjs/ethereumjs-tx/blob/v1.3.7/index.js#L126 + tx.getChainId() as unknown as number, + tx, + (payload) => { + tx.v = Buffer.from(payload.v, 'hex'); + tx.r = Buffer.from(payload.r, 'hex'); + tx.s = Buffer.from(payload.s, 'hex'); + return tx; + }, + ); + } + return this.#signTransaction( + address, + Number(tx.common.chainId()), + tx, + (payload) => { + // Because tx will be immutable, first get a plain javascript object that + // represents the transaction. Using txData here as it aligns with the + // nomenclature of ethereumjs/tx. + const txData: TypedTxData = tx.toJSON(); + // The fromTxData utility expects a type to support transactions with a type other than 0 + txData.type = tx.type; + // The fromTxData utility expects v,r and s to be hex prefixed + txData.v = ethUtil.addHexPrefix(payload.v); + txData.r = ethUtil.addHexPrefix(payload.r); + txData.s = ethUtil.addHexPrefix(payload.s); + // Adopt the 'common' option from the original transaction and set the + // returned object to be frozen if the original is frozen. + return TransactionFactory.fromTxData(txData, { + common: tx.common, + freeze: Object.isFrozen(tx), + }); + }, + ); + } + + async #signTransaction( + address: string, + chainId: number, + tx: T, + handleSigning: (tx: EVMSignedTx) => T, + ): Promise { + let transaction: EVMSignTransactionParams['transaction']; + if (isOldStyleEthereumjsTx(tx)) { + // legacy transaction from ethereumjs-tx package has no .toJSON() function, + // so we need to convert to hex-strings manually manually + transaction = { + to: this.#normalize(tx.to), + value: this.#normalize(tx.value), + data: this.#normalize(tx.data), + chainId, + nonce: this.#normalize(tx.nonce), + gasLimit: this.#normalize(tx.gasLimit), + gasPrice: this.#normalize(tx.gasPrice), + }; + } else { + // new-style transaction from @ethereumjs/tx package + // we can just copy tx.toJSON() for everything except chainId, which must be a number + transaction = { + ...tx.toJSON(), + chainId, + to: this.#normalize(Buffer.from(tx.to?.bytes ?? [])), + } as EVMSignTransactionParams['transaction']; + } + + try { + const details = this.#accountDetailsFromAddress(address); + const response = await this.bridge.ethereumSignTransaction({ + path: details.hdPath, + passphraseState: details.passphraseState, + useEmptyPassphrase: isEmptyPassphrase(details.passphraseState), + transaction, + }); + if (response.success) { + const newOrMutatedTx = handleSigning(response.payload); + + const addressSignedWith = ethUtil.toChecksumAddress( + ethUtil.addHexPrefix( + newOrMutatedTx.getSenderAddress().toString('hex'), + ), + ); + const correctAddress = ethUtil.toChecksumAddress(address); + if (addressSignedWith !== correctAddress) { + throw new Error("signature doesn't match the right address"); + } + + return newOrMutatedTx; + } + throw new Error(response.payload?.error || 'Unknown error'); + } catch (error) { + throw new Error(error?.toString() ?? 'Unknown error'); + } + } + + async signMessage(withAccount: string, data: string): Promise { + return this.signPersonalMessage(withAccount, data); + } + + // For personal_sign, we need to prefix the message: + async signPersonalMessage( + withAccount: string, + message: string, + ): Promise { + return new Promise((resolve, reject) => { + const details = this.#accountDetailsFromAddress(withAccount); + this.bridge + .ethereumSignMessage({ + path: details.hdPath, + passphraseState: details.passphraseState, + useEmptyPassphrase: isEmptyPassphrase(details.passphraseState), + messageHex: ethUtil.stripHexPrefix(message), + }) + .then((response) => { + if (response.success) { + if ( + response.payload.address !== + ethUtil.toChecksumAddress(withAccount) + ) { + reject(new Error('signature doesnt match the right address')); + } + const signature = addHexPrefix(response.payload.signature); + // eslint-disable-next-line promise/no-multiple-resolved + resolve(signature); + } else { + reject(new Error(response.payload?.error || 'Unknown error')); + } + }) + .catch((error) => { + reject(new Error(error?.toString() || 'Unknown error')); + }); + }); + } + + // EIP-712 Sign Typed Data + async signTypedData( + address: string, + data: TypedMessage, + { version }: { version?: SignTypedDataVersion }, + ): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison + const useV4 = version === SignTypedDataVersion.V4; + const dataVersion = useV4 + ? SignTypedDataVersion.V4 + : SignTypedDataVersion.V3; + const typedData = TypedDataUtils.sanitizeData(data); + const domainHash = TypedDataUtils.hashStruct( + 'EIP712Domain', + typedData.domain, + typedData.types, + dataVersion, + ).toString('hex'); + const messageHash = TypedDataUtils.hashStruct( + typedData.primaryType as string, + typedData.message, + typedData.types, + dataVersion, + ).toString('hex'); + + const details = this.#accountDetailsFromAddress(address); + const response = await this.bridge.ethereumSignTypedData({ + path: details.hdPath, + passphraseState: details.passphraseState, + useEmptyPassphrase: isEmptyPassphrase(details.passphraseState), + data: data as EthereumSignTypedDataMessage, + domainHash, + messageHash, + metamaskV4Compat: Boolean(useV4), // eslint-disable-line camelcase + }); + + if (response.success) { + if (ethUtil.toChecksumAddress(address) !== response.payload.address) { + throw new Error('signature doesnt match the right address'); + } + return addHexPrefix(response.payload.signature); + } + + throw new Error(response.payload?.error || 'Unknown error'); + } + + exportAccount(): never { + throw new Error('Not supported on this device'); + } + + forgetDevice(): void { + this.accounts = []; + this.page = 0; + this.unlockedAccount = 0; + this.accountDetails = {}; + this.passphraseState = undefined; + } + + async getPassphraseState( + _index: number, + _hdPath: string, + ): Promise { + // TODO: implement + return Promise.resolve(undefined); + } + + async #getPage( + increment: number, + ): Promise<{ address: string; balance: number | null; index: number }[]> { + this.page += increment; + + if (this.page <= 0) { + this.page = 1; + } + + return new Promise((resolve, reject) => { + const from = (this.page - 1) * this.perPage; + const to = from + this.perPage; + + const accounts: { + address: string; + balance: number | null; + index: number; + }[] = []; + + this.unlock() + .then(async () => { + const paths = []; + for (let i = from; i < to; i++) { + paths.push(this.#getPathForIndex(i)); + } + + // this.passphraseState = passphraseState; + const addresses = await this.#batchGetAddress( + paths, + this.passphraseState, + ); + + if (addresses.length !== paths.length) { + throw new Error('Unknown error'); + } + for (let i = 0; i < paths.length; i++) { + const address = addresses[i]; + if (typeof address === 'undefined') { + throw new Error('Unknown error'); + } + accounts.push({ + address, + balance: null, + index: from + i, + }); + } + resolve(accounts); + }) + .catch((error) => { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors + reject(error); + }); + }); + } + + async #batchGetAddress( + paths: string[], + passphraseState: string | undefined, + ): Promise { + const batchParams: EVMGetPublicKeyParams[] = paths.map((path) => ({ + path, + showOnOneKey: false, + })); + + const response = await this.bridge.batchGetPublicKey({ + bundle: batchParams, + useBatch: true, + passphraseState, + useEmptyPassphrase: isEmptyPassphrase(passphraseState), + skipPassphraseCheck: true, + }); + if (response.success) { + return response.payload.map((item) => { + const address = ethUtil.publicToAddress( + Buffer.from(item.pub, 'hex'), + true, + ); + return ethUtil.toChecksumAddress( + addHexPrefix(Buffer.from(address).toString('hex')), + ); + }); + } + throw new Error(response.payload?.error || 'Unknown error'); + } + + #accountDetailsFromAddress(address: string): AccountDetails { + const checksummedAddress = ethUtil.toChecksumAddress(address); + const accountDetails = this.accountDetails[checksummedAddress]; + if (typeof accountDetails === 'undefined') { + throw new Error('Unknown address'); + } + return accountDetails; + } + + #getPathForIndex(index: number): string { + // Check if the path is BIP 44 (Ledger Live) + if (this.#isLedgerLiveHdPath()) { + return `m/44'/60'/${index}'/0/0`; + } + + if (this.#isLedgerLegacyHdPath()) { + return `m/44'/60'/0'/${index}`; + } + + if (this.#isStandardBip44HdPath()) { + return `m/44'/60'/0'/0/${index}`; + } + + // default path: m/44'/60'/0'/0/x + return `${this.hdPath}/${index}`; + } + + #isLedgerLiveHdPath(): boolean { + return this.hdPath === `m/44'/60'/x'/0/0`; + } + + #isLedgerLegacyHdPath(): boolean { + return this.hdPath === `m/44'/60'/0'/x`; + } + + #isStandardBip44HdPath(): boolean { + return ( + this.hdPath === `m/44'/60'/0'/0/x` || this.hdPath === `m/44'/60'/0'/0` + ); + } +} diff --git a/packages/keyring-eth-onekey/src/onekey-web-bridge.ts b/packages/keyring-eth-onekey/src/onekey-web-bridge.ts new file mode 100644 index 00000000..68f97027 --- /dev/null +++ b/packages/keyring-eth-onekey/src/onekey-web-bridge.ts @@ -0,0 +1,324 @@ +/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/consistent-type-imports */ +import { UI_REQUEST, UI_RESPONSE } from '@onekeyfe/hd-core'; +import type { + ConnectSettings, + CoreApi, + EVMGetPublicKeyParams, + EVMSignedTx, + EVMSignMessageParams, + EVMSignTransactionParams, + EVMSignTypedDataParams, + Params, + UiEvent, + Unsuccessful, +} from '@onekeyfe/hd-core'; +import { HardwareErrorCode } from '@onekeyfe/hd-shared'; +import type { + EthereumMessageSignature, + Features, +} from '@onekeyfe/hd-transport'; + +import { ONEKEY_HARDWARE_UI_EVENT } from './constants'; +import { OneKeyBridge } from './onekey-bridge'; + +export type OneKeyIframeBridgeOptions = { + bridgeUrl: string; +}; + +export class OneKeyWebBridge implements OneKeyBridge { + isSDKInitialized = false; + + sdk: CoreApi | undefined = undefined; + + eventListeners: Map void> = new Map(); + + constructor() { + console.log('OneKeyWebBridge constructor'); + } + + model?: string | undefined; + + on(_event: string, callback: (event: any) => void): void { + this.eventListeners.set(_event, callback); + } + + off(_event: string): void { + this.eventListeners.delete(_event); + } + + handleBlockErrorEvent(payload: Unsuccessful): void { + const { code } = payload.payload; + const errorCodes: number[] = [ + HardwareErrorCode.WebDeviceNotFoundOrNeedsPermission, + HardwareErrorCode.BridgeNotInstalled, + HardwareErrorCode.NewFirmwareForceUpdate, + HardwareErrorCode.NotAllowInBootloaderMode, + HardwareErrorCode.CallMethodNeedUpgradeFirmware, + HardwareErrorCode.DeviceCheckPassphraseStateError, + HardwareErrorCode.DeviceCheckUnlockTypeError, + ]; + + if (code && typeof code === 'number' && errorCodes.includes(code)) { + this.eventListeners.get(ONEKEY_HARDWARE_UI_EVENT)?.(payload.payload); + } + } + + async updateTransportMethod( + transportType: ConnectSettings['env'], + ): Promise { + if (!this.sdk) { + return; + } + await this.sdk.switchTransport(transportType); + } + + async init(): Promise { + if (this.isSDKInitialized) { + return; + } + const sdkLib = await import('@onekeyfe/hd-web-sdk'); + const { HardwareWebSdk, HardwareSDKLowLevel } = sdkLib as any; + const settings: Partial = { + debug: true, + fetchConfig: false, + connectSrc: 'https://jssdk.onekey.so/1.1.0/', + env: 'webusb', + }; + try { + await HardwareWebSdk.init(settings, HardwareSDKLowLevel); + this.isSDKInitialized = true; + this.sdk = HardwareWebSdk as unknown as CoreApi; + + // eslint-disable-next-line id-length + this.sdk?.on('UI_EVENT', (e: any) => { + const originEvent = e as UiEvent; + if (originEvent.type === UI_REQUEST.REQUEST_PIN) { + this.sdk?.uiResponse({ + type: UI_RESPONSE.RECEIVE_PIN, + payload: '@@ONEKEY_INPUT_PIN_IN_DEVICE', + }); + } + if (originEvent.type === UI_REQUEST.REQUEST_PASSPHRASE) { + this.sdk?.uiResponse({ + type: UI_RESPONSE.RECEIVE_PASSPHRASE, + payload: { + value: '', + passphraseOnDevice: true, + save: false, + }, + }); + } + }); + } catch { + this.isSDKInitialized = false; + } + } + + async destroy(): Promise { + this.isSDKInitialized = false; + this.sdk = undefined; + } + + async dispose(): Promise { + this.sdk?.dispose(); + return Promise.resolve(); + } + + getModel(): string | undefined { + return this.model; + } + + async getDeviceFeatures(): Promise< + | { + success: true; + payload: Features; + } + | { + success: false; + payload: { error: string; code?: string | number }; + } + > { + if (!this.sdk) { + return { + success: false, + payload: { error: 'SDK not initialized', code: 800 }, + }; + } + return await this.sdk.getFeatures(); + } + + async getPublicKey(params: { + path: string; + coin: string; + }): Promise< + | { success: false; payload: { error: string; code?: string | number } } + | { success: true; payload: { publicKey: string; chainCode: string } } + > { + if (!this.sdk) { + return { + success: false, + payload: { error: 'SDK not initialized', code: 800 }, + }; + } + return await this.sdk + .evmGetPublicKey('', '', { ...params, skipPassphraseCheck: true }) + .then((result) => { + if (result?.success) { + return { + success: true, + payload: { + publicKey: result.payload.pub, + chainCode: result.payload.node.chain_code, + }, + }; + } + this.handleBlockErrorEvent(result); + return { + success: false, + payload: { + error: result?.payload.error ?? '', + code: + typeof result?.payload?.code === 'number' + ? result?.payload?.code + : undefined, + }, + }; + }); + } + + async batchGetPublicKey( + params: Params & { bundle: EVMGetPublicKeyParams[] }, + ): Promise< + | { success: false; payload: { error: string; code?: string | number } } + | { success: true; payload: { pub: string }[] } + > { + if (!this.sdk) { + return { + success: false, + payload: { error: 'SDK not initialized', code: 800 }, + }; + } + return await this.sdk + .evmGetPublicKey('', '', { + ...params, + skipPassphraseCheck: true, + }) + .then((result) => { + if (result?.success) { + if (Array.isArray(result.payload)) { + return { + success: true, + payload: result.payload.map((item) => ({ pub: item.pub })), + }; + } + return { + success: false, + payload: { error: 'No public key found', code: 800 }, + }; + } + this.handleBlockErrorEvent(result); + return { + success: false, + payload: { + error: result?.payload.error ?? '', + code: result?.payload.code ?? undefined, + }, + }; + }); + } + + async getPassphraseState(): Promise< + | { success: false; payload: { error: string; code?: string | number } } + | { success: true; payload: string | undefined } + > { + if (!this.sdk) { + return { + success: false, + payload: { error: 'SDK not initialized', code: 800 }, + }; + } + return await this.sdk.getPassphraseState('').then((result) => { + if (!result?.success) { + this.handleBlockErrorEvent(result); + } + return result; + }); + } + + async ethereumSignTransaction( + params: Params, + ): Promise< + | { success: false; payload: { error: string; code?: string | number } } + | { success: true; payload: EVMSignedTx } + > { + if (!this.sdk) { + return { + success: false, + payload: { error: 'SDK not initialized', code: 800 }, + }; + } + return await this.sdk + .evmSignTransaction('', '', { + ...params, + skipPassphraseCheck: true, + }) + .then((result) => { + if (!result?.success) { + this.handleBlockErrorEvent(result); + } + return result; + }); + } + + async ethereumSignMessage( + params: Params, + ): Promise< + | { success: false; payload: { error: string; code?: string | number } } + | { success: true; payload: EthereumMessageSignature } + > { + if (!this.sdk) { + return { + success: false, + payload: { error: 'SDK not initialized', code: 800 }, + }; + } + return await this.sdk + .evmSignMessage('', '', { + ...params, + skipPassphraseCheck: true, + }) + .then((result) => { + if (!result?.success) { + this.handleBlockErrorEvent(result); + } + return result; + }); + } + + async ethereumSignTypedData( + params: Params, + ): Promise< + | { success: false; payload: { error: string; code?: string | number } } + | { success: true; payload: EthereumMessageSignature } + > { + if (!this.sdk) { + return { + success: false, + payload: { error: 'SDK not initialized', code: 800 }, + }; + } + return await this.sdk + .evmSignTypedData('', '', { + ...params, + skipPassphraseCheck: true, + }) + .then((result) => { + if (!result?.success) { + this.handleBlockErrorEvent(result); + } + return result; + }); + } +} diff --git a/packages/keyring-eth-onekey/tsconfig.build.json b/packages/keyring-eth-onekey/tsconfig.build.json new file mode 100644 index 00000000..926b19e7 --- /dev/null +++ b/packages/keyring-eth-onekey/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "dist", + "rootDir": "src", + "exactOptionalPropertyTypes": false, + // circumvent missing types in @trezor/connect-web, please see https://github.com/trezor/trezor-suite/issues/10389 + "skipLibCheck": true, + "lib": ["ES2020"], + "target": "es2017" + }, + "references": [{ "path": "../keyring-utils/tsconfig.build.json" }], + "include": ["./src/**/*.ts"], + "exclude": ["./src/**/*.test.ts"] +} diff --git a/packages/keyring-eth-onekey/tsconfig.json b/packages/keyring-eth-onekey/tsconfig.json new file mode 100644 index 00000000..16c03416 --- /dev/null +++ b/packages/keyring-eth-onekey/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./", + "exactOptionalPropertyTypes": false, + // circumvent missing types in @trezor/connect-web, please see https://github.com/trezor/trezor-suite/issues/10389 + "skipLibCheck": true, + "lib": ["ES2020"], + "target": "es2017" + }, + "references": [{ "path": "../keyring-utils" }], + "include": ["./src"], + "exclude": ["./dist/**/*"] +} diff --git a/packages/keyring-eth-onekey/typedoc.json b/packages/keyring-eth-onekey/typedoc.json new file mode 100644 index 00000000..b527b625 --- /dev/null +++ b/packages/keyring-eth-onekey/typedoc.json @@ -0,0 +1,6 @@ +{ + "entryPoints": ["./src/index.ts"], + "excludePrivate": true, + "hideGenerator": true, + "out": "docs" +} diff --git a/yarn.lock b/yarn.lock index 773e6162..fe4c713d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1742,6 +1742,46 @@ __metadata: languageName: unknown linkType: soft +"@metamask/eth-onekey-keyring@workspace:packages/keyring-eth-onekey": + version: 0.0.0-use.local + resolution: "@metamask/eth-onekey-keyring@workspace:packages/keyring-eth-onekey" + dependencies: + "@ethereumjs/common": "npm:^4.4.0" + "@ethereumjs/tx": "npm:^5.4.0" + "@ethereumjs/util": "npm:^9.1.0" + "@lavamoat/allow-scripts": "npm:^3.2.1" + "@lavamoat/preinstall-always-fail": "npm:^2.1.0" + "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@metamask/keyring-utils": "workspace:^" + "@metamask/utils": "npm:^11.1.0" + "@noble/hashes": "npm:^1.7.0" + "@onekeyfe/hd-core": "npm:^1.1.1" + "@onekeyfe/hd-shared": "npm:^1.1.1" + "@onekeyfe/hd-transport": "npm:^1.1.1" + "@onekeyfe/hd-web-sdk": "npm:^1.1.1" + "@ts-bridge/cli": "npm:^0.6.3" + "@types/ethereumjs-tx": "npm:^1.0.1" + "@types/jest": "npm:^29.5.12" + "@types/node": "npm:^20.12.12" + "@types/sinon": "npm:^17.0.3" + "@types/w3c-web-usb": "npm:^1.0.6" + bytebuffer: "npm:^5.0.1" + deepmerge: "npm:^4.2.2" + depcheck: "npm:^1.4.7" + ethereumjs-tx: "npm:^1.3.7" + jest: "npm:^29.5.0" + jest-environment-jsdom: "npm:^29.7.0" + jest-it-up: "npm:^3.1.0" + sinon: "npm:^19.0.2" + ts-jest: "npm:^29.0.5" + ts-node: "npm:^10.9.2" + tslib: "npm:^2.6.2" + typedoc: "npm:^0.25.13" + typescript: "npm:~5.6.3" + languageName: unknown + linkType: soft + "@metamask/eth-qr-keyring@workspace:packages/keyring-eth-qr": version: 0.0.0-use.local resolution: "@metamask/eth-qr-keyring@workspace:packages/keyring-eth-qr" @@ -2511,7 +2551,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.0.0, @noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.6.1, @noble/hashes@npm:^1.7.1, @noble/hashes@npm:~1.8.0": +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.0.0, @noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.6.1, @noble/hashes@npm:^1.7.0, @noble/hashes@npm:^1.7.1, @noble/hashes@npm:~1.8.0": version: 1.8.0 resolution: "@noble/hashes@npm:1.8.0" checksum: 10/474b7f56bc6fb2d5b3a42132561e221b0ea4f91e590f4655312ca13667840896b34195e2b53b7f097ec080a1fdd3b58d902c2a8d0fbdf51d2e238b53808a177e @@ -2656,6 +2696,115 @@ __metadata: languageName: node linkType: hard +"@onekeyfe/cross-inpage-provider-core@npm:^0.0.17": + version: 0.0.17 + resolution: "@onekeyfe/cross-inpage-provider-core@npm:0.0.17" + dependencies: + "@onekeyfe/cross-inpage-provider-errors": "npm:^0.0.17" + "@onekeyfe/cross-inpage-provider-events": "npm:^0.0.17" + "@onekeyfe/cross-inpage-provider-types": "npm:^0.0.17" + events: "npm:^3.3.0" + lodash: "npm:^4.17.21" + ms: "npm:^2.1.3" + checksum: 10/655305db565093b245d42c65f02f951cc4f064469aa201e9acea6c4b9e14e5acc2944815134b18773865ef05afff9bb78face94e6f730ef9e9dd5b0ccd0d72b4 + languageName: node + linkType: hard + +"@onekeyfe/cross-inpage-provider-errors@npm:^0.0.17": + version: 0.0.17 + resolution: "@onekeyfe/cross-inpage-provider-errors@npm:0.0.17" + dependencies: + fast-safe-stringify: "npm:^2.1.1" + checksum: 10/f9a37acaaff97581d5344651b3d72c91cdf537d88c452564db962b8cc214c32c97246de9d5adeeef225ef1ccc5d804545e84755d8c9a95e63af2fc94a90dd4fb + languageName: node + linkType: hard + +"@onekeyfe/cross-inpage-provider-events@npm:^0.0.17": + version: 0.0.17 + resolution: "@onekeyfe/cross-inpage-provider-events@npm:0.0.17" + checksum: 10/f98304e1d98c1b3fc9b2952056019dcd2123de8bf555d9039d1d93a953ceb2937a97a91c1061bd4b971d6efa3a017ed9c68915a3454a7c0b8f9bdcefa0d11d84 + languageName: node + linkType: hard + +"@onekeyfe/cross-inpage-provider-types@npm:^0.0.17": + version: 0.0.17 + resolution: "@onekeyfe/cross-inpage-provider-types@npm:0.0.17" + checksum: 10/4dbf5bc6b4467a8324f2e438757fccd934573e081d88532a75d1588b32120bde3c9f39229ac6678fcc25d4970622fbec4e128f01550b4f6925b798c93ad642a5 + languageName: node + linkType: hard + +"@onekeyfe/hd-core@npm:^1.1.1": + version: 1.1.1 + resolution: "@onekeyfe/hd-core@npm:1.1.1" + dependencies: + "@onekeyfe/hd-shared": "npm:^1.1.1" + "@onekeyfe/hd-transport": "npm:^1.1.1" + axios: "npm:^0.27.2" + bignumber.js: "npm:^9.0.2" + bytebuffer: "npm:^5.0.1" + jszip: "npm:^3.10.1" + parse-uri: "npm:^1.0.7" + semver: "npm:^7.3.7" + peerDependencies: + "@noble/hashes": ^1.1.3 + ripple-keypairs: ^1.1.4 + checksum: 10/449ded7bbcafec0eea68931adf8a5ead3851d2cad55f4bbf9ed35fe6c9617d3ce1064fa3ec875de6589afd6b040ce8757334552deda09d47da3691e01b489360 + languageName: node + linkType: hard + +"@onekeyfe/hd-shared@npm:^1.1.1": + version: 1.1.1 + resolution: "@onekeyfe/hd-shared@npm:1.1.1" + checksum: 10/30bfe13b2ff9070bc81cff389a6c6fceddb1cc0d4efce07ea7d79e29d3021d169768817dbb432fb97eee850a5ad51ae749bed69d2d1119ee0bde32b48fd34cb9 + languageName: node + linkType: hard + +"@onekeyfe/hd-transport-http@npm:^1.1.1": + version: 1.1.1 + resolution: "@onekeyfe/hd-transport-http@npm:1.1.1" + dependencies: + "@onekeyfe/hd-shared": "npm:^1.1.1" + "@onekeyfe/hd-transport": "npm:^1.1.1" + axios: "npm:^0.27.2" + secure-json-parse: "npm:^4.0.0" + checksum: 10/4dc2f40449c08580ee2cf5b2bcb948521cc37dee130896c263210e3b537019b3036e1a892140895c448f1c91564ffe3a71e83b2dec4f5032103d4cc76d953fec + languageName: node + linkType: hard + +"@onekeyfe/hd-transport-web-device@npm:^1.1.1": + version: 1.1.1 + resolution: "@onekeyfe/hd-transport-web-device@npm:1.1.1" + dependencies: + "@onekeyfe/hd-shared": "npm:^1.1.1" + "@onekeyfe/hd-transport": "npm:^1.1.1" + checksum: 10/85badca8d1cda62caedf287e2c8f11c94afe9d9433d04f3e669db93045324b0fbc632728b39516b87e63865f8aa5dadf1dde09a92eff9ba8d11863d347caac2e + languageName: node + linkType: hard + +"@onekeyfe/hd-transport@npm:^1.1.1": + version: 1.1.1 + resolution: "@onekeyfe/hd-transport@npm:1.1.1" + dependencies: + bytebuffer: "npm:^5.0.1" + long: "npm:^4.0.0" + protobufjs: "npm:^6.11.2" + checksum: 10/65c494c0797265029b08934ac9e394f87774090ac6fa2e09e56a3635cd8e9d010f139b9425d243aca09bfcf771f0e287277959f1c753503f9cd28beb1504a733 + languageName: node + linkType: hard + +"@onekeyfe/hd-web-sdk@npm:^1.1.1": + version: 1.1.1 + resolution: "@onekeyfe/hd-web-sdk@npm:1.1.1" + dependencies: + "@onekeyfe/cross-inpage-provider-core": "npm:^0.0.17" + "@onekeyfe/hd-core": "npm:^1.1.1" + "@onekeyfe/hd-shared": "npm:^1.1.1" + "@onekeyfe/hd-transport-http": "npm:^1.1.1" + "@onekeyfe/hd-transport-web-device": "npm:^1.1.1" + checksum: 10/483eb8ec0a68b176836d16e50b98ae07220292e15217efd98327055f29dc6daec98a2c3400274f110d023cbffed843c6658f65c5496d542ce58de05801f105c6 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -4029,6 +4178,13 @@ __metadata: languageName: node linkType: hard +"@types/long@npm:^4.0.1": + version: 4.0.2 + resolution: "@types/long@npm:4.0.2" + checksum: 10/68afa05fb20949d88345876148a76f6ccff5433310e720db51ac5ca21cb8cc6714286dbe04713840ddbd25a8b56b7a23aa87d08472fabf06463a6f2ed4967707 + languageName: node + linkType: hard + "@types/minimatch@npm:^3.0.3": version: 3.0.5 resolution: "@types/minimatch@npm:3.0.5" @@ -4868,6 +5024,16 @@ __metadata: languageName: node linkType: hard +"axios@npm:^0.27.2": + version: 0.27.2 + resolution: "axios@npm:0.27.2" + dependencies: + follow-redirects: "npm:^1.14.9" + form-data: "npm:^4.0.0" + checksum: 10/2efaf18dd0805f7bc772882bc86f004abd92d51007b54c5081f74db0d08ce3593e2c010261896d25a14318eeaa2e966fd825e34f810e8a3339dc64b9d177cf70 + languageName: node + linkType: hard + "axios@npm:^1.8.4": version: 1.10.0 resolution: "axios@npm:1.10.0" @@ -5107,6 +5273,13 @@ __metadata: languageName: node linkType: hard +"bignumber.js@npm:^9.0.2": + version: 9.3.1 + resolution: "bignumber.js@npm:9.3.1" + checksum: 10/1be0372bf0d6d29d0a49b9e6a9cefbd54dad9918232ad21fcd4ec39030260773abf0c76af960c6b3b98d3115a3a71e61c6a111812d1395040a039cfa178e0245 + languageName: node + linkType: hard + "bin-links@npm:4.0.4": version: 4.0.4 resolution: "bin-links@npm:4.0.4" @@ -5363,6 +5536,15 @@ __metadata: languageName: node linkType: hard +"bytebuffer@npm:^5.0.1": + version: 5.0.1 + resolution: "bytebuffer@npm:5.0.1" + dependencies: + long: "npm:~3" + checksum: 10/f3e9739ed9ab30e19d985fc3dadfdbd631d030874bbb313feefddac756f21ac10957257737e630fd9959744318e6e8b7d8c35b797519693bf1897be16c560970 + languageName: node + linkType: hard + "cacache@npm:^16.1.0": version: 16.1.1 resolution: "cacache@npm:16.1.1" @@ -5739,6 +5921,13 @@ __metadata: languageName: node linkType: hard +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10/9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99 + languageName: node + linkType: hard + "cosmiconfig@npm:9.0.0": version: 9.0.0 resolution: "cosmiconfig@npm:9.0.0" @@ -7049,7 +7238,7 @@ __metadata: languageName: node linkType: hard -"fast-safe-stringify@npm:^2.0.6": +"fast-safe-stringify@npm:^2.0.6, fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" checksum: 10/dc1f063c2c6ac9533aee14d406441f86783a8984b2ca09b19c2fe281f9ff59d315298bc7bc22fd1f83d26fe19ef2f20e2ddb68e96b15040292e555c5ced0c1e4 @@ -7184,7 +7373,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.15.6": +"follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.6": version: 1.15.9 resolution: "follow-redirects@npm:1.15.9" peerDependenciesMeta: @@ -7800,6 +7989,13 @@ __metadata: languageName: node linkType: hard +"immediate@npm:~3.0.5": + version: 3.0.6 + resolution: "immediate@npm:3.0.6" + checksum: 10/f9b3486477555997657f70318cc8d3416159f208bec4cca3ff3442fd266bc23f50f0c9bd8547e1371a6b5e82b821ec9a7044a4f7b944798b25aa3cc6d5e63e62 + languageName: node + linkType: hard + "immer@npm:^9.0.6": version: 9.0.21 resolution: "immer@npm:9.0.21" @@ -7860,7 +8056,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.4": +"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -8128,6 +8324,13 @@ __metadata: languageName: node linkType: hard +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10/f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -8895,6 +9098,18 @@ __metadata: languageName: node linkType: hard +"jszip@npm:^3.10.1": + version: 3.10.1 + resolution: "jszip@npm:3.10.1" + dependencies: + lie: "npm:~3.3.0" + pako: "npm:~1.0.2" + readable-stream: "npm:~2.3.6" + setimmediate: "npm:^1.0.5" + checksum: 10/bfbfbb9b0a27121330ac46ab9cdb3b4812433faa9ba4a54742c87ca441e31a6194ff70ae12acefa5fe25406c432290e68003900541d948a169b23d30c34dd984 + languageName: node + linkType: hard + "just-extend@npm:^6.2.0": version: 6.2.0 resolution: "just-extend@npm:6.2.0" @@ -8945,6 +9160,15 @@ __metadata: languageName: node linkType: hard +"lie@npm:~3.3.0": + version: 3.3.0 + resolution: "lie@npm:3.3.0" + dependencies: + immediate: "npm:~3.0.5" + checksum: 10/f335ce67fe221af496185d7ce39c8321304adb701e122942c495f4f72dcee8803f9315ee572f5f8e8b08b9e8d7195da91b9fad776e8864746ba8b5e910adf76e + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.1.6 resolution: "lines-and-columns@npm:1.1.6" @@ -9025,6 +9249,20 @@ __metadata: languageName: node linkType: hard +"long@npm:^4.0.0": + version: 4.0.0 + resolution: "long@npm:4.0.0" + checksum: 10/8296e2ba7bab30f9cfabb81ebccff89c819af6a7a78b4bb5a70ea411aa764ee0532f7441381549dfa6a1a98d72abe9138bfcf99f4fa41238629849bc035b845b + languageName: node + linkType: hard + +"long@npm:~3": + version: 3.2.0 + resolution: "long@npm:3.2.0" + checksum: 10/ffc685ec458ddf71a830d6deb62ff7dc551a736d47473350d9e077c22db96ec88c8a3554c11ffce7d7f2291b0c30da36629e4d0a97c29b5360dc977533c96d28 + languageName: node + linkType: hard + "loose-envify@npm:^1.1.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -9476,7 +9714,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -9947,6 +10185,13 @@ __metadata: languageName: node linkType: hard +"pako@npm:~1.0.2": + version: 1.0.11 + resolution: "pako@npm:1.0.11" + checksum: 10/1ad07210e894472685564c4d39a08717e84c2a68a70d3c1d9e657d32394ef1670e22972a433cbfe48976cb98b154ba06855dcd3fcfba77f60f1777634bec48c0 + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -9975,6 +10220,13 @@ __metadata: languageName: node linkType: hard +"parse-uri@npm:^1.0.7": + version: 1.0.16 + resolution: "parse-uri@npm:1.0.16" + checksum: 10/5fd915fefd81bda753e7dbfdc887a5f8c88b6e4d1a23a4ac4f447f37cbff7fcdcabef047da56ad099c5418087ab7adb4df2f960f12d802467356ee136791bdae + languageName: node + linkType: hard + "parse5@npm:^7.0.0, parse5@npm:^7.1.1": version: 7.1.2 resolution: "parse5@npm:7.1.2" @@ -10194,6 +10446,13 @@ __metadata: languageName: node linkType: hard +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10/1d38588e520dab7cea67cbbe2efdd86a10cc7a074c09657635e34f035277b59fbb57d09d8638346bf7090f8e8ebc070c96fa5fd183b777fff4f5edff5e9466cf + languageName: node + linkType: hard + "process@npm:^0.11.10": version: 0.11.10 resolution: "process@npm:0.11.10" @@ -10248,6 +10507,30 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^6.11.2": + version: 6.11.4 + resolution: "protobufjs@npm:6.11.4" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/long": "npm:^4.0.1" + "@types/node": "npm:>=13.7.0" + long: "npm:^4.0.0" + bin: + pbjs: bin/pbjs + pbts: bin/pbts + checksum: 10/6b7fd7540d74350d65c38f69f398c9995ae019da070e79d9cd464a458c6d19b40b07c9a026be4e10704c824a344b603307745863310c50026ebd661ce4da0663 + languageName: node + linkType: hard + "proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" @@ -10421,6 +10704,21 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:~2.3.6": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10/8500dd3a90e391d6c5d889256d50ec6026c059fadee98ae9aa9b86757d60ac46fff24fafb7a39fa41d54cb39d8be56cc77be202ebd4cd8ffcf4cb226cbaa40d4 + languageName: node + linkType: hard + "readable-web-to-node-stream@npm:^3.0.2": version: 3.0.2 resolution: "readable-web-to-node-stream@npm:3.0.2" @@ -10693,7 +10991,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:~5.1.1": +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" checksum: 10/7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a @@ -10744,6 +11042,13 @@ __metadata: languageName: node linkType: hard +"secure-json-parse@npm:^4.0.0": + version: 4.0.0 + resolution: "secure-json-parse@npm:4.0.0" + checksum: 10/c36c9dec9afaf4ef929a5469995d70d2f20d3d89b57219f22e0349b342715987283dbc1a80ab6f39e0bb28f8c3f3f073ce5363765c20c8d003ac243b4a89bd3d + languageName: node + linkType: hard + "semver-compare@npm:^1.0.0": version: 1.0.0 resolution: "semver-compare@npm:1.0.0" @@ -11192,6 +11497,15 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10/7c41c17ed4dea105231f6df208002ebddd732e8e9e2d619d133cecd8e0087ddfd9587d2feb3c8caf3213cbd841ada6d057f5142cae68a4e62d3540778d9819b4 + languageName: node + linkType: hard + "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -11897,7 +12211,7 @@ __metadata: languageName: node linkType: hard -"util-deprecate@npm:^1.0.1": +"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" checksum: 10/474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 From be93f6986faea5d64a43c632dbbc88445efe0089 Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Tue, 5 Aug 2025 21:59:02 +0800 Subject: [PATCH 02/10] feat: sofe derive --- packages/keyring-eth-onekey/package.json | 3 + .../keyring-eth-onekey/src/onekey-bridge.ts | 7 - .../keyring-eth-onekey/src/onekey-keyring.ts | 167 +++++++++--------- .../src/onekey-web-bridge.ts | 66 +------ yarn.lock | 3 + 5 files changed, 88 insertions(+), 158 deletions(-) diff --git a/packages/keyring-eth-onekey/package.json b/packages/keyring-eth-onekey/package.json index 36bd6194..f095fee2 100644 --- a/packages/keyring-eth-onekey/package.json +++ b/packages/keyring-eth-onekey/package.json @@ -56,6 +56,8 @@ "@onekeyfe/hd-transport": "^1.1.1", "@onekeyfe/hd-web-sdk": "^1.1.1", "bytebuffer": "^5.0.1", + "hdkey": "^2.1.0", + "ripple-address-codec": "^5.0.0", "tslib": "^2.6.2" }, "devDependencies": { @@ -66,6 +68,7 @@ "@metamask/keyring-utils": "workspace:^", "@ts-bridge/cli": "^0.6.3", "@types/ethereumjs-tx": "^1.0.1", + "@types/hdkey": "^2.0.1", "@types/jest": "^29.5.12", "@types/node": "^20.12.12", "@types/sinon": "^17.0.3", diff --git a/packages/keyring-eth-onekey/src/onekey-bridge.ts b/packages/keyring-eth-onekey/src/onekey-bridge.ts index 6f6756f0..f23e81ac 100644 --- a/packages/keyring-eth-onekey/src/onekey-bridge.ts +++ b/packages/keyring-eth-onekey/src/onekey-bridge.ts @@ -9,7 +9,6 @@ import type { EVMSignMessageParams, EVMSignTypedDataParams, EVMGetPublicKeyParams, - Features, } from '@onekeyfe/hd-core'; import type { EthereumMessageSignature } from '@onekeyfe/hd-transport'; @@ -39,18 +38,12 @@ export type OneKeyBridge = { updateTransportMethod(transportType: string): Promise; - getDeviceFeatures(): Response; - // OneKeySdk.getPublicKey has two overloads // It is not possible to extract them from the library using utility types getPublicKey( params: Params, ): Response<{ publicKey: string; chainCode: string }>; - batchGetPublicKey( - params: Params & { bundle: EVMGetPublicKeyParams[] }, - ): Response<{ pub: string }[]>; - getPassphraseState(): Response; ethereumSignTransaction( diff --git a/packages/keyring-eth-onekey/src/onekey-keyring.ts b/packages/keyring-eth-onekey/src/onekey-keyring.ts index 8d729f04..e279c044 100644 --- a/packages/keyring-eth-onekey/src/onekey-keyring.ts +++ b/packages/keyring-eth-onekey/src/onekey-keyring.ts @@ -9,7 +9,6 @@ import type { ConnectSettings, EthereumSignTypedDataMessage, EthereumSignTypedDataTypes, - EVMGetPublicKeyParams, EVMSignedTx, EVMSignTransactionParams, } from '@onekeyfe/hd-core'; @@ -17,6 +16,7 @@ import type { import { Buffer } from 'buffer'; import type OldEthJsTransaction from 'ethereumjs-tx'; import { EventEmitter } from 'events'; +import HDKey from 'hdkey'; import { ONEKEY_HARDWARE_UI_EVENT } from './constants'; import type { OneKeyBridge } from './onekey-bridge'; @@ -129,6 +129,8 @@ export class OneKeyKeyring extends EventEmitter { unlockedAccount = 0; + hdk = new HDKey(); + accounts: readonly string[] = []; accountDetails: Record = {}; @@ -194,16 +196,17 @@ export class OneKeyKeyring extends EventEmitter { this.hdPath = hdPath; } + lock(): void { + this.hdk = new HDKey(); + } + isUnlocked(): boolean { - return false; + return Boolean(this.hdk?.publicKey); } async unlock(): Promise { - const features = await this.bridge.getDeviceFeatures(); - if (features.success) { - if (features.payload.unlocked) { - return 'already unlocked'; - } + if (this.isUnlocked()) { + return 'already unlocked'; } return new Promise((resolve, reject) => { @@ -219,7 +222,24 @@ export class OneKeyKeyring extends EventEmitter { return; } this.passphraseState = passphraseResponse.payload; - resolve('just unlocked'); + + // eslint-disable-next-line no-void + void this.bridge + .getPublicKey({ + showOnOneKey: false, + chainId: 1, + path: this.#getBasePath(), + passphraseState: this.passphraseState, + }) + .then(async (res) => { + if (res.success) { + this.hdk.publicKey = Buffer.from(res.payload.publicKey, 'hex'); + this.hdk.chainCode = Buffer.from(res.payload.chainCode, 'hex'); + resolve('just unlocked'); + } else { + reject(new Error('getPublicKey failed')); + } + }); }) .catch((error) => { reject(new Error(error?.toString() || 'Unknown error')); @@ -237,41 +257,32 @@ export class OneKeyKeyring extends EventEmitter { const to = from + numberOfAccounts; const newAccounts: string[] = []; - const paths: string[] = []; - for (let i = from; i < to; i++) { - paths.push(this.#getPathForIndex(i)); - } - - // eslint-disable-next-line no-void - void this.#batchGetAddress(paths, this.passphraseState) - .then((addresses) => { - if (addresses.length !== paths.length) { + try { + for (let i = from; i < to; i++) { + const address = this.#addressFromIndex(i); + const hdPath = this.#getPathForIndex(i); + if (typeof address === 'undefined') { throw new Error('Unknown error'); } - - for (let i = 0; i < paths.length; i++) { - const address = addresses[i]; - if (typeof address === 'undefined') { - throw new Error('Unknown error'); - } - if (!this.accounts.includes(address)) { - this.accounts = [...this.accounts, address]; - newAccounts.push(address); - } - if (!this.accountDetails[address]) { - this.accountDetails[address] = { - index: i, - hdPath: paths[i] ?? '', - passphraseState: this.passphraseState, - }; - } - this.page = 0; + if (!this.accounts.includes(address)) { + this.accounts = [...this.accounts, address]; + newAccounts.push(address); } - resolve(newAccounts); - }) - .catch((error: Error) => { - reject(error); - }); + if (!this.accountDetails[address]) { + this.accountDetails[address] = { + index: from + i, + hdPath, + passphraseState: this.passphraseState, + }; + } + this.page = 0; + } + + resolve(newAccounts); + } catch (error) { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors + reject(error); + } }); } @@ -565,29 +576,15 @@ export class OneKeyKeyring extends EventEmitter { this.unlock() .then(async () => { - const paths = []; for (let i = from; i < to; i++) { - paths.push(this.#getPathForIndex(i)); - } - - // this.passphraseState = passphraseState; - const addresses = await this.#batchGetAddress( - paths, - this.passphraseState, - ); - - if (addresses.length !== paths.length) { - throw new Error('Unknown error'); - } - for (let i = 0; i < paths.length; i++) { - const address = addresses[i]; + const address = this.#addressFromIndex(i); if (typeof address === 'undefined') { throw new Error('Unknown error'); } accounts.push({ + index: from + i, address, balance: null, - index: from + i, }); } resolve(accounts); @@ -599,36 +596,6 @@ export class OneKeyKeyring extends EventEmitter { }); } - async #batchGetAddress( - paths: string[], - passphraseState: string | undefined, - ): Promise { - const batchParams: EVMGetPublicKeyParams[] = paths.map((path) => ({ - path, - showOnOneKey: false, - })); - - const response = await this.bridge.batchGetPublicKey({ - bundle: batchParams, - useBatch: true, - passphraseState, - useEmptyPassphrase: isEmptyPassphrase(passphraseState), - skipPassphraseCheck: true, - }); - if (response.success) { - return response.payload.map((item) => { - const address = ethUtil.publicToAddress( - Buffer.from(item.pub, 'hex'), - true, - ); - return ethUtil.toChecksumAddress( - addHexPrefix(Buffer.from(address).toString('hex')), - ); - }); - } - throw new Error(response.payload?.error || 'Unknown error'); - } - #accountDetailsFromAddress(address: string): AccountDetails { const checksummedAddress = ethUtil.toChecksumAddress(address); const accountDetails = this.accountDetails[checksummedAddress]; @@ -638,6 +605,34 @@ export class OneKeyKeyring extends EventEmitter { return accountDetails; } + #addressFromIndex(i: number): string { + const dkey = this.hdk.derive(this.#getDerivePath(i)); + const address = ethUtil.bytesToHex( + ethUtil.publicToAddress(dkey.publicKey, true), + ); + return ethUtil.toChecksumAddress(address); + } + + #getDerivePath(index: number): string { + if (this.#isLedgerLiveHdPath()) { + throw new Error('Ledger Live is not supported'); + } + if (this.#isLedgerLegacyHdPath()) { + return `${pathBase}/${index}`; + } + if (this.#isStandardBip44HdPath()) { + return `${pathBase}/0/${index}`; + } + return `${pathBase}/${index}`; + } + + #getBasePath(): string { + if (this.#isLedgerLiveHdPath()) { + throw new Error('Ledger Live is not supported'); + } + return "m/44'/60'/0'"; + } + #getPathForIndex(index: number): string { // Check if the path is BIP 44 (Ledger Live) if (this.#isLedgerLiveHdPath()) { diff --git a/packages/keyring-eth-onekey/src/onekey-web-bridge.ts b/packages/keyring-eth-onekey/src/onekey-web-bridge.ts index 68f97027..677ed867 100644 --- a/packages/keyring-eth-onekey/src/onekey-web-bridge.ts +++ b/packages/keyring-eth-onekey/src/onekey-web-bridge.ts @@ -5,7 +5,6 @@ import { UI_REQUEST, UI_RESPONSE } from '@onekeyfe/hd-core'; import type { ConnectSettings, CoreApi, - EVMGetPublicKeyParams, EVMSignedTx, EVMSignMessageParams, EVMSignTransactionParams, @@ -15,10 +14,7 @@ import type { Unsuccessful, } from '@onekeyfe/hd-core'; import { HardwareErrorCode } from '@onekeyfe/hd-shared'; -import type { - EthereumMessageSignature, - Features, -} from '@onekeyfe/hd-transport'; +import type { EthereumMessageSignature } from '@onekeyfe/hd-transport'; import { ONEKEY_HARDWARE_UI_EVENT } from './constants'; import { OneKeyBridge } from './onekey-bridge'; @@ -130,25 +126,6 @@ export class OneKeyWebBridge implements OneKeyBridge { return this.model; } - async getDeviceFeatures(): Promise< - | { - success: true; - payload: Features; - } - | { - success: false; - payload: { error: string; code?: string | number }; - } - > { - if (!this.sdk) { - return { - success: false, - payload: { error: 'SDK not initialized', code: 800 }, - }; - } - return await this.sdk.getFeatures(); - } - async getPublicKey(params: { path: string; coin: string; @@ -188,47 +165,6 @@ export class OneKeyWebBridge implements OneKeyBridge { }); } - async batchGetPublicKey( - params: Params & { bundle: EVMGetPublicKeyParams[] }, - ): Promise< - | { success: false; payload: { error: string; code?: string | number } } - | { success: true; payload: { pub: string }[] } - > { - if (!this.sdk) { - return { - success: false, - payload: { error: 'SDK not initialized', code: 800 }, - }; - } - return await this.sdk - .evmGetPublicKey('', '', { - ...params, - skipPassphraseCheck: true, - }) - .then((result) => { - if (result?.success) { - if (Array.isArray(result.payload)) { - return { - success: true, - payload: result.payload.map((item) => ({ pub: item.pub })), - }; - } - return { - success: false, - payload: { error: 'No public key found', code: 800 }, - }; - } - this.handleBlockErrorEvent(result); - return { - success: false, - payload: { - error: result?.payload.error ?? '', - code: result?.payload.code ?? undefined, - }, - }; - }); - } - async getPassphraseState(): Promise< | { success: false; payload: { error: string; code?: string | number } } | { success: true; payload: string | undefined } diff --git a/yarn.lock b/yarn.lock index fe4c713d..13d7738a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1762,6 +1762,7 @@ __metadata: "@onekeyfe/hd-web-sdk": "npm:^1.1.1" "@ts-bridge/cli": "npm:^0.6.3" "@types/ethereumjs-tx": "npm:^1.0.1" + "@types/hdkey": "npm:^2.0.1" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^20.12.12" "@types/sinon": "npm:^17.0.3" @@ -1770,9 +1771,11 @@ __metadata: deepmerge: "npm:^4.2.2" depcheck: "npm:^1.4.7" ethereumjs-tx: "npm:^1.3.7" + hdkey: "npm:^2.1.0" jest: "npm:^29.5.0" jest-environment-jsdom: "npm:^29.7.0" jest-it-up: "npm:^3.1.0" + ripple-address-codec: "npm:^5.0.0" sinon: "npm:^19.0.2" ts-jest: "npm:^29.0.5" ts-node: "npm:^10.9.2" From 7bd08e81c0a49b2184c18f198f2bd820cff792b9 Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Thu, 14 Aug 2025 15:27:27 +0800 Subject: [PATCH 03/10] feat: update jd sdk --- packages/keyring-eth-onekey/package.json | 12 +- yarn.lock | 156 +++++++++++------------ 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/packages/keyring-eth-onekey/package.json b/packages/keyring-eth-onekey/package.json index f095fee2..289b6ff4 100644 --- a/packages/keyring-eth-onekey/package.json +++ b/packages/keyring-eth-onekey/package.json @@ -1,6 +1,6 @@ { - "name": "@metamask/eth-onekey-keyring", - "version": "1.0.0", + "name": "eth-onekey-bridge-keyring", + "version": "0.3.1", "description": "A MetaMask compatible keyring, for onekey hardware wallets", "keywords": [ "ethereum", @@ -51,10 +51,10 @@ "@metamask/eth-sig-util": "^8.2.0", "@metamask/utils": "^11.1.0", "@noble/hashes": "^1.7.0", - "@onekeyfe/hd-core": "^1.1.1", - "@onekeyfe/hd-shared": "^1.1.1", - "@onekeyfe/hd-transport": "^1.1.1", - "@onekeyfe/hd-web-sdk": "^1.1.1", + "@onekeyfe/hd-core": "^1.1.6", + "@onekeyfe/hd-shared": "^1.1.6", + "@onekeyfe/hd-transport": "^1.1.6", + "@onekeyfe/hd-web-sdk": "^1.1.6", "bytebuffer": "^5.0.1", "hdkey": "^2.1.0", "ripple-address-codec": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index 13d7738a..299058be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1742,49 +1742,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/eth-onekey-keyring@workspace:packages/keyring-eth-onekey": - version: 0.0.0-use.local - resolution: "@metamask/eth-onekey-keyring@workspace:packages/keyring-eth-onekey" - dependencies: - "@ethereumjs/common": "npm:^4.4.0" - "@ethereumjs/tx": "npm:^5.4.0" - "@ethereumjs/util": "npm:^9.1.0" - "@lavamoat/allow-scripts": "npm:^3.2.1" - "@lavamoat/preinstall-always-fail": "npm:^2.1.0" - "@metamask/auto-changelog": "npm:^3.4.4" - "@metamask/eth-sig-util": "npm:^8.2.0" - "@metamask/keyring-utils": "workspace:^" - "@metamask/utils": "npm:^11.1.0" - "@noble/hashes": "npm:^1.7.0" - "@onekeyfe/hd-core": "npm:^1.1.1" - "@onekeyfe/hd-shared": "npm:^1.1.1" - "@onekeyfe/hd-transport": "npm:^1.1.1" - "@onekeyfe/hd-web-sdk": "npm:^1.1.1" - "@ts-bridge/cli": "npm:^0.6.3" - "@types/ethereumjs-tx": "npm:^1.0.1" - "@types/hdkey": "npm:^2.0.1" - "@types/jest": "npm:^29.5.12" - "@types/node": "npm:^20.12.12" - "@types/sinon": "npm:^17.0.3" - "@types/w3c-web-usb": "npm:^1.0.6" - bytebuffer: "npm:^5.0.1" - deepmerge: "npm:^4.2.2" - depcheck: "npm:^1.4.7" - ethereumjs-tx: "npm:^1.3.7" - hdkey: "npm:^2.1.0" - jest: "npm:^29.5.0" - jest-environment-jsdom: "npm:^29.7.0" - jest-it-up: "npm:^3.1.0" - ripple-address-codec: "npm:^5.0.0" - sinon: "npm:^19.0.2" - ts-jest: "npm:^29.0.5" - ts-node: "npm:^10.9.2" - tslib: "npm:^2.6.2" - typedoc: "npm:^0.25.13" - typescript: "npm:~5.6.3" - languageName: unknown - linkType: soft - "@metamask/eth-qr-keyring@workspace:packages/keyring-eth-qr": version: 0.0.0-use.local resolution: "@metamask/eth-qr-keyring@workspace:packages/keyring-eth-qr" @@ -2736,12 +2693,12 @@ __metadata: languageName: node linkType: hard -"@onekeyfe/hd-core@npm:^1.1.1": - version: 1.1.1 - resolution: "@onekeyfe/hd-core@npm:1.1.1" +"@onekeyfe/hd-core@npm:1.1.6, @onekeyfe/hd-core@npm:^1.1.6": + version: 1.1.6 + resolution: "@onekeyfe/hd-core@npm:1.1.6" dependencies: - "@onekeyfe/hd-shared": "npm:^1.1.1" - "@onekeyfe/hd-transport": "npm:^1.1.1" + "@onekeyfe/hd-shared": "npm:1.1.6" + "@onekeyfe/hd-transport": "npm:1.1.6" axios: "npm:^0.27.2" bignumber.js: "npm:^9.0.2" bytebuffer: "npm:^5.0.1" @@ -2750,61 +2707,61 @@ __metadata: semver: "npm:^7.3.7" peerDependencies: "@noble/hashes": ^1.1.3 - ripple-keypairs: ^1.1.4 - checksum: 10/449ded7bbcafec0eea68931adf8a5ead3851d2cad55f4bbf9ed35fe6c9617d3ce1064fa3ec875de6589afd6b040ce8757334552deda09d47da3691e01b489360 + ripple-keypairs: ^1.3.1 + checksum: 10/949d41f133a4bd726a43a81a047c3c6b339f57042e7a84b76cc21855bdbf95c3b3453e1f8daeddb538c8390d882c6ddaeb10c243ecdb229c9ef33deb02a0361a languageName: node linkType: hard -"@onekeyfe/hd-shared@npm:^1.1.1": - version: 1.1.1 - resolution: "@onekeyfe/hd-shared@npm:1.1.1" - checksum: 10/30bfe13b2ff9070bc81cff389a6c6fceddb1cc0d4efce07ea7d79e29d3021d169768817dbb432fb97eee850a5ad51ae749bed69d2d1119ee0bde32b48fd34cb9 +"@onekeyfe/hd-shared@npm:1.1.6, @onekeyfe/hd-shared@npm:^1.1.6": + version: 1.1.6 + resolution: "@onekeyfe/hd-shared@npm:1.1.6" + checksum: 10/8e2b9b75ebec80a9703dfddb245b503bba219a90edc072e8c82337ebbb593ce028de561cfdb23d32c5afa310de7efa6d9e1476fff6a9a838f8df4f08a851ce4b languageName: node linkType: hard -"@onekeyfe/hd-transport-http@npm:^1.1.1": - version: 1.1.1 - resolution: "@onekeyfe/hd-transport-http@npm:1.1.1" +"@onekeyfe/hd-transport-http@npm:1.1.6": + version: 1.1.6 + resolution: "@onekeyfe/hd-transport-http@npm:1.1.6" dependencies: - "@onekeyfe/hd-shared": "npm:^1.1.1" - "@onekeyfe/hd-transport": "npm:^1.1.1" + "@onekeyfe/hd-shared": "npm:1.1.6" + "@onekeyfe/hd-transport": "npm:1.1.6" axios: "npm:^0.27.2" secure-json-parse: "npm:^4.0.0" - checksum: 10/4dc2f40449c08580ee2cf5b2bcb948521cc37dee130896c263210e3b537019b3036e1a892140895c448f1c91564ffe3a71e83b2dec4f5032103d4cc76d953fec + checksum: 10/e56b74c897207e9f4444257cb74a1b9f96a8a08f0353710e7c1ff015bca6274bfd606b85e053180b17065ce6020bbe0964bed46c19766f4cffec430d1566acaf languageName: node linkType: hard -"@onekeyfe/hd-transport-web-device@npm:^1.1.1": - version: 1.1.1 - resolution: "@onekeyfe/hd-transport-web-device@npm:1.1.1" +"@onekeyfe/hd-transport-web-device@npm:1.1.6": + version: 1.1.6 + resolution: "@onekeyfe/hd-transport-web-device@npm:1.1.6" dependencies: - "@onekeyfe/hd-shared": "npm:^1.1.1" - "@onekeyfe/hd-transport": "npm:^1.1.1" - checksum: 10/85badca8d1cda62caedf287e2c8f11c94afe9d9433d04f3e669db93045324b0fbc632728b39516b87e63865f8aa5dadf1dde09a92eff9ba8d11863d347caac2e + "@onekeyfe/hd-shared": "npm:1.1.6" + "@onekeyfe/hd-transport": "npm:1.1.6" + checksum: 10/085b64b433fe4482cdbd7bf6a397bb57b0b77a0a0c7dab5a5401da2ea778df4fb1fea924317127f4b707489f210b7d792923025af47c9c26883c3f29a3f34562 languageName: node linkType: hard -"@onekeyfe/hd-transport@npm:^1.1.1": - version: 1.1.1 - resolution: "@onekeyfe/hd-transport@npm:1.1.1" +"@onekeyfe/hd-transport@npm:1.1.6, @onekeyfe/hd-transport@npm:^1.1.6": + version: 1.1.6 + resolution: "@onekeyfe/hd-transport@npm:1.1.6" dependencies: bytebuffer: "npm:^5.0.1" long: "npm:^4.0.0" protobufjs: "npm:^6.11.2" - checksum: 10/65c494c0797265029b08934ac9e394f87774090ac6fa2e09e56a3635cd8e9d010f139b9425d243aca09bfcf771f0e287277959f1c753503f9cd28beb1504a733 + checksum: 10/6de5653ec1ed00354267b6b593ebe7d2c3d2ba58e5970da145b905ada7df6155189966a5ee482b799feadd9350244c7a80766bc656fd38c5f8d93726bd019211 languageName: node linkType: hard -"@onekeyfe/hd-web-sdk@npm:^1.1.1": - version: 1.1.1 - resolution: "@onekeyfe/hd-web-sdk@npm:1.1.1" +"@onekeyfe/hd-web-sdk@npm:^1.1.6": + version: 1.1.6 + resolution: "@onekeyfe/hd-web-sdk@npm:1.1.6" dependencies: "@onekeyfe/cross-inpage-provider-core": "npm:^0.0.17" - "@onekeyfe/hd-core": "npm:^1.1.1" - "@onekeyfe/hd-shared": "npm:^1.1.1" - "@onekeyfe/hd-transport-http": "npm:^1.1.1" - "@onekeyfe/hd-transport-web-device": "npm:^1.1.1" - checksum: 10/483eb8ec0a68b176836d16e50b98ae07220292e15217efd98327055f29dc6daec98a2c3400274f110d023cbffed843c6658f65c5496d542ce58de05801f105c6 + "@onekeyfe/hd-core": "npm:1.1.6" + "@onekeyfe/hd-shared": "npm:1.1.6" + "@onekeyfe/hd-transport-http": "npm:1.1.6" + "@onekeyfe/hd-transport-web-device": "npm:1.1.6" + checksum: 10/f4ba39fc8cded85fc3091163798454fa7355a0419efcbc127cd842a81007f10799c2d41dd8b5766ca2f0cf947ff020696f89a1a02f5fee5d8bf1051243854d10 languageName: node linkType: hard @@ -6880,6 +6837,49 @@ __metadata: languageName: node linkType: hard +"eth-onekey-bridge-keyring@workspace:packages/keyring-eth-onekey": + version: 0.0.0-use.local + resolution: "eth-onekey-bridge-keyring@workspace:packages/keyring-eth-onekey" + dependencies: + "@ethereumjs/common": "npm:^4.4.0" + "@ethereumjs/tx": "npm:^5.4.0" + "@ethereumjs/util": "npm:^9.1.0" + "@lavamoat/allow-scripts": "npm:^3.2.1" + "@lavamoat/preinstall-always-fail": "npm:^2.1.0" + "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@metamask/keyring-utils": "workspace:^" + "@metamask/utils": "npm:^11.1.0" + "@noble/hashes": "npm:^1.7.0" + "@onekeyfe/hd-core": "npm:^1.1.6" + "@onekeyfe/hd-shared": "npm:^1.1.6" + "@onekeyfe/hd-transport": "npm:^1.1.6" + "@onekeyfe/hd-web-sdk": "npm:^1.1.6" + "@ts-bridge/cli": "npm:^0.6.3" + "@types/ethereumjs-tx": "npm:^1.0.1" + "@types/hdkey": "npm:^2.0.1" + "@types/jest": "npm:^29.5.12" + "@types/node": "npm:^20.12.12" + "@types/sinon": "npm:^17.0.3" + "@types/w3c-web-usb": "npm:^1.0.6" + bytebuffer: "npm:^5.0.1" + deepmerge: "npm:^4.2.2" + depcheck: "npm:^1.4.7" + ethereumjs-tx: "npm:^1.3.7" + hdkey: "npm:^2.1.0" + jest: "npm:^29.5.0" + jest-environment-jsdom: "npm:^29.7.0" + jest-it-up: "npm:^3.1.0" + ripple-address-codec: "npm:^5.0.0" + sinon: "npm:^19.0.2" + ts-jest: "npm:^29.0.5" + ts-node: "npm:^10.9.2" + tslib: "npm:^2.6.2" + typedoc: "npm:^0.25.13" + typescript: "npm:~5.6.3" + languageName: unknown + linkType: soft + "eth-sig-util@npm:^3.0.1": version: 3.0.1 resolution: "eth-sig-util@npm:3.0.1" From 8648ef296c21baf1d2a2d6b98babf5348386dd8f Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Thu, 14 Aug 2025 17:02:57 +0800 Subject: [PATCH 04/10] feat: update jd sdk --- packages/keyring-eth-onekey/package.json | 10 ++-- yarn.lock | 76 ++++++++++++------------ 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/keyring-eth-onekey/package.json b/packages/keyring-eth-onekey/package.json index 289b6ff4..9b122c61 100644 --- a/packages/keyring-eth-onekey/package.json +++ b/packages/keyring-eth-onekey/package.json @@ -1,6 +1,6 @@ { "name": "eth-onekey-bridge-keyring", - "version": "0.3.1", + "version": "0.3.2", "description": "A MetaMask compatible keyring, for onekey hardware wallets", "keywords": [ "ethereum", @@ -51,10 +51,10 @@ "@metamask/eth-sig-util": "^8.2.0", "@metamask/utils": "^11.1.0", "@noble/hashes": "^1.7.0", - "@onekeyfe/hd-core": "^1.1.6", - "@onekeyfe/hd-shared": "^1.1.6", - "@onekeyfe/hd-transport": "^1.1.6", - "@onekeyfe/hd-web-sdk": "^1.1.6", + "@onekeyfe/hd-core": "^1.1.6-patch.1", + "@onekeyfe/hd-shared": "^1.1.6-patch.1", + "@onekeyfe/hd-transport": "^1.1.6-patch.1", + "@onekeyfe/hd-web-sdk": "^1.1.6-patch.1", "bytebuffer": "^5.0.1", "hdkey": "^2.1.0", "ripple-address-codec": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index 299058be..b99b1d1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2693,12 +2693,12 @@ __metadata: languageName: node linkType: hard -"@onekeyfe/hd-core@npm:1.1.6, @onekeyfe/hd-core@npm:^1.1.6": - version: 1.1.6 - resolution: "@onekeyfe/hd-core@npm:1.1.6" +"@onekeyfe/hd-core@npm:1.1.7, @onekeyfe/hd-core@npm:^1.1.6-patch.1": + version: 1.1.7 + resolution: "@onekeyfe/hd-core@npm:1.1.7" dependencies: - "@onekeyfe/hd-shared": "npm:1.1.6" - "@onekeyfe/hd-transport": "npm:1.1.6" + "@onekeyfe/hd-shared": "npm:1.1.7" + "@onekeyfe/hd-transport": "npm:1.1.7" axios: "npm:^0.27.2" bignumber.js: "npm:^9.0.2" bytebuffer: "npm:^5.0.1" @@ -2708,60 +2708,60 @@ __metadata: peerDependencies: "@noble/hashes": ^1.1.3 ripple-keypairs: ^1.3.1 - checksum: 10/949d41f133a4bd726a43a81a047c3c6b339f57042e7a84b76cc21855bdbf95c3b3453e1f8daeddb538c8390d882c6ddaeb10c243ecdb229c9ef33deb02a0361a + checksum: 10/25e8bd0841c3f7f32f3124bbda1eb6246ce04f15fa9201a646325b9a8f980a2ccb3798ea22b2fbbae3a67b69c7f13128f75c56d03aa7624f0b408b623ca81884 languageName: node linkType: hard -"@onekeyfe/hd-shared@npm:1.1.6, @onekeyfe/hd-shared@npm:^1.1.6": - version: 1.1.6 - resolution: "@onekeyfe/hd-shared@npm:1.1.6" - checksum: 10/8e2b9b75ebec80a9703dfddb245b503bba219a90edc072e8c82337ebbb593ce028de561cfdb23d32c5afa310de7efa6d9e1476fff6a9a838f8df4f08a851ce4b +"@onekeyfe/hd-shared@npm:1.1.7, @onekeyfe/hd-shared@npm:^1.1.6-patch.1": + version: 1.1.7 + resolution: "@onekeyfe/hd-shared@npm:1.1.7" + checksum: 10/02c22211751c5e4927ad04f527647b3e9634a86af3299808f269a27b2259532eca35ceeaff9973128e3ff52575201952238ba9a9e248fed51c704b21898591cb languageName: node linkType: hard -"@onekeyfe/hd-transport-http@npm:1.1.6": - version: 1.1.6 - resolution: "@onekeyfe/hd-transport-http@npm:1.1.6" +"@onekeyfe/hd-transport-http@npm:1.1.7": + version: 1.1.7 + resolution: "@onekeyfe/hd-transport-http@npm:1.1.7" dependencies: - "@onekeyfe/hd-shared": "npm:1.1.6" - "@onekeyfe/hd-transport": "npm:1.1.6" + "@onekeyfe/hd-shared": "npm:1.1.7" + "@onekeyfe/hd-transport": "npm:1.1.7" axios: "npm:^0.27.2" secure-json-parse: "npm:^4.0.0" - checksum: 10/e56b74c897207e9f4444257cb74a1b9f96a8a08f0353710e7c1ff015bca6274bfd606b85e053180b17065ce6020bbe0964bed46c19766f4cffec430d1566acaf + checksum: 10/7525de83528fe46e56764150cd9ecac1080d5943a44c9d39848d6df0677c68c87f4a60becf08148bfcfa6be7c5ae9a9e4910ec8dfd96e4aaecff72893b49ec59 languageName: node linkType: hard -"@onekeyfe/hd-transport-web-device@npm:1.1.6": - version: 1.1.6 - resolution: "@onekeyfe/hd-transport-web-device@npm:1.1.6" +"@onekeyfe/hd-transport-web-device@npm:1.1.7": + version: 1.1.7 + resolution: "@onekeyfe/hd-transport-web-device@npm:1.1.7" dependencies: - "@onekeyfe/hd-shared": "npm:1.1.6" - "@onekeyfe/hd-transport": "npm:1.1.6" - checksum: 10/085b64b433fe4482cdbd7bf6a397bb57b0b77a0a0c7dab5a5401da2ea778df4fb1fea924317127f4b707489f210b7d792923025af47c9c26883c3f29a3f34562 + "@onekeyfe/hd-shared": "npm:1.1.7" + "@onekeyfe/hd-transport": "npm:1.1.7" + checksum: 10/8b2e95a9035fcec6549b89b185425a234fe50cffab0190f3ade68b41a48010677ab7ed0b1b6078c325a63784d299c6d3cfe21e5d0dca072a8cb2611125011685 languageName: node linkType: hard -"@onekeyfe/hd-transport@npm:1.1.6, @onekeyfe/hd-transport@npm:^1.1.6": - version: 1.1.6 - resolution: "@onekeyfe/hd-transport@npm:1.1.6" +"@onekeyfe/hd-transport@npm:1.1.7, @onekeyfe/hd-transport@npm:^1.1.6-patch.1": + version: 1.1.7 + resolution: "@onekeyfe/hd-transport@npm:1.1.7" dependencies: bytebuffer: "npm:^5.0.1" long: "npm:^4.0.0" protobufjs: "npm:^6.11.2" - checksum: 10/6de5653ec1ed00354267b6b593ebe7d2c3d2ba58e5970da145b905ada7df6155189966a5ee482b799feadd9350244c7a80766bc656fd38c5f8d93726bd019211 + checksum: 10/b4c2f385e94551ee8929038c9d3940e383951f955e757be7e4b7c9d72ff0e8adc699e07446ee3ba4e7ca85cec614cff802d66140d03d313f5b438ec512bc2aeb languageName: node linkType: hard -"@onekeyfe/hd-web-sdk@npm:^1.1.6": - version: 1.1.6 - resolution: "@onekeyfe/hd-web-sdk@npm:1.1.6" +"@onekeyfe/hd-web-sdk@npm:^1.1.6-patch.1": + version: 1.1.7 + resolution: "@onekeyfe/hd-web-sdk@npm:1.1.7" dependencies: "@onekeyfe/cross-inpage-provider-core": "npm:^0.0.17" - "@onekeyfe/hd-core": "npm:1.1.6" - "@onekeyfe/hd-shared": "npm:1.1.6" - "@onekeyfe/hd-transport-http": "npm:1.1.6" - "@onekeyfe/hd-transport-web-device": "npm:1.1.6" - checksum: 10/f4ba39fc8cded85fc3091163798454fa7355a0419efcbc127cd842a81007f10799c2d41dd8b5766ca2f0cf947ff020696f89a1a02f5fee5d8bf1051243854d10 + "@onekeyfe/hd-core": "npm:1.1.7" + "@onekeyfe/hd-shared": "npm:1.1.7" + "@onekeyfe/hd-transport-http": "npm:1.1.7" + "@onekeyfe/hd-transport-web-device": "npm:1.1.7" + checksum: 10/fd70db6be81761e81cb02c67eb5e9d865aa15d76d0d312ee7986c26b2939a124afad96bf29c758ad425597acee8c8ea2c5e3e02480b50c0a7de70d87a61be526 languageName: node linkType: hard @@ -6851,10 +6851,10 @@ __metadata: "@metamask/keyring-utils": "workspace:^" "@metamask/utils": "npm:^11.1.0" "@noble/hashes": "npm:^1.7.0" - "@onekeyfe/hd-core": "npm:^1.1.6" - "@onekeyfe/hd-shared": "npm:^1.1.6" - "@onekeyfe/hd-transport": "npm:^1.1.6" - "@onekeyfe/hd-web-sdk": "npm:^1.1.6" + "@onekeyfe/hd-core": "npm:^1.1.6-patch.1" + "@onekeyfe/hd-shared": "npm:^1.1.6-patch.1" + "@onekeyfe/hd-transport": "npm:^1.1.6-patch.1" + "@onekeyfe/hd-web-sdk": "npm:^1.1.6-patch.1" "@ts-bridge/cli": "npm:^0.6.3" "@types/ethereumjs-tx": "npm:^1.0.1" "@types/hdkey": "npm:^2.0.1" From cbc7a7d156e35eba3220e8d11a79458a37339ef8 Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Thu, 14 Aug 2025 17:37:17 +0800 Subject: [PATCH 05/10] feat: update jd sdk --- packages/keyring-eth-onekey/package.json | 10 +-- yarn.lock | 78 ++++++++++++------------ 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/packages/keyring-eth-onekey/package.json b/packages/keyring-eth-onekey/package.json index 9b122c61..c34601f0 100644 --- a/packages/keyring-eth-onekey/package.json +++ b/packages/keyring-eth-onekey/package.json @@ -1,6 +1,6 @@ { "name": "eth-onekey-bridge-keyring", - "version": "0.3.2", + "version": "0.3.3", "description": "A MetaMask compatible keyring, for onekey hardware wallets", "keywords": [ "ethereum", @@ -51,10 +51,10 @@ "@metamask/eth-sig-util": "^8.2.0", "@metamask/utils": "^11.1.0", "@noble/hashes": "^1.7.0", - "@onekeyfe/hd-core": "^1.1.6-patch.1", - "@onekeyfe/hd-shared": "^1.1.6-patch.1", - "@onekeyfe/hd-transport": "^1.1.6-patch.1", - "@onekeyfe/hd-web-sdk": "^1.1.6-patch.1", + "@onekeyfe/hd-core": "1.1.6-patch.1", + "@onekeyfe/hd-shared": "1.1.6-patch.1", + "@onekeyfe/hd-transport": "1.1.6-patch.1", + "@onekeyfe/hd-web-sdk": "1.1.6-patch.1", "bytebuffer": "^5.0.1", "hdkey": "^2.1.0", "ripple-address-codec": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index b99b1d1c..36ccb196 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2693,12 +2693,12 @@ __metadata: languageName: node linkType: hard -"@onekeyfe/hd-core@npm:1.1.7, @onekeyfe/hd-core@npm:^1.1.6-patch.1": - version: 1.1.7 - resolution: "@onekeyfe/hd-core@npm:1.1.7" +"@onekeyfe/hd-core@npm:1.1.6-patch.1": + version: 1.1.6-patch.1 + resolution: "@onekeyfe/hd-core@npm:1.1.6-patch.1" dependencies: - "@onekeyfe/hd-shared": "npm:1.1.7" - "@onekeyfe/hd-transport": "npm:1.1.7" + "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" + "@onekeyfe/hd-transport": "npm:1.1.6-patch.1" axios: "npm:^0.27.2" bignumber.js: "npm:^9.0.2" bytebuffer: "npm:^5.0.1" @@ -2707,61 +2707,61 @@ __metadata: semver: "npm:^7.3.7" peerDependencies: "@noble/hashes": ^1.1.3 - ripple-keypairs: ^1.3.1 - checksum: 10/25e8bd0841c3f7f32f3124bbda1eb6246ce04f15fa9201a646325b9a8f980a2ccb3798ea22b2fbbae3a67b69c7f13128f75c56d03aa7624f0b408b623ca81884 + ripple-keypairs: ^2.0.0 + checksum: 10/37f80410ed626bbeb11eb50fddcd3f94188cde1a2f920024f077218751027e7c7251bafe0d05bc8fe5cf8861186d09154ddf48237eae7477111975a8151b8842 languageName: node linkType: hard -"@onekeyfe/hd-shared@npm:1.1.7, @onekeyfe/hd-shared@npm:^1.1.6-patch.1": - version: 1.1.7 - resolution: "@onekeyfe/hd-shared@npm:1.1.7" - checksum: 10/02c22211751c5e4927ad04f527647b3e9634a86af3299808f269a27b2259532eca35ceeaff9973128e3ff52575201952238ba9a9e248fed51c704b21898591cb +"@onekeyfe/hd-shared@npm:1.1.6-patch.1": + version: 1.1.6-patch.1 + resolution: "@onekeyfe/hd-shared@npm:1.1.6-patch.1" + checksum: 10/4bf4ad95971f8ebbd385798f949d80d26aba7e7bf2c0bb9f4c59b327a4b047dc3ec2d256114a042186ea6457292578070afb9423d8c332ac9554cacbbef737a6 languageName: node linkType: hard -"@onekeyfe/hd-transport-http@npm:1.1.7": - version: 1.1.7 - resolution: "@onekeyfe/hd-transport-http@npm:1.1.7" +"@onekeyfe/hd-transport-http@npm:1.1.6-patch.1": + version: 1.1.6-patch.1 + resolution: "@onekeyfe/hd-transport-http@npm:1.1.6-patch.1" dependencies: - "@onekeyfe/hd-shared": "npm:1.1.7" - "@onekeyfe/hd-transport": "npm:1.1.7" + "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" + "@onekeyfe/hd-transport": "npm:1.1.6-patch.1" axios: "npm:^0.27.2" secure-json-parse: "npm:^4.0.0" - checksum: 10/7525de83528fe46e56764150cd9ecac1080d5943a44c9d39848d6df0677c68c87f4a60becf08148bfcfa6be7c5ae9a9e4910ec8dfd96e4aaecff72893b49ec59 + checksum: 10/a85a4f072fa7a3301c0375b65cfda4019d3b40bd1c27a0d4be50ca6a94880e78bb0c7ce1d00f3c5c33f7e8b694ba2c53046fee46c1969338410ded2355ee0d8f languageName: node linkType: hard -"@onekeyfe/hd-transport-web-device@npm:1.1.7": - version: 1.1.7 - resolution: "@onekeyfe/hd-transport-web-device@npm:1.1.7" +"@onekeyfe/hd-transport-web-device@npm:1.1.6-patch.1": + version: 1.1.6-patch.1 + resolution: "@onekeyfe/hd-transport-web-device@npm:1.1.6-patch.1" dependencies: - "@onekeyfe/hd-shared": "npm:1.1.7" - "@onekeyfe/hd-transport": "npm:1.1.7" - checksum: 10/8b2e95a9035fcec6549b89b185425a234fe50cffab0190f3ade68b41a48010677ab7ed0b1b6078c325a63784d299c6d3cfe21e5d0dca072a8cb2611125011685 + "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" + "@onekeyfe/hd-transport": "npm:1.1.6-patch.1" + checksum: 10/83e3bcebe243f4b3bb49d2cf8a3fb1ce76e81ac6d6e4b98cc8ee0c81a90f633bf385185858f8bd886267f66607ccc20a450c3fa9d31e342ec1beef9b983e464b languageName: node linkType: hard -"@onekeyfe/hd-transport@npm:1.1.7, @onekeyfe/hd-transport@npm:^1.1.6-patch.1": - version: 1.1.7 - resolution: "@onekeyfe/hd-transport@npm:1.1.7" +"@onekeyfe/hd-transport@npm:1.1.6-patch.1": + version: 1.1.6-patch.1 + resolution: "@onekeyfe/hd-transport@npm:1.1.6-patch.1" dependencies: bytebuffer: "npm:^5.0.1" long: "npm:^4.0.0" protobufjs: "npm:^6.11.2" - checksum: 10/b4c2f385e94551ee8929038c9d3940e383951f955e757be7e4b7c9d72ff0e8adc699e07446ee3ba4e7ca85cec614cff802d66140d03d313f5b438ec512bc2aeb + checksum: 10/fa9c79ac477b42aaabd790ee87bde04474bfed5e8cd095df580e937440e0dddf3516554a9c2dbb4c17b36009eadadf27727ee0449d2d877392ded34d124e80c6 languageName: node linkType: hard -"@onekeyfe/hd-web-sdk@npm:^1.1.6-patch.1": - version: 1.1.7 - resolution: "@onekeyfe/hd-web-sdk@npm:1.1.7" +"@onekeyfe/hd-web-sdk@npm:1.1.6-patch.1": + version: 1.1.6-patch.1 + resolution: "@onekeyfe/hd-web-sdk@npm:1.1.6-patch.1" dependencies: "@onekeyfe/cross-inpage-provider-core": "npm:^0.0.17" - "@onekeyfe/hd-core": "npm:1.1.7" - "@onekeyfe/hd-shared": "npm:1.1.7" - "@onekeyfe/hd-transport-http": "npm:1.1.7" - "@onekeyfe/hd-transport-web-device": "npm:1.1.7" - checksum: 10/fd70db6be81761e81cb02c67eb5e9d865aa15d76d0d312ee7986c26b2939a124afad96bf29c758ad425597acee8c8ea2c5e3e02480b50c0a7de70d87a61be526 + "@onekeyfe/hd-core": "npm:1.1.6-patch.1" + "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" + "@onekeyfe/hd-transport-http": "npm:1.1.6-patch.1" + "@onekeyfe/hd-transport-web-device": "npm:1.1.6-patch.1" + checksum: 10/a9f85147f380ab724c4a465454e8002616343af1957d80d2793c6f5a3c1d73f4ac04c4dcf318307088922b0dc4f6fb7c29ecf5ac5175262e2be7afaa298fec71 languageName: node linkType: hard @@ -6851,10 +6851,10 @@ __metadata: "@metamask/keyring-utils": "workspace:^" "@metamask/utils": "npm:^11.1.0" "@noble/hashes": "npm:^1.7.0" - "@onekeyfe/hd-core": "npm:^1.1.6-patch.1" - "@onekeyfe/hd-shared": "npm:^1.1.6-patch.1" - "@onekeyfe/hd-transport": "npm:^1.1.6-patch.1" - "@onekeyfe/hd-web-sdk": "npm:^1.1.6-patch.1" + "@onekeyfe/hd-core": "npm:1.1.6-patch.1" + "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" + "@onekeyfe/hd-transport": "npm:1.1.6-patch.1" + "@onekeyfe/hd-web-sdk": "npm:1.1.6-patch.1" "@ts-bridge/cli": "npm:^0.6.3" "@types/ethereumjs-tx": "npm:^1.0.1" "@types/hdkey": "npm:^2.0.1" From f8dc1e0f8c1f405b5b026db110f90164a8e5bb69 Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Thu, 14 Aug 2025 22:05:51 +0800 Subject: [PATCH 06/10] fix: build --- packages/keyring-eth-onekey/package.json | 5 ++++- .../keyring-eth-onekey/src/onekey-keyring.ts | 12 ++++++------ .../keyring-eth-onekey/tsconfig.build.json | 1 - packages/keyring-eth-onekey/tsconfig.json | 1 - tsconfig.build.json | 1 + yarn.lock | 18 ++++++++++++++++++ 6 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/keyring-eth-onekey/package.json b/packages/keyring-eth-onekey/package.json index c34601f0..47e10c5d 100644 --- a/packages/keyring-eth-onekey/package.json +++ b/packages/keyring-eth-onekey/package.json @@ -1,6 +1,6 @@ { "name": "eth-onekey-bridge-keyring", - "version": "0.3.3", + "version": "0.3.4", "description": "A MetaMask compatible keyring, for onekey hardware wallets", "keywords": [ "ethereum", @@ -41,6 +41,7 @@ "changelog:update": "../../scripts/update-changelog.sh @metamask/eth-trezor-keyring", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eth-trezor-keyring", "publish:preview": "yarn npm publish --tag preview", + "publish": "yarn npm publish", "test": "jest && jest-it-up", "test:clean": "jest --clearCache", "test:watch": "jest --watch" @@ -67,6 +68,7 @@ "@metamask/auto-changelog": "^3.4.4", "@metamask/keyring-utils": "workspace:^", "@ts-bridge/cli": "^0.6.3", + "@types/bytebuffer": "^5.0.49", "@types/ethereumjs-tx": "^1.0.1", "@types/hdkey": "^2.0.1", "@types/jest": "^29.5.12", @@ -103,6 +105,7 @@ "hdkey>secp256k1": false, "ethereumjs-tx>ethereumjs-util>ethereum-cryptography>keccak": false, "ethereumjs-tx>ethereumjs-util>ethereum-cryptography>secp256k1": false, + "@onekeyfe/hd-transport>protobufjs": false, "@onekeyfe/hd-core>@onekeyfe/hd-transport>protobufjs": false, "@onekeyfe/hd-web-sdk>@onekeyfe/hd-core>@onekeyfe/hd-transport>protobufjs": false } diff --git a/packages/keyring-eth-onekey/src/onekey-keyring.ts b/packages/keyring-eth-onekey/src/onekey-keyring.ts index e279c044..300369fc 100644 --- a/packages/keyring-eth-onekey/src/onekey-keyring.ts +++ b/packages/keyring-eth-onekey/src/onekey-keyring.ts @@ -35,7 +35,7 @@ enum NetworkApiUrls { export type AccountDetails = { index?: number; hdPath: string; - passphraseState?: string; + passphraseState?: string | undefined; }; export type AccountPageEntry = { @@ -229,7 +229,7 @@ export class OneKeyKeyring extends EventEmitter { showOnOneKey: false, chainId: 1, path: this.#getBasePath(), - passphraseState: this.passphraseState, + passphraseState: this.passphraseState ?? '', }) .then(async (res) => { if (res.success) { @@ -418,14 +418,14 @@ export class OneKeyKeyring extends EventEmitter { ...tx.toJSON(), chainId, to: this.#normalize(Buffer.from(tx.to?.bytes ?? [])), - } as EVMSignTransactionParams['transaction']; + } as unknown as EVMSignTransactionParams['transaction']; } try { const details = this.#accountDetailsFromAddress(address); const response = await this.bridge.ethereumSignTransaction({ path: details.hdPath, - passphraseState: details.passphraseState, + passphraseState: details.passphraseState ?? '', useEmptyPassphrase: isEmptyPassphrase(details.passphraseState), transaction, }); @@ -464,7 +464,7 @@ export class OneKeyKeyring extends EventEmitter { this.bridge .ethereumSignMessage({ path: details.hdPath, - passphraseState: details.passphraseState, + passphraseState: details.passphraseState ?? '', useEmptyPassphrase: isEmptyPassphrase(details.passphraseState), messageHex: ethUtil.stripHexPrefix(message), }) @@ -517,7 +517,7 @@ export class OneKeyKeyring extends EventEmitter { const details = this.#accountDetailsFromAddress(address); const response = await this.bridge.ethereumSignTypedData({ path: details.hdPath, - passphraseState: details.passphraseState, + passphraseState: details.passphraseState ?? '', useEmptyPassphrase: isEmptyPassphrase(details.passphraseState), data: data as EthereumSignTypedDataMessage, domainHash, diff --git a/packages/keyring-eth-onekey/tsconfig.build.json b/packages/keyring-eth-onekey/tsconfig.build.json index 926b19e7..9bcd3d13 100644 --- a/packages/keyring-eth-onekey/tsconfig.build.json +++ b/packages/keyring-eth-onekey/tsconfig.build.json @@ -5,7 +5,6 @@ "outDir": "dist", "rootDir": "src", "exactOptionalPropertyTypes": false, - // circumvent missing types in @trezor/connect-web, please see https://github.com/trezor/trezor-suite/issues/10389 "skipLibCheck": true, "lib": ["ES2020"], "target": "es2017" diff --git a/packages/keyring-eth-onekey/tsconfig.json b/packages/keyring-eth-onekey/tsconfig.json index 16c03416..5ad645d0 100644 --- a/packages/keyring-eth-onekey/tsconfig.json +++ b/packages/keyring-eth-onekey/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "baseUrl": "./", "exactOptionalPropertyTypes": false, - // circumvent missing types in @trezor/connect-web, please see https://github.com/trezor/trezor-suite/issues/10389 "skipLibCheck": true, "lib": ["ES2020"], "target": "es2017" diff --git a/tsconfig.build.json b/tsconfig.build.json index 60377db1..95d164ed 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -4,6 +4,7 @@ { "path": "./packages/keyring-internal-api/tsconfig.build.json" }, { "path": "./packages/keyring-eth-ledger-bridge/tsconfig.build.json" }, { "path": "./packages/keyring-eth-qr/tsconfig.build.json" }, + { "path": "./packages/keyring-eth-onekey/tsconfig.build.json" }, { "path": "./packages/keyring-eth-simple/tsconfig.build.json" }, { "path": "./packages/keyring-eth-trezor/tsconfig.build.json" }, { "path": "./packages/keyring-eth-hd/tsconfig.build.json" }, diff --git a/yarn.lock b/yarn.lock index 36ccb196..9eddd162 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4017,6 +4017,16 @@ __metadata: languageName: node linkType: hard +"@types/bytebuffer@npm:^5.0.49": + version: 5.0.49 + resolution: "@types/bytebuffer@npm:5.0.49" + dependencies: + "@types/long": "npm:^3.0.0" + "@types/node": "npm:*" + checksum: 10/31eb2521d2710f256c3d17a3e8d87f04394f335b29f7276c31c054ddbf4795146f2663effa3b6e910442da69238e994d2db9f7d5918eead4313e3f9e29165932 + languageName: node + linkType: hard + "@types/color-name@npm:^1.1.1": version: 1.1.1 resolution: "@types/color-name@npm:1.1.1" @@ -4138,6 +4148,13 @@ __metadata: languageName: node linkType: hard +"@types/long@npm:^3.0.0": + version: 3.0.32 + resolution: "@types/long@npm:3.0.32" + checksum: 10/cc5422875a085b49b74ffeb5c60a8681d30f700859a8931012b4a58c5c6005cdacb4d3ce3e5af7a7f579ee20d5c2e442a773a83b3a4f7a2d39795a7a8e9a962d + languageName: node + linkType: hard + "@types/long@npm:^4.0.1": version: 4.0.2 resolution: "@types/long@npm:4.0.2" @@ -6856,6 +6873,7 @@ __metadata: "@onekeyfe/hd-transport": "npm:1.1.6-patch.1" "@onekeyfe/hd-web-sdk": "npm:1.1.6-patch.1" "@ts-bridge/cli": "npm:^0.6.3" + "@types/bytebuffer": "npm:^5.0.49" "@types/ethereumjs-tx": "npm:^1.0.1" "@types/hdkey": "npm:^2.0.1" "@types/jest": "npm:^29.5.12" From 04710f621627e7ccd478a6f1fbdf7c72a2718ca2 Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Thu, 14 Aug 2025 22:40:53 +0800 Subject: [PATCH 07/10] feat: add error tip --- packages/keyring-eth-onekey/package.json | 2 +- packages/keyring-eth-onekey/src/onekey-web-bridge.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/keyring-eth-onekey/package.json b/packages/keyring-eth-onekey/package.json index 47e10c5d..533862b3 100644 --- a/packages/keyring-eth-onekey/package.json +++ b/packages/keyring-eth-onekey/package.json @@ -1,6 +1,6 @@ { "name": "eth-onekey-bridge-keyring", - "version": "0.3.4", + "version": "0.3.5", "description": "A MetaMask compatible keyring, for onekey hardware wallets", "keywords": [ "ethereum", diff --git a/packages/keyring-eth-onekey/src/onekey-web-bridge.ts b/packages/keyring-eth-onekey/src/onekey-web-bridge.ts index 677ed867..25ef4de4 100644 --- a/packages/keyring-eth-onekey/src/onekey-web-bridge.ts +++ b/packages/keyring-eth-onekey/src/onekey-web-bridge.ts @@ -54,6 +54,7 @@ export class OneKeyWebBridge implements OneKeyBridge { HardwareErrorCode.CallMethodNeedUpgradeFirmware, HardwareErrorCode.DeviceCheckPassphraseStateError, HardwareErrorCode.DeviceCheckUnlockTypeError, + HardwareErrorCode.SelectDevice, ]; if (code && typeof code === 'number' && errorCodes.includes(code)) { From deea5da7dfe507dd876b15746bd5ffb15b4cb94f Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Thu, 21 Aug 2025 21:27:21 +0800 Subject: [PATCH 08/10] chore: add test case --- packages/keyring-eth-onekey/CHANGELOG.md | 15 + packages/keyring-eth-onekey/jest.config.js | 8 +- packages/keyring-eth-onekey/package.json | 25 +- .../src/onekey-keyring.test.ts | 1215 +++++++++++++++++ .../keyring-eth-onekey/src/onekey-keyring.ts | 47 +- .../src/onekey-web-bridge.test.ts | 636 +++++++++ .../src/onekey-web-bridge.ts | 4 - yarn.lock | 292 ++-- 8 files changed, 2122 insertions(+), 120 deletions(-) create mode 100644 packages/keyring-eth-onekey/CHANGELOG.md create mode 100644 packages/keyring-eth-onekey/src/onekey-keyring.test.ts create mode 100644 packages/keyring-eth-onekey/src/onekey-web-bridge.test.ts diff --git a/packages/keyring-eth-onekey/CHANGELOG.md b/packages/keyring-eth-onekey/CHANGELOG.md new file mode 100644 index 00000000..7d41f673 --- /dev/null +++ b/packages/keyring-eth-onekey/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Changed + +- Bump axios +- Init Project + +[Unreleased]: https://github.com/MetaMask/accounts/ diff --git a/packages/keyring-eth-onekey/jest.config.js b/packages/keyring-eth-onekey/jest.config.js index 0bb9dd48..4b5c9805 100644 --- a/packages/keyring-eth-onekey/jest.config.js +++ b/packages/keyring-eth-onekey/jest.config.js @@ -23,10 +23,10 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 52.38, - functions: 91.22, - lines: 90.15, - statements: 90.35, + branches: 74.2, + functions: 91.25, + lines: 92.78, + statements: 92.83, }, }, }); diff --git a/packages/keyring-eth-onekey/package.json b/packages/keyring-eth-onekey/package.json index 533862b3..2777609a 100644 --- a/packages/keyring-eth-onekey/package.json +++ b/packages/keyring-eth-onekey/package.json @@ -1,6 +1,6 @@ { - "name": "eth-onekey-bridge-keyring", - "version": "0.3.5", + "name": "@metamask/eth-onekey-keyring", + "version": "0.1.0", "description": "A MetaMask compatible keyring, for onekey hardware wallets", "keywords": [ "ethereum", @@ -38,10 +38,10 @@ "build": "ts-bridge --project tsconfig.build.json --no-references", "build:clean": "yarn build --clean", "build:docs": "typedoc", - "changelog:update": "../../scripts/update-changelog.sh @metamask/eth-trezor-keyring", - "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eth-trezor-keyring", - "publish:preview": "yarn npm publish --tag preview", + "changelog:update": "../../scripts/update-changelog.sh @metamask/eth-onekey-keyring", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eth-onekey-keyring", "publish": "yarn npm publish", + "publish:preview": "yarn npm publish --tag preview", "test": "jest && jest-it-up", "test:clean": "jest --clearCache", "test:watch": "jest --watch" @@ -50,23 +50,18 @@ "@ethereumjs/tx": "^5.4.0", "@ethereumjs/util": "^9.1.0", "@metamask/eth-sig-util": "^8.2.0", - "@metamask/utils": "^11.1.0", "@noble/hashes": "^1.7.0", - "@onekeyfe/hd-core": "1.1.6-patch.1", - "@onekeyfe/hd-shared": "1.1.6-patch.1", - "@onekeyfe/hd-transport": "1.1.6-patch.1", - "@onekeyfe/hd-web-sdk": "1.1.6-patch.1", - "bytebuffer": "^5.0.1", - "hdkey": "^2.1.0", - "ripple-address-codec": "^5.0.0", - "tslib": "^2.6.2" + "@onekeyfe/hd-core": "1.1.6-patch.4", + "@onekeyfe/hd-shared": "1.1.6-patch.4", + "@onekeyfe/hd-transport": "1.1.6-patch.4", + "@onekeyfe/hd-web-sdk": "1.1.6-patch.4", + "hdkey": "^2.1.0" }, "devDependencies": { "@ethereumjs/common": "^4.4.0", "@lavamoat/allow-scripts": "^3.2.1", "@lavamoat/preinstall-always-fail": "^2.1.0", "@metamask/auto-changelog": "^3.4.4", - "@metamask/keyring-utils": "workspace:^", "@ts-bridge/cli": "^0.6.3", "@types/bytebuffer": "^5.0.49", "@types/ethereumjs-tx": "^1.0.1", diff --git a/packages/keyring-eth-onekey/src/onekey-keyring.test.ts b/packages/keyring-eth-onekey/src/onekey-keyring.test.ts new file mode 100644 index 00000000..79ec997d --- /dev/null +++ b/packages/keyring-eth-onekey/src/onekey-keyring.test.ts @@ -0,0 +1,1215 @@ +/* eslint-disable jest/no-conditional-expect */ +import { Common, Chain, Hardfork } from '@ethereumjs/common'; +import type { TypedTransaction } from '@ethereumjs/tx'; +import { + TransactionFactory, + FeeMarketEIP1559Transaction, +} from '@ethereumjs/tx'; +import { Address } from '@ethereumjs/util'; +import { SignTypedDataVersion } from '@metamask/eth-sig-util'; +// eslint-disable-next-line @typescript-eslint/naming-convention +import EthereumTx from 'ethereumjs-tx'; +// eslint-disable-next-line @typescript-eslint/naming-convention +import HDKey from 'hdkey'; +import * as sinon from 'sinon'; + +import type { OneKeyBridge } from './onekey-bridge'; +import type { AccountDetails } from './onekey-keyring'; +import { OneKeyKeyring } from './onekey-keyring'; +import { OneKeyWebBridge } from './onekey-web-bridge'; + +const CONNECT_SRC = 'https://jssdk.onekey.so/1.1.5/'; +const fakeAccounts = [ + '0xF30952A1c534CDE7bC471380065726fa8686dfB3', + '0x44fe3Cf56CaF651C4bD34Ae6dbcffa34e9e3b84B', + '0x8Ee3374Fa705C1F939715871faf91d4348D5b906', + '0xEF69e24dE9CdEe93C4736FE29791E45d5D4CFd6A', + '0xC668a5116A045e9162902795021907Cb15aa2620', + '0xbF519F7a6D8E72266825D770C60dbac55a3baeb9', + '0x0258632Fe2F91011e06375eB0E6f8673C0463204', + '0x4fC1700C0C61980aef0Fb9bDBA67D8a25B5d4335', + '0xeEC5D417152aE295c047FB0B0eBd7c7090dDedEb', + '0xd3f978B9eEEdB68A38CF252B3779afbeb3623fDf', + '0xd819fE2beD53f44825F66873a159B687736d3092', + '0xE761dA62f053ad9eE221d325657535991Ab659bD', + '0xd4F1686961642340a80334b5171d85Bbd390c691', + '0x6772C4B1E841b295960Bb4662dceD9bb71726357', + '0x41bEAD6585eCA6c79B553Ca136f0DFA78A006899', +] as const; + +const fakeXPubKey = + 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt'; +const fakeHdKey = HDKey.fromExtendedKey(fakeXPubKey); +const fakeTx = new EthereumTx({ + nonce: '0x00', + gasPrice: '0x09184e72a000', + gasLimit: '0x2710', + to: '0x0000000000000000000000000000000000000000', + value: '0x00', + data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', + // EIP 155 chainId - mainnet: 1, ropsten: 3 + chainId: 1, +}); + +const common = new Common({ chain: 'mainnet' }); +const commonEIP1559 = new Common({ + chain: Chain.Mainnet, + hardfork: Hardfork.London, +}); +const newFakeTx = TransactionFactory.fromTxData( + { + nonce: '0x00', + gasPrice: '0x09184e72a000', + gasLimit: '0x2710', + to: '0x0000000000000000000000000000000000000000', + value: '0x00', + data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', + }, + { common, freeze: false }, +); + +const contractDeploymentFakeTx = TransactionFactory.fromTxData( + { + nonce: '0x00', + gasPrice: '0x09184e72a000', + gasLimit: '0x2710', + value: '0x00', + data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', + }, + { common, freeze: false }, +); + +const fakeTypeTwoTx = FeeMarketEIP1559Transaction.fromTxData( + { + nonce: '0x00', + maxFeePerGas: '0x19184e72a000', + maxPriorityFeePerGas: '0x09184e72a000', + gasLimit: '0x2710', + to: '0x0000000000000000000000000000000000000000', + value: '0x00', + data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', + type: 2, + v: '0x01', + }, + { common: commonEIP1559, freeze: false }, +); + +describe('OneKeyKeyring', function () { + let keyring: OneKeyKeyring; + let bridge: OneKeyBridge; + + beforeEach(async function () { + bridge = new OneKeyWebBridge(); + keyring = new OneKeyKeyring({ bridge }); + keyring.hdk = fakeHdKey; + keyring.accountDetails = { + [fakeAccounts[0]]: { + index: 0, + hdPath: `m/44'/60'/0'/0/0`, + passphraseState: '', + }, + }; + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('Keyring.type', function () { + it('is a class property that returns the type string.', function () { + const { type } = OneKeyKeyring; + expect(typeof type).toBe('string'); + }); + + it('returns the correct value', function () { + const { type } = keyring; + const correct = OneKeyKeyring.type; + expect(type).toBe(correct); + }); + }); + + describe('constructor', function () { + it('constructs', async function () { + const keyringInstance = new OneKeyKeyring({ bridge }); + expect(typeof keyringInstance).toBe('object'); + const accounts = await keyringInstance.getAccounts(); + expect(Array.isArray(accounts)).toBe(true); + }); + + it('throws if a bridge is not provided', async function () { + expect( + () => + new OneKeyKeyring({ + bridge: undefined as unknown as OneKeyBridge, + }), + ).toThrow('Bridge is a required dependency for the keyring'); + }); + }); + + describe('init', function () { + it('initialises the bridge', async function () { + const initStub = sinon.stub().resolves(); + bridge.init = initStub; + + await keyring.init({ + fetchConfig: true, + connectSrc: CONNECT_SRC, + env: 'web', + }); + + expect(initStub.calledOnce).toBe(true); + sinon.assert.calledWithExactly(initStub, { + fetchConfig: true, + connectSrc: CONNECT_SRC, + env: 'web', + }); + }); + }); + + describe('destroy', function () { + it('calls dispose on bridge', async function () { + const disposeStub = sinon.stub().resolves(); + bridge.dispose = disposeStub; + + await keyring.destroy(); + + expect(disposeStub.calledOnce).toBe(true); + sinon.assert.calledWithExactly(disposeStub); + }); + }); + + describe('serialize', function () { + it('serializes an instance', async function () { + const output = await keyring.serialize(); + expect(output.page).toBe(0); + expect(output.hdPath).toBe(`m/44'/60'/0'/0`); + expect(Array.isArray(output.accounts)).toBe(true); + expect(output.accounts).toHaveLength(0); + }); + }); + + describe('deserialize', function () { + it('serializes what it deserializes', async function () { + const someHdPath = `m/44'/60'/0'/1`; + await keyring.deserialize({ + page: 10, + hdPath: someHdPath, + accounts: [], + }); + const serialized = await keyring.serialize(); + expect(serialized.accounts).toHaveLength(0); + expect(serialized.page).toBe(10); + expect(serialized.hdPath).toBe(someHdPath); + }); + }); + + describe('isUnlocked', function () { + it('should return true if we have a public key', function () { + expect(keyring.isUnlocked()).toBe(true); + }); + }); + + describe('unlock', function () { + it('should resolve if we have a public key', async function () { + expect(async () => { + await keyring.unlock(); + }).not.toThrow(); + }); + + it('should call OneKeyWebBridge.getPublicKey if we dont have a public key', async function () { + const getPassphraseStateStub = sinon.stub().resolves({ + success: true, + payload: '', + }); + const getPublicKeyStub = sinon.stub().resolves({ + success: true, + payload: { + publicKey: fakeHdKey.publicKey.toString('hex'), + chainCode: fakeHdKey.chainCode.toString('hex'), + }, + }); + bridge.getPassphraseState = getPassphraseStateStub; + bridge.getPublicKey = getPublicKeyStub; + + keyring.hdk = new HDKey(); + try { + await keyring.unlock(); + } catch { + // Since we only care about ensuring our function gets called, + // we want to ignore warnings due to stub data + } + + expect(getPublicKeyStub.calledOnce).toBe(true); + sinon.assert.calledWithExactly(getPublicKeyStub, { + showOnOneKey: false, + chainId: 1, + path: "m/44'/60'/0'", + passphraseState: '', + }); + }); + }); + + describe('setAccountToUnlock', function () { + it('should set unlockedAccount', function () { + keyring.setAccountToUnlock(3); + expect(keyring.unlockedAccount).toBe(3); + }); + }); + + describe('addAccounts', function () { + describe('with no arguments', function () { + it('returns a single account', async function () { + keyring.setAccountToUnlock(0); + const accounts = await keyring.addAccounts(1); + expect(accounts).toHaveLength(1); + }); + + it('returns the custom accounts desired', async function () { + keyring.setAccountToUnlock(0); + await keyring.addAccounts(1); + keyring.setAccountToUnlock(2); + await keyring.addAccounts(1); + + const accounts = await keyring.getAccounts(); + expect(accounts[0]).toBe(fakeAccounts[0]); + expect(accounts[1]).toBe(fakeAccounts[2]); + }); + }); + + describe('with a numeric argument', function () { + it('returns that number of accounts', async function () { + keyring.setAccountToUnlock(0); + const firstBatch = await keyring.addAccounts(3); + keyring.setAccountToUnlock(3); + const secondBatch = await keyring.addAccounts(2); + + expect(firstBatch).toHaveLength(3); + expect(secondBatch).toHaveLength(2); + }); + + it('returns the expected accounts', async function () { + keyring.setAccountToUnlock(0); + const firstBatch = await keyring.addAccounts(3); + keyring.setAccountToUnlock(3); + const secondBatch = await keyring.addAccounts(2); + + expect(firstBatch).toStrictEqual([ + fakeAccounts[0], + fakeAccounts[1], + fakeAccounts[2], + ]); + expect(secondBatch).toStrictEqual([fakeAccounts[3], fakeAccounts[4]]); + }); + }); + }); + + describe('removeAccount', function () { + describe('if the account exists', function () { + it('should remove that account', async function () { + keyring.setAccountToUnlock(0); + const accounts = await keyring.addAccounts(1); + expect(accounts).toHaveLength(1); + keyring.removeAccount(fakeAccounts[0]); + const accountsAfterRemoval = await keyring.getAccounts(); + expect(accountsAfterRemoval).toHaveLength(0); + }); + + it('should remove only the account requested', async function () { + keyring.setAccountToUnlock(0); + await keyring.addAccounts(1); + keyring.setAccountToUnlock(1); + await keyring.addAccounts(1); + + let accounts = await keyring.getAccounts(); + expect(accounts).toHaveLength(2); + + keyring.removeAccount(fakeAccounts[0]); + accounts = await keyring.getAccounts(); + + expect(accounts).toHaveLength(1); + expect(accounts[0]).toBe(fakeAccounts[1]); + }); + }); + + describe('if the account does not exist', function () { + it('should throw an error', function () { + const unexistingAccount = '0x0000000000000000000000000000000000000000'; + expect(() => { + keyring.removeAccount(unexistingAccount); + }).toThrow(`Address ${unexistingAccount} not found in this keyring`); + }); + }); + }); + + describe('getFirstPage', function () { + it('should set the currentPage to 1', async function () { + await keyring.getFirstPage(); + expect(keyring.page).toBe(1); + }); + + it('should return the list of accounts for current page', async function () { + const accounts = await keyring.getFirstPage(); + + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); + }); + }); + + describe('getNextPage', function () { + it('should return the list of accounts for current page', async function () { + const accounts = await keyring.getNextPage(); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); + }); + + it('should be able to advance to the next page', async function () { + // manually advance 1 page + await keyring.getNextPage(); + + const accounts = await keyring.getNextPage(); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[keyring.perPage + 0]); + expect(accounts[1]?.address).toBe(fakeAccounts[keyring.perPage + 1]); + expect(accounts[2]?.address).toBe(fakeAccounts[keyring.perPage + 2]); + expect(accounts[3]?.address).toBe(fakeAccounts[keyring.perPage + 3]); + expect(accounts[4]?.address).toBe(fakeAccounts[keyring.perPage + 4]); + }); + }); + + describe('getPreviousPage', function () { + it('should return the list of accounts for current page', async function () { + // manually advance 1 page + await keyring.getNextPage(); + const accounts = await keyring.getPreviousPage(); + + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); + }); + + it('should be able to go back to the previous page', async function () { + // manually advance 1 page + await keyring.getNextPage(); + const accounts = await keyring.getPreviousPage(); + + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); + }); + }); + + describe('getAccounts', function () { + const accountIndex = 5; + let accounts: string[] = []; + beforeEach(async function () { + keyring.setAccountToUnlock(accountIndex); + await keyring.addAccounts(1); + accounts = (await keyring.getAccounts()) as string[]; + }); + + it('returns an array of accounts', function () { + expect(Array.isArray(accounts)).toBe(true); + expect(accounts).toHaveLength(1); + }); + + it('returns the expected', function () { + const expectedAccount = fakeAccounts[accountIndex]; + expect(accounts[0]).toBe(expectedAccount); + }); + }); + + describe('signTransaction', function () { + it('should pass serialized transaction to onekey and return signed tx', async function () { + const ethereumSignTransactionStub = sinon.stub().resolves({ + success: true, + payload: { v: '0x1', r: '0x0', s: '0x0' }, + }); + bridge.ethereumSignTransaction = ethereumSignTransactionStub; + + sinon.stub(fakeTx, 'verifySignature').callsFake(() => true); + sinon + .stub(fakeTx, 'getSenderAddress') + .callsFake(() => + Buffer.from(Address.fromString(fakeAccounts[0]).bytes), + ); + + const returnedTx = await keyring.signTransaction(fakeAccounts[0], fakeTx); + // assert that the v,r,s values got assigned to tx. + expect(returnedTx.v).toBeDefined(); + expect(returnedTx.r).toBeDefined(); + expect(returnedTx.s).toBeDefined(); + // ensure we get a older version transaction back + expect((returnedTx as EthereumTx).getChainId()).toBe(1); + expect((returnedTx as TypedTransaction).common).toBeUndefined(); + expect(ethereumSignTransactionStub.calledOnce).toBe(true); + }); + + it('should pass serialized newer transaction to onekey and return signed tx', async function () { + sinon.stub(TransactionFactory, 'fromTxData').callsFake(() => { + // without having a private key/public key pair in this test, we have + // mock out this method and return the original tx because we can't + // replicate r and s values without the private key. + return newFakeTx; + }); + + const ethereumSignTransactionStub = sinon.stub().resolves({ + success: true, + payload: { v: '0x25', r: '0x0', s: '0x0' }, + }); + bridge.ethereumSignTransaction = ethereumSignTransactionStub; + + sinon + .stub(newFakeTx, 'getSenderAddress') + .callsFake(() => Address.fromString(fakeAccounts[0])); + sinon.stub(newFakeTx, 'verifySignature').callsFake(() => true); + + const returnedTx = await keyring.signTransaction( + fakeAccounts[0], + newFakeTx, + ); + // ensure we get a new version transaction back + // eslint-disable-next-line @typescript-eslint/unbound-method + expect((returnedTx as EthereumTx).getChainId).toBeUndefined(); + expect( + (returnedTx as TypedTransaction).common.chainId().toString(16), + ).toBe('1'); + expect(ethereumSignTransactionStub.calledOnce).toBe(true); + }); + + it('should pass serialized contract deployment transaction to onekey and return signed tx', async function () { + sinon.stub(TransactionFactory, 'fromTxData').callsFake(() => { + // without having a private key/public key pair in this test, we have + // mock out this method and return the original tx because we can't + // replicate r and s values without the private key. + return contractDeploymentFakeTx; + }); + + const ethereumSignTransactionStub = sinon.stub().resolves({ + success: true, + payload: { v: '0x25', r: '0x0', s: '0x0' }, + }); + bridge.ethereumSignTransaction = ethereumSignTransactionStub; + + sinon + .stub(contractDeploymentFakeTx, 'getSenderAddress') + .callsFake(() => Address.fromString(fakeAccounts[0])); + + sinon + .stub(contractDeploymentFakeTx, 'verifySignature') + .callsFake(() => true); + + const returnedTx = await keyring.signTransaction( + fakeAccounts[0], + contractDeploymentFakeTx, + ); + // ensure we get a new version transaction back + // eslint-disable-next-line @typescript-eslint/unbound-method + expect((returnedTx as EthereumTx).getChainId).toBeUndefined(); + expect( + (returnedTx as TypedTransaction).common.chainId().toString(16), + ).toBe('1'); + expect(ethereumSignTransactionStub.calledOnce).toBe(true); + expect(ethereumSignTransactionStub.getCall(0).args[0]).toStrictEqual({ + passphraseState: '', + useEmptyPassphrase: true, + path: `m/44'/60'/0'/0/0`, + transaction: { + ...contractDeploymentFakeTx.toJSON(), + to: '0x', + chainId: 1, + }, + }); + }); + + it('should pass correctly encoded EIP1559 transaction to onekey and return signed tx', async function () { + // Copied from @MetaMask/eth-ledger-bridge-keyring + // Generated by signing fakeTypeTwoTx with an unknown private key + const expectedRSV = { + v: '0x0', + r: '0x5ffb3adeaec80e430e7a7b02d95c5108b6f09a0bdf3cf69869dc1b38d0fb8d3a', + s: '0x28b234a5403d31564e18258df84c51a62683e3f54fa2b106fdc1a9058006a112', + }; + // Override actual address of 0x391535104b6e0Ea6dDC2AD0158aB3Fbd7F04ed1B + const fromTxDataStub = sinon.stub(TransactionFactory, 'fromTxData'); + fromTxDataStub.callsFake((...args) => { + const tx = fromTxDataStub.wrappedMethod(...args); + sinon + .stub(tx, 'getSenderAddress') + .returns(Address.fromString(fakeAccounts[0])); + return tx; + }); + + const ethereumSignTransactionStub = sinon.stub().resolves({ + success: true, + payload: expectedRSV, + }); + bridge.ethereumSignTransaction = ethereumSignTransactionStub; + + const returnedTx = await keyring.signTransaction( + fakeAccounts[0], + fakeTypeTwoTx, + ); + + expect(ethereumSignTransactionStub.calledOnce).toBe(true); + sinon.assert.calledWithExactly(ethereumSignTransactionStub, { + passphraseState: '', + useEmptyPassphrase: true, + path: "m/44'/60'/0'/0/0", + transaction: { + type: '0x2', + chainId: 1, + nonce: '0x0', + maxPriorityFeePerGas: '0x9184e72a000', + maxFeePerGas: '0x19184e72a000', + gasLimit: '0x2710', + to: '0x0000000000000000000000000000000000000000', + value: '0x0', + data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', + accessList: [], + v: '0x1', + r: undefined, + s: undefined, + }, + }); + + expect(returnedTx.toJSON()).toStrictEqual({ + ...fakeTypeTwoTx.toJSON(), + ...expectedRSV, + }); + }); + }); + + describe('signMessage', function () { + it('should call onekeyConnect.ethereumSignMessage', async function () { + const ethereumSignMessageStub = sinon.stub().resolves({}); + bridge.ethereumSignMessage = ethereumSignMessageStub; + + try { + await keyring.signMessage(fakeAccounts[0], 'some msg'); + } catch { + // Since we only care about ensuring our function gets called, + // we want to ignore warnings due to stub data + } + + expect(ethereumSignMessageStub.calledOnce).toBe(true); + }); + }); + + describe('signPersonalMessage', function () { + it('should call onekeyConnect.ethereumSignMessage', async function () { + const ethereumSignMessageStub = sinon.stub().resolves({}); + bridge.ethereumSignMessage = ethereumSignMessageStub; + + try { + await keyring.signPersonalMessage(fakeAccounts[0], 'some msg'); + } catch { + // Since we only care about ensuring our function gets called, + // we want to ignore warnings due to stub data + } + + expect(ethereumSignMessageStub.calledOnce).toBe(true); + }); + }); + + describe('signTypedData', function () { + it('should call onekeyConnect.ethereumSignTypedData', async function () { + const ethereumSignTypedDataStub = sinon.stub().resolves({ + success: true, + payload: { signature: '0x00', address: fakeAccounts[0] }, + }); + bridge.ethereumSignTypedData = ethereumSignTypedDataStub; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore next-line + // eslint-disable-next-line no-invalid-this + this.timeout = 60000; + await keyring.signTypedData( + fakeAccounts[0], + // Message with missing data that @metamask/eth-sig-util accepts + { + types: { EIP712Domain: [], EmptyMessage: [] }, + primaryType: 'EmptyMessage', + domain: {}, + message: {}, + }, + { version: SignTypedDataVersion.V4 }, + ); + + expect(ethereumSignTypedDataStub.calledOnce).toBe(true); + sinon.assert.calledWithExactly(ethereumSignTypedDataStub, { + passphraseState: '', + useEmptyPassphrase: true, + path: "m/44'/60'/0'/0/0", + data: { + // Empty message that onekey-connect/EIP-712 spec accepts + types: { EIP712Domain: [], EmptyMessage: [] }, + primaryType: 'EmptyMessage', + domain: {}, + message: {}, + }, + metamaskV4Compat: true, + domainHash: + '6192106f129ce05c9075d319c1fa6ea9b3ae37cbd0c1ef92e2be7137bb07baa1', + messageHash: + 'c9e71eb57cf9fa86ec670283b58cb15326bb6933c8d8e2ecb2c0849021b3ef42', + }); + }); + }); + + describe('forgetDevice', function () { + it('should clear the content of the keyring', async function () { + // Add an account + keyring.setAccountToUnlock(0); + await keyring.addAccounts(1); + + // Wipe the keyring + keyring.forgetDevice(); + + const accounts = await keyring.getAccounts(); + + expect(keyring.isUnlocked()).toBe(false); + expect(accounts).toHaveLength(0); + }); + }); + + describe('setHdPath', function () { + const initialProperties = { + hdPath: `m/44'/60'/0'/0` as const, + accounts: [fakeAccounts[0]], + page: 2, + }; + + // hdPath?: string; + // accounts?: string[]; + // accountDetails?: Readonly>; + // page?: number; + // passphraseState?: string; + + const accountToUnlock = 1; + + const mockPaths: Record = { + '0x123': { + index: 0, + hdPath: `m/44'/60'/0'/0`, + passphraseState: '123', + }, + }; + + beforeEach(async function () { + await keyring.deserialize(initialProperties); + // eslint-disable-next-line require-atomic-updates + keyring.accountDetails = mockPaths; + keyring.setAccountToUnlock(accountToUnlock); + }); + + it('should do nothing if passed an hdPath equal to the current hdPath', async function () { + keyring.setHdPath(initialProperties.hdPath); + expect(keyring.hdPath).toBe(initialProperties.hdPath); + expect(keyring.accounts).toStrictEqual(initialProperties.accounts); + expect(keyring.page).toBe(initialProperties.page); + expect(keyring.hdk.publicKey.toString('hex')).toBe( + fakeHdKey.publicKey.toString('hex'), + ); + expect(keyring.unlockedAccount).toBe(accountToUnlock); + expect(keyring.accountDetails).toStrictEqual(mockPaths); + }); + + it('should update the hdPath and reset account and page properties if passed a new hdPath', async function () { + const ledgerLegacyHdPathString = `m/44'/60'/0'/x`; + + keyring.setHdPath(ledgerLegacyHdPathString); + + expect(keyring.hdPath).toBe(ledgerLegacyHdPathString); + expect(keyring.accounts).toStrictEqual([]); + expect(keyring.page).toBe(0); + expect(keyring.perPage).toBe(5); + expect(keyring.hdk.publicKey).toBeNull(); + expect(keyring.unlockedAccount).toBe(0); + expect(keyring.accountDetails).toStrictEqual({}); + }); + + it('should throw an error if passed an ledger live hdPath', async function () { + const unsupportedPath = "m/44'/60'/x'/0/0"; + expect(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore next-line + keyring.setHdPath(unsupportedPath); + }).toThrow(`Unknown HD path`); + }); + + it('should throw an error if passed an unsupported hdPath', async function () { + const unsupportedPath = 'unsupported hdPath'; + expect(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore next-line + keyring.setHdPath(unsupportedPath); + }).toThrow(`Unknown HD path`); + }); + }); + + describe('error handling and edge cases', function () { + it('should handle signing errors', async function () { + await keyring.addAccounts(1); + const errorResponse = { + success: false, + payload: { error: 'Signing failed' }, + }; + bridge.ethereumSignTransaction = sinon.stub().resolves(errorResponse); + + try { + await keyring.signTransaction(fakeAccounts[0], fakeTx); + throw new Error('Expected error was not thrown'); + } catch (error) { + // eslint-disable-next-line jest/no-conditional-expect + expect((error as Error).message).toContain('Signing failed'); + } + }); + + it('should handle message signing errors', async function () { + await keyring.addAccounts(1); + const errorResponse = { + success: false, + payload: { error: 'Message signing failed' }, + }; + bridge.ethereumSignMessage = sinon.stub().resolves(errorResponse); + + try { + await keyring.signPersonalMessage(fakeAccounts[0], '0x68656c6c6f'); + throw new Error('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).toContain('Message signing failed'); + } + }); + + it('should handle address verification mismatch in signing', async function () { + await keyring.addAccounts(1); + const wrongAddress = '0x1234567890123456789012345678901234567890'; + const successResponse = { + success: true, + payload: { + v: '0x1', + r: '0x0', + s: '0x0', + address: wrongAddress, + }, + }; + bridge.ethereumSignMessage = sinon.stub().resolves(successResponse); + + try { + await keyring.signPersonalMessage(fakeAccounts[0], '0x68656c6c6f'); + throw new Error('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).toContain( + 'signature doesnt match the right address', + ); + } + }); + + it('should handle getPreviousPage when already on first page', async function () { + keyring.page = 0; + const accounts = await keyring.getPreviousPage(); + expect(accounts).toHaveLength(keyring.perPage); + expect(keyring.page).toBe(1); // When page <= 0, it gets set to 1 + }); + }); + + describe('HD path validation edge cases', function () { + it('should handle Ledger Live HD path correctly', function () { + const ledgerLiveHdPath = "m/44'/60'/0'/x"; + keyring.setHdPath(ledgerLiveHdPath); + expect(keyring.hdPath).toBe(ledgerLiveHdPath); + }); + + it('should handle standard BIP44 HD path correctly', function () { + const standardHdPath = "m/44'/60'/0'/0/x"; + keyring.setHdPath(standardHdPath); + expect(keyring.hdPath).toBe(standardHdPath); + }); + + it('should handle default HD path correctly', function () { + const defaultPath = "m/44'/60'/0'/0"; + keyring.setHdPath(defaultPath); + expect(keyring.hdPath).toBe(defaultPath); + }); + + it('should handle different HD path formats', function () { + // Test different HD path validations + const ledgerLiveHdPath = "m/44'/60'/0'/x"; + keyring.setHdPath(ledgerLiveHdPath); + expect(keyring.hdPath).toBe(ledgerLiveHdPath); + + const standardHdPath = "m/44'/60'/0'/0/x"; + keyring.setHdPath(standardHdPath); + expect(keyring.hdPath).toBe(standardHdPath); + }); + }); + + describe('account filtering', function () { + beforeEach(async function () { + keyring.setAccountToUnlock(0); + await keyring.addAccounts(5); + }); + + it('should handle removeAccount with all accounts removed', function () { + const allAccounts = [...keyring.accounts]; + + allAccounts.forEach((account) => { + keyring.removeAccount(account); + }); + + expect(keyring.accounts).toHaveLength(0); + expect(Object.keys(keyring.accountDetails)).toHaveLength(0); + }); + + it('should handle removeAccount with non-existent account', function () { + const nonExistentAccount = '0x1234567890123456789012345678901234567890'; + + expect(() => { + keyring.removeAccount(nonExistentAccount); + }).toThrow( + 'Address 0x1234567890123456789012345678901234567890 not found in this keyring', + ); + }); + }); + + describe('transaction serialization edge cases', function () { + it('should handle transaction with empty "to" field (contract deployment)', async function () { + await keyring.addAccounts(1); + + const successResponse = { + success: true, + payload: { v: '0x1', r: '0x0', s: '0x0' }, + }; + const ethereumSignTransactionStub = sinon + .stub() + .resolves(successResponse); + bridge.ethereumSignTransaction = ethereumSignTransactionStub; + + sinon.stub(fakeTx, 'verifySignature').callsFake(() => true); + sinon + .stub(fakeTx, 'getSenderAddress') + .callsFake(() => + Buffer.from(Address.fromString(fakeAccounts[0]).bytes), + ); + + // Simulate a contract deployment transaction by setting to to null + const originalTo = fakeTx.to; + // @ts-expect-error - for testing purposes + fakeTx.to = null; + + const result = await keyring.signTransaction(fakeAccounts[0], fakeTx); + expect(result).toBeDefined(); + + const call = ethereumSignTransactionStub.getCall(0); + expect(call.args[0].transaction.to).toBe('0x'); + + // Restore original to value + // eslint-disable-next-line require-atomic-updates + fakeTx.to = originalTo; + }); + + it('should test hex prefix utilities', async function () { + // Test the serialize method + const serialized = await keyring.serialize(); + expect(serialized.hdPath).toMatch(/^m\//u); // Should start with m/ + }); + }); + + describe('HD path private method coverage', function () { + it('should handle standard BIP44 path variations', async function () { + keyring.setHdPath("m/44'/60'/0'/0/x"); + await keyring.unlock(); + await keyring.addAccounts(1); + expect(keyring.accounts).toHaveLength(1); + + keyring.setHdPath("m/44'/60'/0'/0"); + await keyring.unlock(); + await keyring.addAccounts(1); + expect(keyring.accounts.length).toBeGreaterThan(0); + }); + + it('should handle HD path comparison logic', async function () { + expect(keyring.hdPath).toBe("m/44'/60'/0'/0"); + expect(keyring.accounts).toHaveLength(0); + + await keyring.addAccounts(2); + keyring.setHdPath("m/44'/60'/0'/0/x"); + + expect(keyring.hdPath).toBe("m/44'/60'/0'/0/x"); + expect(keyring.accounts).toHaveLength(2); + + keyring.setHdPath("m/44'/60'/0'/0"); + expect(keyring.hdPath).toBe("m/44'/60'/0'/0"); + expect(keyring.accounts).toHaveLength(2); + + keyring.setHdPath("m/44'/60'/0'/x"); + expect(keyring.hdPath).toBe("m/44'/60'/0'/x"); + expect(keyring.accounts).toHaveLength(0); + }); + }); + + describe('additional edge cases for coverage', function () { + it('should handle forgetDevice', function () { + keyring.forgetDevice(); + expect(keyring.accounts).toHaveLength(0); + expect(keyring.page).toBe(0); + expect(keyring.unlockedAccount).toBe(0); + expect(Object.keys(keyring.accountDetails)).toHaveLength(0); + }); + + it('should handle exportAccount error', function () { + expect(() => { + keyring.exportAccount(); + }).toThrow('Not supported on this device'); + }); + + it('should handle isUnlocked when not unlocked', function () { + keyring.hdk = new HDKey(); + expect(keyring.isUnlocked()).toBe(false); + }); + + it('should handle different passphrase states', function () { + keyring.passphraseState = ''; + expect(keyring.passphraseState).toBe(''); + + keyring.passphraseState = undefined; + expect(keyring.passphraseState).toBeUndefined(); + }); + + it('should handle addHexPrefix utility function', function () { + const testMessage = 'hello world'; + const messageHex = Buffer.from(testMessage, 'utf8').toString('hex'); + + // These calls will use add hex prefix indirectly + // eslint-disable-next-line jest/no-restricted-matchers + expect(messageHex).toBeTruthy(); + }); + + it('should handle getName method', function () { + expect(keyring.getName()).toBe('OneKey Hardware'); + }); + + it('should handle init bridge method', async function () { + const initSpy = sinon.stub(bridge, 'init').resolves(); + + await keyring.init({ debug: true }); + expect(initSpy.calledOnce).toBe(true); + + // Test destroy method by calling keyring destroy + await keyring.destroy(); + }); + + it('should handle getNextPage and getPreviousPage correctly', async function () { + const nextPageAccounts = await keyring.getNextPage(); + expect(nextPageAccounts).toHaveLength(keyring.perPage); + expect(keyring.page).toBeGreaterThan(0); + + const prevPageAccounts = await keyring.getPreviousPage(); + expect(prevPageAccounts).toHaveLength(keyring.perPage); + }); + + it('should handle signMessage method', async function () { + await keyring.addAccounts(1); + const expectedSignature = '0xsignature123'; + const signPersonalMessageStub = sinon + .stub(keyring, 'signPersonalMessage') + .resolves(expectedSignature); + + const result = await keyring.signMessage(fakeAccounts[0], '0x68656c6c6f'); + expect(result).toBe(expectedSignature); + expect( + signPersonalMessageStub.calledWith(fakeAccounts[0], '0x68656c6c6f'), + ).toBe(true); + }); + + it('should handle transaction signing with address verification', async function () { + await keyring.addAccounts(1); + const successResponse = { + success: true, + payload: { v: '0x1', r: '0x0', s: '0x0' }, + }; + bridge.ethereumSignTransaction = sinon.stub().resolves(successResponse); + + // Mock the transaction verification to pass + sinon.stub(fakeTx, 'verifySignature').callsFake(() => true); + sinon + .stub(fakeTx, 'getSenderAddress') + .callsFake(() => + Buffer.from(Address.fromString(fakeAccounts[0]).bytes), + ); + + const result = await keyring.signTransaction(fakeAccounts[0], fakeTx); + expect(result).toBeDefined(); + expect(result.v).toBeDefined(); + expect(result.r).toBeDefined(); + expect(result.s).toBeDefined(); + }); + + it('should handle basic unlock scenarios', async function () { + const accounts = await keyring.getAccounts(); + expect(accounts).toStrictEqual([]); + }); + + it('should handle addAccounts error scenarios', async function () { + const unlockStub = sinon + .stub(keyring, 'unlock') + .rejects(new Error('Unlock failed')); + + try { + await keyring.addAccounts(1); + throw new Error('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).toContain('Unlock failed'); + } + + unlockStub.restore(); + }); + + it('should handle bridge constructor error', function () { + expect(() => { + // eslint-disable-next-line no-new + new OneKeyKeyring({ bridge: undefined as unknown as OneKeyBridge }); + }).toThrow('Bridge is a required dependency for the keyring'); + }); + + it('should handle event emission from bridge', function () { + const eventSpy = sinon.stub(keyring, 'emit'); + + expect(keyring.bridge).toBe(bridge); + expect(eventSpy.called).toBe(false); + }); + + describe('HD path variations', function () { + it('should handle Ledger Legacy HD path in setHdPath', function () { + // Cover branches in #isSameHdPath for Ledger Legacy + keyring.setHdPath("m/44'/60'/0'/x"); + expect(keyring.hdPath).toBe("m/44'/60'/0'/x"); + + // Setting the same path should trigger #isSameHdPath but not reset + const originalHdk = keyring.hdk; + keyring.setHdPath("m/44'/60'/0'/x"); // This should call #isSameHdPath and return true + expect(keyring.hdk).toBe(originalHdk); // HDKey should not be reset + }); + + it('should handle path comparison between different Ledger Legacy paths', function () { + // Cover line 687-688 in #isSameHdPath + keyring.setHdPath("m/44'/60'/0'/x"); + + // Change to different Ledger Legacy path - should reset HDKey + keyring.setHdPath("m/44'/60'/0'/x"); + expect(keyring.hdPath).toBe("m/44'/60'/0'/x"); + }); + + it('should handle Standard BIP44 path variations in setHdPath', function () { + // Cover branches in #isSameHdPath for standard BIP44 + keyring.setHdPath("m/44'/60'/0'/0/x"); + expect(keyring.hdPath).toBe("m/44'/60'/0'/0/x"); + + // Test equivalence with defaultHdPath - should trigger #isSameHdPath + keyring.setHdPath("m/44'/60'/0'/0"); // Should be considered same as m/44'/60'/0'/0/x + expect(keyring.hdPath).toBe("m/44'/60'/0'/0"); + }); + + it('should handle default path comparison in #isSameHdPath', function () { + // Cover line 694: return this.hdPath === newHdPath; + // Directly set hdPath to test custom path logic + keyring.hdPath = "m/44'/60'/1'/2/3"; // Custom path not in predefined categories + + const originalHdk = keyring.hdk; + keyring.setHdPath("m/44'/60'/0'/0"); // Different path should reset + expect(keyring.hdPath).toBe("m/44'/60'/0'/0"); + expect(keyring.hdk).not.toBe(originalHdk); // HDKey should be reset + }); + + it('should handle Ledger Legacy path in addAccounts workflow', async function () { + // This will trigger #getPathForIndex with Ledger Legacy path (line 660) + keyring.setHdPath("m/44'/60'/0'/x"); + + // Set up successful unlock + const unlockResult = 'unlocked'; + const unlockSpy = sinon.stub(keyring, 'unlock').resolves(unlockResult); + + // Mock bridge methods to avoid actual hardware calls (33-byte public key) + keyring.hdk.publicKey = Buffer.from(`02${'0'.repeat(64)}`, 'hex'); // 33 bytes + keyring.hdk.chainCode = Buffer.from( + '123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01', + 'hex', + ); // 32 bytes + + const accounts = await keyring.addAccounts(1); + expect(accounts).toHaveLength(1); + + unlockSpy.restore(); + }); + + it('should handle custom path in getPathForIndex', async function () { + // Cover line 668: return `${this.hdPath}/${index}`; + // Directly set hdPath to bypass ALLOWED_HD_PATHS check + keyring.hdPath = "m/44'/60'/1'/2"; // Custom path that doesn't match predefined patterns + + const unlockResult = 'unlocked'; + const unlockSpy = sinon.stub(keyring, 'unlock').resolves(unlockResult); + + keyring.hdk.publicKey = Buffer.from(`02${'0'.repeat(64)}`, 'hex'); // 33 bytes + keyring.hdk.chainCode = Buffer.from( + '123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01', + 'hex', + ); // 32 bytes + + const accounts = await keyring.addAccounts(1); + expect(accounts).toHaveLength(1); + + unlockSpy.restore(); + }); + + it('should handle same custom path in #isSameHdPath', function () { + // Cover line 694: return this.hdPath === newHdPath; when custom paths are the same + keyring.hdPath = "m/44'/60'/5'/6"; // Custom path + + const originalHdk = keyring.hdk; + keyring.setHdPath("m/44'/60'/0'/0"); // Change to allowed path + expect(keyring.hdPath).toBe("m/44'/60'/0'/0"); + expect(keyring.hdk).not.toBe(originalHdk); // HDKey should be reset + }); + + it('should handle Ledger Live HD path errors', async function () { + // Cover lines 641 and 648: throw new Error('Ledger Live is not supported'); + keyring.hdPath = "m/44'/60'/x'/0/0"; // Ledger Live path (not in ALLOWED_HD_PATHS but we set directly) + + const unlockSpy = sinon.stub(keyring, 'unlock').resolves('unlocked'); + keyring.hdk.publicKey = Buffer.from(`02${'0'.repeat(64)}`, 'hex'); + keyring.hdk.chainCode = Buffer.from( + '123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01', + 'hex', + ); + + // This should trigger the Ledger Live error paths during addAccounts + try { + await keyring.addAccounts(1); + // If we get here, the test setup was wrong + expect(true).toBe(false); + } catch (error) { + expect((error as Error).message).toContain( + 'Ledger Live is not supported', + ); + } + + unlockSpy.restore(); + }); + }); + }); +}); diff --git a/packages/keyring-eth-onekey/src/onekey-keyring.ts b/packages/keyring-eth-onekey/src/onekey-keyring.ts index 300369fc..56a51446 100644 --- a/packages/keyring-eth-onekey/src/onekey-keyring.ts +++ b/packages/keyring-eth-onekey/src/onekey-keyring.ts @@ -25,6 +25,15 @@ const pathBase = 'm'; const defaultHdPath = `${pathBase}/44'/60'/0'/0`; const keyringType = 'OneKey Hardware'; +const hdPathString = `m/44'/60'/0'/0/x`; +const ledgerLegacyHdPathString = `m/44'/60'/0'/x`; + +const ALLOWED_HD_PATHS: Record = { + [defaultHdPath]: true, + [hdPathString]: true, + [ledgerLegacyHdPathString]: true, +} as const; + enum NetworkApiUrls { Ropsten = 'https://api-ropsten.etherscan.io', Kovan = 'https://api-kovan.etherscan.io', @@ -193,6 +202,19 @@ export class OneKeyKeyring extends EventEmitter { } setHdPath(hdPath: string): void { + if (!ALLOWED_HD_PATHS[hdPath]) { + throw new Error('Unknown HD path'); + } + + // Reset HDKey if the path changes + if (!this.#isSameHdPath(hdPath)) { + this.hdk = new HDKey(); + this.accounts = []; + this.page = 0; + this.perPage = 5; + this.unlockedAccount = 0; + this.accountDetails = {}; + } this.hdPath = hdPath; } @@ -540,6 +562,7 @@ export class OneKeyKeyring extends EventEmitter { } forgetDevice(): void { + this.hdk = new HDKey(); this.accounts = []; this.page = 0; this.unlockedAccount = 0; @@ -617,12 +640,6 @@ export class OneKeyKeyring extends EventEmitter { if (this.#isLedgerLiveHdPath()) { throw new Error('Ledger Live is not supported'); } - if (this.#isLedgerLegacyHdPath()) { - return `${pathBase}/${index}`; - } - if (this.#isStandardBip44HdPath()) { - return `${pathBase}/0/${index}`; - } return `${pathBase}/${index}`; } @@ -660,8 +677,20 @@ export class OneKeyKeyring extends EventEmitter { } #isStandardBip44HdPath(): boolean { - return ( - this.hdPath === `m/44'/60'/0'/0/x` || this.hdPath === `m/44'/60'/0'/0` - ); + return this.hdPath === `m/44'/60'/0'/0/x` || this.hdPath === defaultHdPath; + } + + #isSameHdPath(newHdPath: string): boolean { + if (this.#isLedgerLiveHdPath()) { + return newHdPath === `m/44'/60'/x'/0/0`; + } + if (this.#isLedgerLegacyHdPath()) { + return newHdPath === `m/44'/60'/0'/x`; + } + if (this.#isStandardBip44HdPath()) { + return newHdPath === `m/44'/60'/0'/0/x` || newHdPath === defaultHdPath; + } + + return this.hdPath === newHdPath; } } diff --git a/packages/keyring-eth-onekey/src/onekey-web-bridge.test.ts b/packages/keyring-eth-onekey/src/onekey-web-bridge.test.ts new file mode 100644 index 00000000..a074fe10 --- /dev/null +++ b/packages/keyring-eth-onekey/src/onekey-web-bridge.test.ts @@ -0,0 +1,636 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { UI_REQUEST, UI_RESPONSE } from '@onekeyfe/hd-core'; +import { HardwareErrorCode } from '@onekeyfe/hd-shared'; + +import { ONEKEY_HARDWARE_UI_EVENT } from './constants'; +import { OneKeyWebBridge } from './onekey-web-bridge'; + +// Mock the dynamic import +const mockHardwareWebSdk = { + init: jest.fn(), + on: jest.fn(), + uiResponse: jest.fn(), + dispose: jest.fn(), + switchTransport: jest.fn(), + evmGetPublicKey: jest.fn(), + getPassphraseState: jest.fn(), + evmSignTransaction: jest.fn(), + evmSignMessage: jest.fn(), + evmSignTypedData: jest.fn(), +}; + +const mockHardwareSDKLowLevel = {}; + +// Mock the dynamic import at module level +jest.mock('@onekeyfe/hd-web-sdk', () => ({ + HardwareWebSdk: mockHardwareWebSdk, + HardwareSDKLowLevel: mockHardwareSDKLowLevel, +})); + +describe('OneKeyWebBridge', function () { + let bridge: OneKeyWebBridge; + + beforeEach(function () { + bridge = new OneKeyWebBridge(); + jest.clearAllMocks(); + }); + + describe('init', function () { + it('should initialize SDK and set up event handlers', async function () { + mockHardwareWebSdk.init.mockResolvedValue(undefined); + + await bridge.init(); + + expect(mockHardwareWebSdk.init).toHaveBeenCalledTimes(1); + expect(mockHardwareWebSdk.init).toHaveBeenCalledWith( + { + debug: true, + fetchConfig: false, + connectSrc: 'https://jssdk.onekey.so/1.1.0/', + env: 'webusb', + }, + mockHardwareSDKLowLevel, + ); + expect(bridge.isSDKInitialized).toBe(true); + expect(bridge.sdk).toBe(mockHardwareWebSdk); + expect(mockHardwareWebSdk.on).toHaveBeenCalledWith( + 'UI_EVENT', + expect.any(Function), + ); + }); + + it('should not initialize again if already initialized', async function () { + bridge.isSDKInitialized = true; + + await bridge.init(); + + expect(mockHardwareWebSdk.init).not.toHaveBeenCalled(); + }); + + it('should handle initialization failure', async function () { + mockHardwareWebSdk.init.mockRejectedValue(new Error('Init failed')); + + await bridge.init(); + + expect(bridge.isSDKInitialized).toBe(false); + expect(bridge.sdk).toBeUndefined(); + }); + + it('should handle PIN request in UI event', async function () { + let uiEventCallback: any; + mockHardwareWebSdk.on.mockImplementation( + (event: string, callback: any) => { + if (event === 'UI_EVENT') { + uiEventCallback = callback; + } + }, + ); + mockHardwareWebSdk.init.mockResolvedValue(undefined); + + await bridge.init(); + + // Simulate PIN request + uiEventCallback({ type: UI_REQUEST.REQUEST_PIN }); + + expect(mockHardwareWebSdk.uiResponse).toHaveBeenCalledWith({ + type: UI_RESPONSE.RECEIVE_PIN, + payload: '@@ONEKEY_INPUT_PIN_IN_DEVICE', + }); + }); + + it('should handle passphrase request in UI event', async function () { + let uiEventCallback: any; + mockHardwareWebSdk.on.mockImplementation( + (event: string, callback: any) => { + if (event === 'UI_EVENT') { + uiEventCallback = callback; + } + }, + ); + mockHardwareWebSdk.init.mockResolvedValue(undefined); + + await bridge.init(); + + // Simulate passphrase request + uiEventCallback({ type: UI_REQUEST.REQUEST_PASSPHRASE }); + + expect(mockHardwareWebSdk.uiResponse).toHaveBeenCalledWith({ + type: UI_RESPONSE.RECEIVE_PASSPHRASE, + payload: { + value: '', + passphraseOnDevice: true, + save: false, + }, + }); + }); + }); + + describe('destroy', function () { + it('should destroy SDK', async function () { + bridge.sdk = mockHardwareWebSdk as any; + bridge.isSDKInitialized = true; + + await bridge.destroy(); + + expect(bridge.isSDKInitialized).toBe(false); + expect(bridge.sdk).toBeUndefined(); + }); + }); + + describe('dispose', function () { + it('should call dispose on SDK', async function () { + bridge.sdk = mockHardwareWebSdk as any; + + await bridge.dispose(); + + expect(mockHardwareWebSdk.dispose).toHaveBeenCalledTimes(1); + }); + + it('should handle dispose when SDK is not initialized', async function () { + bridge.sdk = undefined; + + // eslint-disable-next-line jest/no-restricted-matchers + await expect(bridge.dispose()).resolves.toBeUndefined(); + }); + }); + + describe('updateTransportMethod', function () { + it('should switch transport when SDK is initialized', async function () { + bridge.sdk = mockHardwareWebSdk as any; + + await bridge.updateTransportMethod('webusb'); + + expect(mockHardwareWebSdk.switchTransport).toHaveBeenCalledTimes(1); + expect(mockHardwareWebSdk.switchTransport).toHaveBeenCalledWith('webusb'); + }); + + it('should not switch transport when SDK is not initialized', async function () { + bridge.sdk = undefined; + + await bridge.updateTransportMethod('webusb'); + + expect(mockHardwareWebSdk.switchTransport).not.toHaveBeenCalled(); + }); + }); + + describe('getPublicKey', function () { + it('should call evmGetPublicKey', async function () { + const expectedResult = { + success: true, + payload: { + pub: '0x123', + // eslint-disable-next-line @typescript-eslint/naming-convention + node: { chain_code: 'abc123' }, + }, + }; + mockHardwareWebSdk.evmGetPublicKey.mockResolvedValue(expectedResult); + bridge.sdk = mockHardwareWebSdk as any; + + const params = { + path: "m/44'/60'/0'/0/0", + coin: 'eth', + }; + const result = await bridge.getPublicKey(params); + + expect(mockHardwareWebSdk.evmGetPublicKey).toHaveBeenCalledTimes(1); + expect(mockHardwareWebSdk.evmGetPublicKey).toHaveBeenCalledWith('', '', { + ...params, + skipPassphraseCheck: true, + }); + expect(result).toStrictEqual({ + success: true, + payload: { + publicKey: '0x123', + chainCode: 'abc123', + }, + }); + }); + + it('should handle public key error response', async function () { + const errorResult = { + success: false, + payload: { + error: 'Device not found', + code: 404, + }, + }; + mockHardwareWebSdk.evmGetPublicKey.mockResolvedValue(errorResult); + bridge.sdk = mockHardwareWebSdk as any; + const handleBlockErrorEventSpy = jest + .spyOn(bridge, 'handleBlockErrorEvent') + .mockImplementation(); + + const result = await bridge.getPublicKey({ + path: "m/44'/60'/0'/0/0", + coin: 'eth', + }); + + expect(result).toStrictEqual({ + success: false, + payload: { + error: 'Device not found', + code: 404, + }, + }); + expect(handleBlockErrorEventSpy).toHaveBeenCalledWith(errorResult); + }); + + it('should handle error without code', async function () { + const errorResult = { + success: false, + payload: { + error: 'Some error', + }, + }; + mockHardwareWebSdk.evmGetPublicKey.mockResolvedValue(errorResult); + bridge.sdk = mockHardwareWebSdk as any; + + const result = await bridge.getPublicKey({ + path: "m/44'/60'/0'/0/0", + coin: 'eth', + }); + + expect(result).toStrictEqual({ + success: false, + payload: { + error: 'Some error', + code: undefined, + }, + }); + }); + + it('should return error when SDK is not initialized', async function () { + bridge.sdk = undefined; + + const result = await bridge.getPublicKey({ + path: "m/44'/60'/0'/0/0", + coin: 'eth', + }); + + expect(result).toStrictEqual({ + success: false, + payload: { + error: 'SDK not initialized', + code: 800, + }, + }); + }); + }); + + describe('getPassphraseState', function () { + it('should call getPassphraseState', async function () { + const expectedResult = { + success: true, + payload: 'some-state', + }; + mockHardwareWebSdk.getPassphraseState.mockResolvedValue(expectedResult); + bridge.sdk = mockHardwareWebSdk as any; + + const result = await bridge.getPassphraseState(); + + expect(mockHardwareWebSdk.getPassphraseState).toHaveBeenCalledTimes(1); + expect(mockHardwareWebSdk.getPassphraseState).toHaveBeenCalledWith(''); + expect(result).toBe(expectedResult); + }); + + it('should handle passphrase state error response and call handleBlockErrorEvent', async function () { + const errorResult = { + success: false, + payload: { + error: 'Failed to get passphrase state', + }, + }; + mockHardwareWebSdk.getPassphraseState.mockResolvedValue(errorResult); + bridge.sdk = mockHardwareWebSdk as any; + const handleBlockErrorEventSpy = jest + .spyOn(bridge, 'handleBlockErrorEvent') + .mockImplementation(); + + const result = await bridge.getPassphraseState(); + + expect(result).toBe(errorResult); + expect(handleBlockErrorEventSpy).toHaveBeenCalledWith(errorResult); + }); + + it('should return error when SDK is not initialized', async function () { + bridge.sdk = undefined; + + const result = await bridge.getPassphraseState(); + + expect(result).toStrictEqual({ + success: false, + payload: { + error: 'SDK not initialized', + code: 800, + }, + }); + }); + }); + + describe('ethereumSignTransaction', function () { + it('should call evmSignTransaction', async function () { + const expectedResult = { + success: true, + payload: { signature: '0xsignature' }, + }; + mockHardwareWebSdk.evmSignTransaction.mockResolvedValue(expectedResult); + bridge.sdk = mockHardwareWebSdk as any; + + const params = { + path: "m/44'/60'/0'/0/0", + transaction: { + to: '0x123', + value: '0x0', + gasLimit: '0x5208', + gasPrice: '0x1', + nonce: '0x0', + data: '0x', + chainId: 1, + }, + }; + const result = await bridge.ethereumSignTransaction(params); + + expect(mockHardwareWebSdk.evmSignTransaction).toHaveBeenCalledTimes(1); + expect(mockHardwareWebSdk.evmSignTransaction).toHaveBeenCalledWith( + '', + '', + { + ...params, + skipPassphraseCheck: true, + }, + ); + expect(result).toBe(expectedResult); + }); + + it('should handle transaction signing error response and call handleBlockErrorEvent', async function () { + const errorResult = { + success: false, + payload: { + error: 'Transaction signing failed', + code: 500, + }, + }; + mockHardwareWebSdk.evmSignTransaction.mockResolvedValue(errorResult); + bridge.sdk = mockHardwareWebSdk as any; + const handleBlockErrorEventSpy = jest + .spyOn(bridge, 'handleBlockErrorEvent') + .mockImplementation(); + + const params = { + path: "m/44'/60'/0'/0/0", + transaction: { + to: '0x123', + value: '0x0', + gasLimit: '0x5208', + gasPrice: '0x1', + nonce: '0x0', + data: '0x', + chainId: 1, + }, + }; + const result = await bridge.ethereumSignTransaction(params); + + expect(result).toBe(errorResult); + expect(handleBlockErrorEventSpy).toHaveBeenCalledWith(errorResult); + }); + + it('should return error when SDK is not initialized', async function () { + bridge.sdk = undefined; + + const params = { + path: "m/44'/60'/0'/0/0", + transaction: { + to: '0x123', + value: '0x0', + gasLimit: '0x5208', + gasPrice: '0x1', + nonce: '0x0', + data: '0x', + chainId: 1, + }, + }; + const result = await bridge.ethereumSignTransaction(params); + + expect(result).toStrictEqual({ + success: false, + payload: { + error: 'SDK not initialized', + code: 800, + }, + }); + }); + }); + + describe('ethereumSignMessage', function () { + it('should call evmSignMessage', async function () { + const expectedResult = { + success: true, + payload: { signature: '0xsignature' }, + }; + mockHardwareWebSdk.evmSignMessage.mockResolvedValue(expectedResult); + bridge.sdk = mockHardwareWebSdk as any; + + const params = { + path: "m/44'/60'/0'/0/0", + messageHex: '48656c6c6f20576f726c64', + }; + const result = await bridge.ethereumSignMessage(params); + + expect(mockHardwareWebSdk.evmSignMessage).toHaveBeenCalledTimes(1); + expect(mockHardwareWebSdk.evmSignMessage).toHaveBeenCalledWith('', '', { + ...params, + skipPassphraseCheck: true, + }); + expect(result).toBe(expectedResult); + }); + + it('should handle message signing error response and call handleBlockErrorEvent', async function () { + const errorResult = { + success: false, + payload: { + error: 'Message signing failed', + code: 600, + }, + }; + mockHardwareWebSdk.evmSignMessage.mockResolvedValue(errorResult); + bridge.sdk = mockHardwareWebSdk as any; + const handleBlockErrorEventSpy = jest + .spyOn(bridge, 'handleBlockErrorEvent') + .mockImplementation(); + + const params = { + path: "m/44'/60'/0'/0/0", + messageHex: '48656c6c6f20576f726c64', + }; + const result = await bridge.ethereumSignMessage(params); + + expect(result).toBe(errorResult); + expect(handleBlockErrorEventSpy).toHaveBeenCalledWith(errorResult); + }); + + it('should return error when SDK is not initialized', async function () { + bridge.sdk = undefined; + + const params = { + path: "m/44'/60'/0'/0/0", + messageHex: '48656c6c6f20576f726c64', + }; + const result = await bridge.ethereumSignMessage(params); + + expect(result).toStrictEqual({ + success: false, + payload: { + error: 'SDK not initialized', + code: 800, + }, + }); + }); + }); + + describe('ethereumSignTypedData', function () { + it('should call evmSignTypedData', async function () { + const expectedResult = { + success: true, + payload: { signature: '0xsignature' }, + }; + mockHardwareWebSdk.evmSignTypedData.mockResolvedValue(expectedResult); + bridge.sdk = mockHardwareWebSdk as any; + + const params = { + path: "m/44'/60'/0'/0/0", + data: { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + }, + primaryType: 'EIP712Domain', + domain: { name: 'Test' }, + message: {}, + }, + metamaskV4Compat: true, + }; + const result = await bridge.ethereumSignTypedData(params); + + expect(mockHardwareWebSdk.evmSignTypedData).toHaveBeenCalledTimes(1); + expect(mockHardwareWebSdk.evmSignTypedData).toHaveBeenCalledWith('', '', { + ...params, + skipPassphraseCheck: true, + }); + expect(result).toBe(expectedResult); + }); + + it('should handle typed data signing error response and call handleBlockErrorEvent', async function () { + const errorResult = { + success: false, + payload: { + error: 'Typed data signing failed', + code: 700, + }, + }; + mockHardwareWebSdk.evmSignTypedData.mockResolvedValue(errorResult); + bridge.sdk = mockHardwareWebSdk as any; + const handleBlockErrorEventSpy = jest + .spyOn(bridge, 'handleBlockErrorEvent') + .mockImplementation(); + + const params = { + path: "m/44'/60'/0'/0/0", + data: { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + }, + primaryType: 'EIP712Domain', + domain: { name: 'Test' }, + message: {}, + }, + metamaskV4Compat: true, + }; + const result = await bridge.ethereumSignTypedData(params); + + expect(result).toBe(errorResult); + expect(handleBlockErrorEventSpy).toHaveBeenCalledWith(errorResult); + }); + + it('should return error when SDK is not initialized', async function () { + bridge.sdk = undefined; + + const params = { + path: "m/44'/60'/0'/0/0", + data: { + types: { + EIP712Domain: [{ name: 'name', type: 'string' }], + }, + primaryType: 'EIP712Domain', + domain: { name: 'Test' }, + message: {}, + }, + metamaskV4Compat: true, + }; + const result = await bridge.ethereumSignTypedData(params); + + expect(result).toStrictEqual({ + success: false, + payload: { + error: 'SDK not initialized', + code: 800, + }, + }); + }); + }); + + describe('event handling', function () { + it('should add and remove event listeners', function () { + const callback = jest.fn(); + + bridge.on('test-event', callback); + expect(bridge.eventListeners.has('test-event')).toBe(true); + expect(bridge.eventListeners.get('test-event')).toBe(callback); + + bridge.off('test-event'); + expect(bridge.eventListeners.has('test-event')).toBe(false); + }); + + it('should handle block error event with matching error codes', function () { + const callback = jest.fn(); + bridge.on(ONEKEY_HARDWARE_UI_EVENT, callback); + + const payload = { + success: false as const, + payload: { + error: 'Device not found', + code: HardwareErrorCode.WebDeviceNotFoundOrNeedsPermission, + }, + }; + + bridge.handleBlockErrorEvent(payload); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(payload.payload); + }); + + it('should not handle block error event with non-matching error codes', function () { + const callback = jest.fn(); + bridge.on(ONEKEY_HARDWARE_UI_EVENT, callback); + + const payload = { + success: false as const, + payload: { + error: 'Some other error', + code: 999, + }, + }; + + bridge.handleBlockErrorEvent(payload); + expect(callback).not.toHaveBeenCalled(); + }); + }); + + describe('model management', function () { + it('should return model', function () { + bridge.model = 'OneKey Pro'; + expect(bridge.getModel()).toBe('OneKey Pro'); + }); + + it('should return undefined when model is not set', function () { + expect(bridge.getModel()).toBeUndefined(); + }); + }); +}); diff --git a/packages/keyring-eth-onekey/src/onekey-web-bridge.ts b/packages/keyring-eth-onekey/src/onekey-web-bridge.ts index 25ef4de4..d5d3b37a 100644 --- a/packages/keyring-eth-onekey/src/onekey-web-bridge.ts +++ b/packages/keyring-eth-onekey/src/onekey-web-bridge.ts @@ -30,10 +30,6 @@ export class OneKeyWebBridge implements OneKeyBridge { eventListeners: Map void> = new Map(); - constructor() { - console.log('OneKeyWebBridge constructor'); - } - model?: string | undefined; on(_event: string, callback: (event: any) => void): void { diff --git a/yarn.lock b/yarn.lock index 9eddd162..fcd3d068 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1742,6 +1742,45 @@ __metadata: languageName: unknown linkType: soft +"@metamask/eth-onekey-keyring@workspace:packages/keyring-eth-onekey": + version: 0.0.0-use.local + resolution: "@metamask/eth-onekey-keyring@workspace:packages/keyring-eth-onekey" + dependencies: + "@ethereumjs/common": "npm:^4.4.0" + "@ethereumjs/tx": "npm:^5.4.0" + "@ethereumjs/util": "npm:^9.1.0" + "@lavamoat/allow-scripts": "npm:^3.2.1" + "@lavamoat/preinstall-always-fail": "npm:^2.1.0" + "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/eth-sig-util": "npm:^8.2.0" + "@noble/hashes": "npm:^1.7.0" + "@onekeyfe/hd-core": "npm:1.1.6-patch.4" + "@onekeyfe/hd-shared": "npm:1.1.6-patch.4" + "@onekeyfe/hd-transport": "npm:1.1.6-patch.4" + "@onekeyfe/hd-web-sdk": "npm:1.1.6-patch.4" + "@ts-bridge/cli": "npm:^0.6.3" + "@types/bytebuffer": "npm:^5.0.49" + "@types/ethereumjs-tx": "npm:^1.0.1" + "@types/hdkey": "npm:^2.0.1" + "@types/jest": "npm:^29.5.12" + "@types/node": "npm:^20.12.12" + "@types/sinon": "npm:^17.0.3" + "@types/w3c-web-usb": "npm:^1.0.6" + deepmerge: "npm:^4.2.2" + depcheck: "npm:^1.4.7" + ethereumjs-tx: "npm:^1.3.7" + hdkey: "npm:^2.1.0" + jest: "npm:^29.5.0" + jest-environment-jsdom: "npm:^29.7.0" + jest-it-up: "npm:^3.1.0" + sinon: "npm:^19.0.2" + ts-jest: "npm:^29.0.5" + ts-node: "npm:^10.9.2" + typedoc: "npm:^0.25.13" + typescript: "npm:~5.6.3" + languageName: unknown + linkType: soft + "@metamask/eth-qr-keyring@workspace:packages/keyring-eth-qr": version: 0.0.0-use.local resolution: "@metamask/eth-qr-keyring@workspace:packages/keyring-eth-qr" @@ -2693,13 +2732,13 @@ __metadata: languageName: node linkType: hard -"@onekeyfe/hd-core@npm:1.1.6-patch.1": - version: 1.1.6-patch.1 - resolution: "@onekeyfe/hd-core@npm:1.1.6-patch.1" +"@onekeyfe/hd-core@npm:1.1.6-patch.4": + version: 1.1.6-patch.4 + resolution: "@onekeyfe/hd-core@npm:1.1.6-patch.4" dependencies: - "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" - "@onekeyfe/hd-transport": "npm:1.1.6-patch.1" - axios: "npm:^0.27.2" + "@onekeyfe/hd-shared": "npm:1.1.6-patch.4" + "@onekeyfe/hd-transport": "npm:1.1.6-patch.4" + axios: "npm:^0.30.1" bignumber.js: "npm:^9.0.2" bytebuffer: "npm:^5.0.1" jszip: "npm:^3.10.1" @@ -2707,61 +2746,60 @@ __metadata: semver: "npm:^7.3.7" peerDependencies: "@noble/hashes": ^1.1.3 - ripple-keypairs: ^2.0.0 - checksum: 10/37f80410ed626bbeb11eb50fddcd3f94188cde1a2f920024f077218751027e7c7251bafe0d05bc8fe5cf8861186d09154ddf48237eae7477111975a8151b8842 + checksum: 10/ce1f551deb0c4ac87ced4b994d3e2b7da3c2bd7be7a919f40fa9e435085d1606757830047fa5738c01d2a7034c814cc0c466e53bcfe5c2107ad0bf538e12f89a languageName: node linkType: hard -"@onekeyfe/hd-shared@npm:1.1.6-patch.1": - version: 1.1.6-patch.1 - resolution: "@onekeyfe/hd-shared@npm:1.1.6-patch.1" - checksum: 10/4bf4ad95971f8ebbd385798f949d80d26aba7e7bf2c0bb9f4c59b327a4b047dc3ec2d256114a042186ea6457292578070afb9423d8c332ac9554cacbbef737a6 +"@onekeyfe/hd-shared@npm:1.1.6-patch.4": + version: 1.1.6-patch.4 + resolution: "@onekeyfe/hd-shared@npm:1.1.6-patch.4" + checksum: 10/458aa1305ce98ed1229cb04f0372b69a5811cae2b19e768864ba5161a5e832b9da7023bd5ae9050a9fdbe7416fffab016c160bee3fba8e05b9f175a362a0a072 languageName: node linkType: hard -"@onekeyfe/hd-transport-http@npm:1.1.6-patch.1": - version: 1.1.6-patch.1 - resolution: "@onekeyfe/hd-transport-http@npm:1.1.6-patch.1" +"@onekeyfe/hd-transport-http@npm:1.1.6-patch.4": + version: 1.1.6-patch.4 + resolution: "@onekeyfe/hd-transport-http@npm:1.1.6-patch.4" dependencies: - "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" - "@onekeyfe/hd-transport": "npm:1.1.6-patch.1" - axios: "npm:^0.27.2" + "@onekeyfe/hd-shared": "npm:1.1.6-patch.4" + "@onekeyfe/hd-transport": "npm:1.1.6-patch.4" + axios: "npm:^0.30.1" secure-json-parse: "npm:^4.0.0" - checksum: 10/a85a4f072fa7a3301c0375b65cfda4019d3b40bd1c27a0d4be50ca6a94880e78bb0c7ce1d00f3c5c33f7e8b694ba2c53046fee46c1969338410ded2355ee0d8f + checksum: 10/b29fb4cbd2138dd40ff9c38c9928e5a87ca35b2648c2252afbdf61a681f12d06e846d2d6eb743dc5c7b42945e3c0445b35eeeacddf42bde448414a298d0a1672 languageName: node linkType: hard -"@onekeyfe/hd-transport-web-device@npm:1.1.6-patch.1": - version: 1.1.6-patch.1 - resolution: "@onekeyfe/hd-transport-web-device@npm:1.1.6-patch.1" +"@onekeyfe/hd-transport-web-device@npm:1.1.6-patch.4": + version: 1.1.6-patch.4 + resolution: "@onekeyfe/hd-transport-web-device@npm:1.1.6-patch.4" dependencies: - "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" - "@onekeyfe/hd-transport": "npm:1.1.6-patch.1" - checksum: 10/83e3bcebe243f4b3bb49d2cf8a3fb1ce76e81ac6d6e4b98cc8ee0c81a90f633bf385185858f8bd886267f66607ccc20a450c3fa9d31e342ec1beef9b983e464b + "@onekeyfe/hd-shared": "npm:1.1.6-patch.4" + "@onekeyfe/hd-transport": "npm:1.1.6-patch.4" + checksum: 10/34848a6f0716cd8f8d89e6b0eb11ecb739bda52e30fa0d2bacfa92480271a18ca28559f0d88eeff74d27efc8c7c4de05000c5654fa22247b2f9a8ba5dca43f3f languageName: node linkType: hard -"@onekeyfe/hd-transport@npm:1.1.6-patch.1": - version: 1.1.6-patch.1 - resolution: "@onekeyfe/hd-transport@npm:1.1.6-patch.1" +"@onekeyfe/hd-transport@npm:1.1.6-patch.4": + version: 1.1.6-patch.4 + resolution: "@onekeyfe/hd-transport@npm:1.1.6-patch.4" dependencies: bytebuffer: "npm:^5.0.1" long: "npm:^4.0.0" protobufjs: "npm:^6.11.2" - checksum: 10/fa9c79ac477b42aaabd790ee87bde04474bfed5e8cd095df580e937440e0dddf3516554a9c2dbb4c17b36009eadadf27727ee0449d2d877392ded34d124e80c6 + checksum: 10/285e8a1abf2663bf3914b14f59a7273116432704688115b5de70218a08e8bc06b9579558bfcbf966bd225c7960babe764d32a21dd4bc8b6d0fbb03cfd7532fa1 languageName: node linkType: hard -"@onekeyfe/hd-web-sdk@npm:1.1.6-patch.1": - version: 1.1.6-patch.1 - resolution: "@onekeyfe/hd-web-sdk@npm:1.1.6-patch.1" +"@onekeyfe/hd-web-sdk@npm:1.1.6-patch.4": + version: 1.1.6-patch.4 + resolution: "@onekeyfe/hd-web-sdk@npm:1.1.6-patch.4" dependencies: "@onekeyfe/cross-inpage-provider-core": "npm:^0.0.17" - "@onekeyfe/hd-core": "npm:1.1.6-patch.1" - "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" - "@onekeyfe/hd-transport-http": "npm:1.1.6-patch.1" - "@onekeyfe/hd-transport-web-device": "npm:1.1.6-patch.1" - checksum: 10/a9f85147f380ab724c4a465454e8002616343af1957d80d2793c6f5a3c1d73f4ac04c4dcf318307088922b0dc4f6fb7c29ecf5ac5175262e2be7afaa298fec71 + "@onekeyfe/hd-core": "npm:1.1.6-patch.4" + "@onekeyfe/hd-shared": "npm:1.1.6-patch.4" + "@onekeyfe/hd-transport-http": "npm:1.1.6-patch.4" + "@onekeyfe/hd-transport-web-device": "npm:1.1.6-patch.4" + checksum: 10/b8ab1e72b789dc54f7e529fd98bbed8c24c135779d04e451dea21769ce0ad28b36fe055b13923e9bae14f537cac668b32e2e726d244e379035cbf77173d2a374 languageName: node linkType: hard @@ -5001,13 +5039,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^0.27.2": - version: 0.27.2 - resolution: "axios@npm:0.27.2" +"axios@npm:^0.30.1": + version: 0.30.1 + resolution: "axios@npm:0.30.1" dependencies: - follow-redirects: "npm:^1.14.9" - form-data: "npm:^4.0.0" - checksum: 10/2efaf18dd0805f7bc772882bc86f004abd92d51007b54c5081f74db0d08ce3593e2c010261896d25a14318eeaa2e966fd825e34f810e8a3339dc64b9d177cf70 + follow-redirects: "npm:^1.15.4" + form-data: "npm:^4.0.4" + proxy-from-env: "npm:^1.1.0" + checksum: 10/ec5fcbb1cd7827e62028772f421ba558316ae0c2e1bef16823c79ce8f6aa7a65c60671c8b3f505c930abd3f7ca4c55604e86f21925887b762b71a934f0df58a1 languageName: node linkType: hard @@ -5568,6 +5607,16 @@ __metadata: languageName: node linkType: hard +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10/00482c1f6aa7cfb30fb1dbeb13873edf81cfac7c29ed67a5957d60635a56b2a4a480f1016ddbdb3395cc37900d46037fb965043a51c5c789ffeab4fc535d18b5 + languageName: node + linkType: hard + "call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.7": version: 1.0.7 resolution: "call-bind@npm:1.0.7" @@ -6325,6 +6374,17 @@ __metadata: languageName: node linkType: hard +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10/5add88a3d68d42d6e6130a0cac450b7c2edbe73364bbd2fc334564418569bea97c6943a8fcd70e27130bf32afc236f30982fc4905039b703f23e9e0433c29934 + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -6467,6 +6527,13 @@ __metadata: languageName: node linkType: hard +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 + languageName: node + linkType: hard + "es-errors@npm:^1.3.0": version: 1.3.0 resolution: "es-errors@npm:1.3.0" @@ -6474,6 +6541,27 @@ __metadata: languageName: node linkType: hard +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10/54fe77de288451dae51c37bfbfe3ec86732dc3778f98f3eb3bdb4bf48063b2c0b8f9c93542656986149d08aa5be3204286e2276053d19582b76753f1a2728867 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/86814bf8afbcd8966653f731415888019d4bc4aca6b6c354132a7a75bb87566751e320369654a101d23a91c87a85c79b178bcf40332839bd347aff437c4fb65f + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.1.2": version: 3.1.2 resolution: "escalade@npm:3.1.2" @@ -6854,50 +6942,6 @@ __metadata: languageName: node linkType: hard -"eth-onekey-bridge-keyring@workspace:packages/keyring-eth-onekey": - version: 0.0.0-use.local - resolution: "eth-onekey-bridge-keyring@workspace:packages/keyring-eth-onekey" - dependencies: - "@ethereumjs/common": "npm:^4.4.0" - "@ethereumjs/tx": "npm:^5.4.0" - "@ethereumjs/util": "npm:^9.1.0" - "@lavamoat/allow-scripts": "npm:^3.2.1" - "@lavamoat/preinstall-always-fail": "npm:^2.1.0" - "@metamask/auto-changelog": "npm:^3.4.4" - "@metamask/eth-sig-util": "npm:^8.2.0" - "@metamask/keyring-utils": "workspace:^" - "@metamask/utils": "npm:^11.1.0" - "@noble/hashes": "npm:^1.7.0" - "@onekeyfe/hd-core": "npm:1.1.6-patch.1" - "@onekeyfe/hd-shared": "npm:1.1.6-patch.1" - "@onekeyfe/hd-transport": "npm:1.1.6-patch.1" - "@onekeyfe/hd-web-sdk": "npm:1.1.6-patch.1" - "@ts-bridge/cli": "npm:^0.6.3" - "@types/bytebuffer": "npm:^5.0.49" - "@types/ethereumjs-tx": "npm:^1.0.1" - "@types/hdkey": "npm:^2.0.1" - "@types/jest": "npm:^29.5.12" - "@types/node": "npm:^20.12.12" - "@types/sinon": "npm:^17.0.3" - "@types/w3c-web-usb": "npm:^1.0.6" - bytebuffer: "npm:^5.0.1" - deepmerge: "npm:^4.2.2" - depcheck: "npm:^1.4.7" - ethereumjs-tx: "npm:^1.3.7" - hdkey: "npm:^2.1.0" - jest: "npm:^29.5.0" - jest-environment-jsdom: "npm:^29.7.0" - jest-it-up: "npm:^3.1.0" - ripple-address-codec: "npm:^5.0.0" - sinon: "npm:^19.0.2" - ts-jest: "npm:^29.0.5" - ts-node: "npm:^10.9.2" - tslib: "npm:^2.6.2" - typedoc: "npm:^0.25.13" - typescript: "npm:~5.6.3" - languageName: unknown - linkType: soft - "eth-sig-util@npm:^3.0.1": version: 3.0.1 resolution: "eth-sig-util@npm:3.0.1" @@ -7394,7 +7438,17 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.6": +"follow-redirects@npm:^1.15.4": + version: 1.15.11 + resolution: "follow-redirects@npm:1.15.11" + peerDependenciesMeta: + debug: + optional: true + checksum: 10/07372fd74b98c78cf4d417d68d41fdaa0be4dcacafffb9e67b1e3cf090bc4771515e65020651528faab238f10f9b9c0d9707d6c1574a6c0387c5de1042cde9ba + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.6": version: 1.15.9 resolution: "follow-redirects@npm:1.15.9" peerDependenciesMeta: @@ -7434,6 +7488,19 @@ __metadata: languageName: node linkType: hard +"form-data@npm:^4.0.4": + version: 4.0.4 + resolution: "form-data@npm:4.0.4" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" + mime-types: "npm:^2.1.12" + checksum: 10/a4b62e21932f48702bc468cc26fb276d186e6b07b557e3dd7cc455872bdbb82db7db066844a64ad3cf40eaf3a753c830538183570462d3649fdfd705601cbcfb + languageName: node + linkType: hard + "fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -7535,6 +7602,24 @@ __metadata: languageName: node linkType: hard +"get-intrinsic@npm:^1.2.6": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10/6e9dd920ff054147b6f44cb98104330e87caafae051b6d37b13384a45ba15e71af33c3baeac7cb630a0aaa23142718dcf25b45cfdd86c184c5dcb4e56d953a10 + languageName: node + linkType: hard + "get-npm-tarball-url@npm:^2.0.3": version: 2.1.0 resolution: "get-npm-tarball-url@npm:2.1.0" @@ -7549,6 +7634,16 @@ __metadata: languageName: node linkType: hard +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b + languageName: node + linkType: hard + "get-stdin@npm:^9.0.0": version: 9.0.0 resolution: "get-stdin@npm:9.0.0" @@ -7737,6 +7832,13 @@ __metadata: languageName: node linkType: hard +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 + languageName: node + linkType: hard + "graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" @@ -7788,6 +7890,13 @@ __metadata: languageName: node linkType: hard +"has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa + languageName: node + linkType: hard + "has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": version: 1.0.2 resolution: "has-tostringtag@npm:1.0.2" @@ -9451,6 +9560,13 @@ __metadata: languageName: node linkType: hard +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd + languageName: node + linkType: hard + "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" From a18c7794d3a2f7118c7f2a986ce42aa4f92c6aaa Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Fri, 22 Aug 2025 16:19:19 +0800 Subject: [PATCH 09/10] chore: fix lint --- README.md | 2 + packages/keyring-eth-onekey/jest.config.js | 6 +- .../src/onekey-keyring.test.ts | 51 ++++------- .../keyring-eth-onekey/src/onekey-keyring.ts | 3 + yarn.lock | 89 ++----------------- 5 files changed, 35 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 0b062de2..83fb8e49 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repository contains the following packages [^fn1]: - [`@metamask/account-api`](packages/account-api) - [`@metamask/eth-hd-keyring`](packages/keyring-eth-hd) - [`@metamask/eth-ledger-bridge-keyring`](packages/keyring-eth-ledger-bridge) +- [`@metamask/eth-onekey-keyring`](packages/keyring-eth-onekey) - [`@metamask/eth-qr-keyring`](packages/keyring-eth-qr) - [`@metamask/eth-simple-keyring`](packages/keyring-eth-simple) - [`@metamask/eth-snap-keyring`](packages/keyring-snap-bridge) @@ -40,6 +41,7 @@ linkStyle default opacity:0.5 keyring_api(["@metamask/keyring-api"]); eth_hd_keyring(["@metamask/eth-hd-keyring"]); eth_ledger_bridge_keyring(["@metamask/eth-ledger-bridge-keyring"]); + eth_onekey_keyring(["@metamask/eth-onekey-keyring"]); eth_qr_keyring(["@metamask/eth-qr-keyring"]); eth_simple_keyring(["@metamask/eth-simple-keyring"]); eth_trezor_keyring(["@metamask/eth-trezor-keyring"]); diff --git a/packages/keyring-eth-onekey/jest.config.js b/packages/keyring-eth-onekey/jest.config.js index 4b5c9805..0b3cadbc 100644 --- a/packages/keyring-eth-onekey/jest.config.js +++ b/packages/keyring-eth-onekey/jest.config.js @@ -23,10 +23,10 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 74.2, + branches: 74.31, functions: 91.25, - lines: 92.78, - statements: 92.83, + lines: 92.83, + statements: 92.88, }, }, }); diff --git a/packages/keyring-eth-onekey/src/onekey-keyring.test.ts b/packages/keyring-eth-onekey/src/onekey-keyring.test.ts index 79ec997d..05d2d389 100644 --- a/packages/keyring-eth-onekey/src/onekey-keyring.test.ts +++ b/packages/keyring-eth-onekey/src/onekey-keyring.test.ts @@ -20,25 +20,25 @@ import { OneKeyWebBridge } from './onekey-web-bridge'; const CONNECT_SRC = 'https://jssdk.onekey.so/1.1.5/'; const fakeAccounts = [ - '0xF30952A1c534CDE7bC471380065726fa8686dfB3', - '0x44fe3Cf56CaF651C4bD34Ae6dbcffa34e9e3b84B', - '0x8Ee3374Fa705C1F939715871faf91d4348D5b906', - '0xEF69e24dE9CdEe93C4736FE29791E45d5D4CFd6A', - '0xC668a5116A045e9162902795021907Cb15aa2620', - '0xbF519F7a6D8E72266825D770C60dbac55a3baeb9', - '0x0258632Fe2F91011e06375eB0E6f8673C0463204', - '0x4fC1700C0C61980aef0Fb9bDBA67D8a25B5d4335', - '0xeEC5D417152aE295c047FB0B0eBd7c7090dDedEb', - '0xd3f978B9eEEdB68A38CF252B3779afbeb3623fDf', - '0xd819fE2beD53f44825F66873a159B687736d3092', - '0xE761dA62f053ad9eE221d325657535991Ab659bD', - '0xd4F1686961642340a80334b5171d85Bbd390c691', - '0x6772C4B1E841b295960Bb4662dceD9bb71726357', - '0x41bEAD6585eCA6c79B553Ca136f0DFA78A006899', + '0x73d0385F4d8E00C5e6504C6030F47BF6212736A8', + '0xFA01a39f8Abaeb660c3137f14A310d0b414b2A15', + '0x574BbB36871bA6b78E27f4B4dCFb76eA0091880B', + '0xba98D6a5ac827632E3457De7512d211e4ff7e8bD', + '0x1f815D67006163E502b8eD4947C91ad0A62De24e', + '0xf69619a3dCAA63757A6BA0AF3628f5F6C42c50d2', + '0xA8664Df3D5E74BE57c19fC7005BBcd0F5328041e', + '0xf2252f414e727d652d5a488fE4BFf7e64478737F', + '0x5708Ae081b48ad7bA8c50ca3D4fa0238d544D6FA', + '0x12eF7dfb86f6D5E3e0521b72472ca02D2a3814F4', + '0x9115Fa64b8B9864D6545Fc00d62B6A9Cbb876be7', + '0x8B6cF2eA1A54E054EFC35E4244Ac507c479bb5F6', + '0x6C480ba4409dd5FF29Cbd3ED67152B791750a708', + '0x5f2E5ddEd3DBD431deCc406Ae999F277B625Ba25', + '0x8a143C4CCed2ce826DE598Dbbf7C706cD6DB0Ccd', ] as const; const fakeXPubKey = - 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt'; + 'xpub6CNFa58kEQJu2hwMVoofpDEKVVSg6gfwqBqE2zHAianaUnQkrJzJJ42iLDp7Dmg2aP88qCKoFZ4jidk3tECdQuF4567NGHDfe7iBRwHxgke'; const fakeHdKey = HDKey.fromExtendedKey(fakeXPubKey); const fakeTx = new EthereumTx({ nonce: '0x00', @@ -1143,12 +1143,7 @@ describe('OneKeyKeyring', function () { const unlockResult = 'unlocked'; const unlockSpy = sinon.stub(keyring, 'unlock').resolves(unlockResult); - // Mock bridge methods to avoid actual hardware calls (33-byte public key) - keyring.hdk.publicKey = Buffer.from(`02${'0'.repeat(64)}`, 'hex'); // 33 bytes - keyring.hdk.chainCode = Buffer.from( - '123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01', - 'hex', - ); // 32 bytes + keyring.hdk = fakeHdKey; const accounts = await keyring.addAccounts(1); expect(accounts).toHaveLength(1); @@ -1164,11 +1159,7 @@ describe('OneKeyKeyring', function () { const unlockResult = 'unlocked'; const unlockSpy = sinon.stub(keyring, 'unlock').resolves(unlockResult); - keyring.hdk.publicKey = Buffer.from(`02${'0'.repeat(64)}`, 'hex'); // 33 bytes - keyring.hdk.chainCode = Buffer.from( - '123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01', - 'hex', - ); // 32 bytes + keyring.hdk = fakeHdKey; const accounts = await keyring.addAccounts(1); expect(accounts).toHaveLength(1); @@ -1191,11 +1182,7 @@ describe('OneKeyKeyring', function () { keyring.hdPath = "m/44'/60'/x'/0/0"; // Ledger Live path (not in ALLOWED_HD_PATHS but we set directly) const unlockSpy = sinon.stub(keyring, 'unlock').resolves('unlocked'); - keyring.hdk.publicKey = Buffer.from(`02${'0'.repeat(64)}`, 'hex'); - keyring.hdk.chainCode = Buffer.from( - '123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01', - 'hex', - ); + keyring.hdk = fakeHdKey; // This should trigger the Ledger Live error paths during addAccounts try { diff --git a/packages/keyring-eth-onekey/src/onekey-keyring.ts b/packages/keyring-eth-onekey/src/onekey-keyring.ts index 56a51446..cb40499a 100644 --- a/packages/keyring-eth-onekey/src/onekey-keyring.ts +++ b/packages/keyring-eth-onekey/src/onekey-keyring.ts @@ -640,6 +640,9 @@ export class OneKeyKeyring extends EventEmitter { if (this.#isLedgerLiveHdPath()) { throw new Error('Ledger Live is not supported'); } + if (this.#isStandardBip44HdPath()) { + return `${pathBase}/0/${index}`; + } return `${pathBase}/${index}`; } diff --git a/yarn.lock b/yarn.lock index fcd3d068..1dad96d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5282,14 +5282,7 @@ __metadata: languageName: node linkType: hard -"bignumber.js@npm:^9.0.0, bignumber.js@npm:^9.0.1, bignumber.js@npm:^9.1.2, bignumber.js@npm:^9.3.0": - version: 9.3.0 - resolution: "bignumber.js@npm:9.3.0" - checksum: 10/60b79efcf7b56b925fca8eebd10d1f4b70aa2bf6eade7f5af0266f0092226dd2abcd9a3ee315ecb39459750d5a630ce3980b707e5d7bea32c97ffd378e8cc159 - languageName: node - linkType: hard - -"bignumber.js@npm:^9.0.2": +"bignumber.js@npm:^9.0.0, bignumber.js@npm:^9.0.1, bignumber.js@npm:^9.0.2, bignumber.js@npm:^9.1.2, bignumber.js@npm:^9.3.0": version: 9.3.1 resolution: "bignumber.js@npm:9.3.1" checksum: 10/1be0372bf0d6d29d0a49b9e6a9cefbd54dad9918232ad21fcd4ec39030260773abf0c76af960c6b3b98d3115a3a71e61c6a111812d1395040a039cfa178e0245 @@ -6518,16 +6511,7 @@ __metadata: languageName: node linkType: hard -"es-define-property@npm:^1.0.0": - version: 1.0.0 - resolution: "es-define-property@npm:1.0.0" - dependencies: - get-intrinsic: "npm:^1.2.4" - checksum: 10/f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 - languageName: node - linkType: hard - -"es-define-property@npm:^1.0.1": +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": version: 1.0.1 resolution: "es-define-property@npm:1.0.1" checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 @@ -7438,7 +7422,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.15.4": +"follow-redirects@npm:^1.15.4, follow-redirects@npm:^1.15.6": version: 1.15.11 resolution: "follow-redirects@npm:1.15.11" peerDependenciesMeta: @@ -7448,16 +7432,6 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.15.6": - version: 1.15.9 - resolution: "follow-redirects@npm:1.15.9" - peerDependenciesMeta: - debug: - optional: true - checksum: 10/e3ab42d1097e90d28b913903841e6779eb969b62a64706a3eb983e894a5db000fbd89296f45f08885a0e54cd558ef62e81be1165da9be25a6c44920da10f424c - languageName: node - linkType: hard - "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -7477,18 +7451,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.8" - mime-types: "npm:^2.1.12" - checksum: 10/7264aa760a8cf09482816d8300f1b6e2423de1b02bba612a136857413fdc96d7178298ced106817655facc6b89036c6e12ae31c9eb5bdc16aabf502ae8a5d805 - languageName: node - linkType: hard - -"form-data@npm:^4.0.4": +"form-data@npm:^4.0.0, form-data@npm:^4.0.4": version: 4.0.4 resolution: "form-data@npm:4.0.4" dependencies: @@ -7589,20 +7552,7 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4": - version: 1.2.4 - resolution: "get-intrinsic@npm:1.2.4" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - has-proto: "npm:^1.0.1" - has-symbols: "npm:^1.0.3" - hasown: "npm:^2.0.0" - checksum: 10/85bbf4b234c3940edf8a41f4ecbd4e25ce78e5e6ad4e24ca2f77037d983b9ef943fd72f00f3ee97a49ec622a506b67db49c36246150377efcda1c9eb03e5f06d - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.2.6": +"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.6": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" dependencies: @@ -7823,16 +7773,7 @@ __metadata: languageName: node linkType: hard -"gopd@npm:^1.0.1": - version: 1.0.1 - resolution: "gopd@npm:1.0.1" - dependencies: - get-intrinsic: "npm:^1.1.3" - checksum: 10/5fbc7ad57b368ae4cd2f41214bd947b045c1a4be2f194a7be1778d71f8af9dbf4004221f3b6f23e30820eb0d052b4f819fe6ebe8221e2a3c6f0ee4ef173421ca - languageName: node - linkType: hard - -"gopd@npm:^1.2.0": +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": version: 1.2.0 resolution: "gopd@npm:1.2.0" checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 @@ -7876,21 +7817,7 @@ __metadata: languageName: node linkType: hard -"has-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "has-proto@npm:1.0.1" - checksum: 10/eab2ab0ed1eae6d058b9bbc4c1d99d2751b29717be80d02fd03ead8b62675488de0c7359bc1fdd4b87ef6fd11e796a9631ad4d7452d9324fdada70158c2e5be7 - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.3": - version: 1.0.3 - resolution: "has-symbols@npm:1.0.3" - checksum: 10/464f97a8202a7690dadd026e6d73b1ceeddd60fe6acfd06151106f050303eaa75855aaa94969df8015c11ff7c505f196114d22f7386b4a471038da5874cf5e9b - languageName: node - linkType: hard - -"has-symbols@npm:^1.1.0": +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": version: 1.1.0 resolution: "has-symbols@npm:1.1.0" checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa @@ -7934,7 +7861,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0, hasown@npm:^2.0.2": +"hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" dependencies: From a6219080ff4ab7f4cbd62e2c95c8f407ce10b92a Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Tue, 26 Aug 2025 15:42:36 +0800 Subject: [PATCH 10/10] fix: index error --- packages/keyring-eth-onekey/src/onekey-keyring.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/keyring-eth-onekey/src/onekey-keyring.ts b/packages/keyring-eth-onekey/src/onekey-keyring.ts index cb40499a..e8ceafdd 100644 --- a/packages/keyring-eth-onekey/src/onekey-keyring.ts +++ b/packages/keyring-eth-onekey/src/onekey-keyring.ts @@ -292,7 +292,7 @@ export class OneKeyKeyring extends EventEmitter { } if (!this.accountDetails[address]) { this.accountDetails[address] = { - index: from + i, + index: i, hdPath, passphraseState: this.passphraseState, }; @@ -605,7 +605,7 @@ export class OneKeyKeyring extends EventEmitter { throw new Error('Unknown error'); } accounts.push({ - index: from + i, + index: i, address, balance: null, });