EVM Signer
End-to-end EVM signing guide. Covers address confirmation, EIP‑1559/Legacy transactions, personal_sign, and EIP‑712 typed data. Content is aligned with hardware-js-sdk behavior (Promise responses + UI events) and mirrors the Chinese page.
Index
- How it works
- Installation
- Initialization
- Common scenarios
- Interaction & Status
- Examples
- Validation & Troubleshooting
How it works
HardwareSDKtalks to the OneKey device app; APIs returnPromise<{ success, payload }>.- User interaction is driven by
UI_REQUESTevents (PIN, passphrase, confirm address/tx/message/typed data/app open). - The device shows path/account, recipient, amount, chainId, fee summary; long
datais often summarized as a hash. - Host serializes and verifies with
ethers/viem, then broadcasts via RPC.
Installation
npm i @onekeyfe/hd-core @onekeyfe/hd-common-connect-sdkInitialization
- Keep firmware up to date; Web requires HTTPS + user gestures; BLE needs proper permission.
- Before signing, call
evmGetAddress(showOnOneKey: true)once to confirm path/account on device.
import HardwareSDK from '@onekeyfe/hd-common-connect-sdk';
await HardwareSDK.init({ env: 'webusb', fetchConfig: true, debug: false });
const [{ connectId }] = await HardwareSDK.searchDevices();
const deviceId = (await HardwareSDK.getFeatures(connectId)).payload?.device_id;Common scenarios
Scenario 1: Get Address
const res = await HardwareSDK.evmGetAddress(connectId, deviceId, {
path: "m/44'/60'/0'/0/0",
showOnOneKey: true,
chainId: 1,
});
// res.payload.address, publicKey?, chainCode?Parameters
path(string | number[]): required; BIP44 path.showOnOneKey?(boolean): optional; display and confirm on device.chainId?(number): optional; chainId shown on device.
Returns
Promise<{ success, payload: { address, path, publicKey?, chainCode? } }>Scenario 2: Sign Transaction
Supports EIP‑1559 (type: 2) and Legacy.
const { success, payload } = await HardwareSDK.evmSignTransaction(connectId, deviceId, {
path: "m/44'/60'/0'/0/0",
transaction: tx,
chainId: 1,
keepSession: true,
domain: 'ens.example', // optional
});
// payload: { v, r, s }Parameters
path: required.transaction: required.- 1559:
tovaluedatanoncegasLimitmaxFeePerGasmaxPriorityFeePerGaschainIdtype: 2 - Legacy:
tovaluedatanoncegasLimitgasPricechainId
- 1559:
chainId: required.keepSession?: optional; reuse session for multiple calls.domain?: optional; domain label (e.g., ENS) for device display.
Returns
Promise<{ success, payload: { v, r, s } }>Scenario 3: Sign Message (personal_sign)
const res = await HardwareSDK.evmSignMessage(connectId, deviceId, {
path: "m/44'/60'/0'/0/0",
messageHex,
chainId: 1,
});
// res.payload.signatureParameters
path: required.messageHex: required; message to hex (with/without0x).chainId?: optional; shown on device.
Returns
Promise<{ success, payload: { signature } }>Scenario 4: Sign Typed Data (EIP-712)
const res = await HardwareSDK.evmSignTypedData(connectId, deviceId, {
path: "m/44'/60'/0'/0/0",
data: typedData,
chainId: 1,
});
// res.payload.signatureParameters
path: required.data: required; EIP‑712 JSON v4 withdomain/types/primaryType/message.chainId: required.
TypedData shape:
interface TypedData {
domain: {
name?: string;
version?: string;
chainId?: number;
verifyingContract?: string;
salt?: string;
};
types: Record<string, Array<{ name: string; type: string }>>;
primaryType: string;
message: Record<string, unknown>;
}Returns
Promise<{ success, payload: { signature } }>Interaction & Status
- APIs resolve when the operation finishes; user prompts are surfaced via
UI_REQUESTevents. - Common prompts: unlock device, open EVM app, verify address, sign transaction, sign personal message, sign typed data.
- For repeated calls, use
keepSessionto reduce PIN/passphrase prompts; run calls serially per device.
Examples
EIP-1559 transaction
import HardwareSDK, { UI_REQUEST, UI_RESPONSE } from '@onekeyfe/hd-core';
import { serialize, TransactionTypes } from '@ethersproject/transactions';
await HardwareSDK.init({ env: 'webusb', debug: false });
const [{ connectId }] = await HardwareSDK.searchDevices();
const deviceId = (await HardwareSDK.getFeatures(connectId)).payload?.device_id;
HardwareSDK.on(UI_REQUEST.REQUEST_PIN, () => {
// Prompt PIN (prefer on-device); then call uiResponse if needed
});
const accountPath = "m/44'/60'/0'/0/0";
await HardwareSDK.evmGetAddress(connectId, deviceId, { path: accountPath, showOnOneKey: true });
const tx = {
to: '0xd0d6d6c5fe4a677d343cc433536bb717bae167dd',
value: '0x0',
data: '0x',
nonce: '0x0',
gasLimit: '0x5208',
maxFeePerGas: '0x3b9aca00',
maxPriorityFeePerGas: '0x59682f00',
chainId: 1,
};
const { success, payload: sig } = await HardwareSDK.evmSignTransaction(connectId, deviceId, {
path: accountPath,
transaction: tx,
chainId: 1,
keepSession: true,
});
const rawTx = serialize({ ...tx, type: TransactionTypes.eip1559 }, {
r: sig.r,
s: sig.s,
v: Number(sig.v),
});
// Broadcast with your RPC provider, e.g. ethers.js provider.sendTransaction(rawTx)Legacy transaction (gasPrice)
const legacyTx = {
to: '0xRecipient',
value: '0x2386f26fc10000', // 0.01 ETH
data: '0x',
nonce: '0x1',
gasLimit: '0x5208',
gasPrice: '0x3b9aca00', // 1 gwei
chainId: 1,
};
const sig = await HardwareSDK.evmSignTransaction(connectId, deviceId, {
path: accountPath,
transaction: legacyTx,
chainId: 1,
});Message (personal_sign / EIP-191)
const message = 'Hello OneKey';
const messageHex = Buffer.from(message).toString('hex');
const res = await HardwareSDK.evmSignMessage(connectId, deviceId, {
path: accountPath,
messageHex,
chainId: 1,
});
// res.payload.signature -> verify with ethers.verifyMessage(message, signature)Typed data (EIP-712)
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' },
],
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
},
primaryType: 'Mail',
domain: {
name: 'Ether Mail',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
},
message: {
from: { name: 'Cow', wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' },
to: { name: 'Bob', wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' },
contents: 'Hello, Bob!',
},
};
const res = await HardwareSDK.evmSignTypedData(connectId, deviceId, {
path: accountPath,
data: typedData,
chainId: 1,
});
// res.payload.signature -> recover with ethers.verifyTypedData(domain, types, message, signature)Validation & Troubleshooting
- Verify on device: path/account, recipient, amount, chainId, fee summary; ensure they match your UI.
- Gas: 1559 requires
maxFeePerGas + maxPriorityFeePerGas; Legacy usesgasPriceonly. - Message/Typed Data: show clear summaries/hashes in UI to avoid phishing; recover signer with
ethers.verifyMessage/ethers.verifyTypedData. - Common issues:
- Path mismatch: default
m/44'/60'/0'/0/0, increment account/address as needed; confirm once withshowOnOneKey.
- Path mismatch: default
- ChainId mismatch: must match the target network.
- Large
datamay be rejected; simplify if necessary. - Repeated passphrase prompts: combine
keepSessionwith passphrase state and serialize calls per device. - User reject/timeout: surface retry/cancel; do not auto-replay. See chain docs: evmSignTransaction · evmSignMessage · evmSignTypedData.
Last updated on