Skip to Content
Hardware

evmSignTypedData

Ethereum: Sign Typed Data

Asks device to sign an EIP-712  typed data message using the private key derived from the specified BIP32 path.

The device asks the user to confirm the request. The displayed detail depends on whether parsed signing or hash signing is used.

  • Supports EIP-712 (v3 and v4)
  • Use evmSignTypedData for signing typed structured data
  • Not for EIP-191 typed data signing
  • Use evmSignMessage to sign EIP-191(personal_sign)
const result = await HardwareSDK.evmSignTypedData(connectId, deviceId, params);

Params

Optional common params

  • path - required string | Array<number> SDK minimum length is 3. read more
  • data - required Object type of EthereumSignTypedDataMessage<EthereumSignTypedDataTypes>. A JSON Schema definition can be found in the EIP-712 spec.
  • metamaskV4Compat - required boolean set to true for compatibility with MetaMask’s signTypedData_v4 .
  • chainId - optional number The ChainId in ETH is a unique identifier for a specific Ethereum network, used to distinguish different versions of the blockchain. Reference .

Parsed signing

Pass the complete EIP-712 object, including types.EIP712Domain, and set metamaskV4Compat to match the original request. In the parsed flow, the device requests type definitions and field values from the SDK. Available detail depends on the device and firmware; see the Clear Signing guide.

Hash signing fallback

When the SDK selects hash signing, the device receives precomputed hashes instead of the structured message. This is blind signing, not structured Clear Signing. Hashes must be derived from the exact same data with the same encoding mode; the SDK does not compute or cross-check supplied hashes against data.

  • domainHash: optional for parsed signing; required for fallback. A hex-encoded 32-byte EIP-712 domain hash.
  • messageHash: optional in the public type, but required by the current automatic fallback branches. A hex-encoded 32-byte EIP-712 message struct hash, not the final 0x1901 signing digest. A domain-only request (primaryType: 'EIP712Domain') may omit it during parsed signing; the current fallback validation still requires both hashes.

The current SDK selects fallback in these cases:

ConditionScope
No parsed EIP-712 capability, or a format limit is exceededClassic, Mini, Classic1s, Classic Pure; Classic1s/Pure are not unconditionally blind-signing devices
Nested arrays in the supplied dataAll device families
Hex bytes in message.data exceed the applicable limit1,536 bytes for Classic1s/Pure and Pro2/Neo; 1,536 bytes for Touch/Pro on firmware 4.4.0 or later; 1,024 bytes otherwise

The size check above measures message.data, not the total JSON document. The Classic/Mini family also has format limits: 16 fields per struct, 63 characters per type/member name, 24 array-typed fields, access-path depth of 6, 8 custom dependency structs excluding the primary type and domain, and 1,536 bytes per string/bytes value. Arrays of primitive values are limited to 24 elements; struct arrays have that limit in v4 mode. Cyclic type dependencies also trigger fallback.

On Touch/Pro, hash signing requires firmware 4.2.0 or later. Earlier firmware returns CallMethodNeedUpgradeFirmware when this route is selected.

Supplying hashes does not force the hash route when parsed signing is available. If fallback is required and hashes are absent, the call fails. Handle this explicitly in the application’s signing policy; do not silently replace the original request or claim a successful signature proves that all fields were displayed.

Example

This fragment uses ethers v6 to prepare v4-compatible fallback hashes. Reuse an initialized SDK and the selected wallet. Hashes do not bypass device confirmation.

import { TypedDataEncoder } from 'ethers'; const eip712Data = { types: { EIP712Domain: [ { name: 'name', type: 'string', }, { name: 'chainId', type: 'uint256' }, ], Message: [ { name: "wallet", type: "string" }, { name: "number", type: "uint64" } ] }, primaryType: 'Message', domain: { name: 'example.onekey.io', chainId: 1, }, message: { "wallet": "OneKey Wallet", // be careful with JavaScript numbers: MAX_SAFE_INTEGER is quite low "number": `${2n ** 55n}`, }, }; const { EIP712Domain, ...messageTypes } = eip712Data.types; const domainHash = TypedDataEncoder.hashDomain(eip712Data.domain); const messageHash = TypedDataEncoder.from(messageTypes).hash(eip712Data.message); const result = await HardwareSDK.evmSignTypedData(connectId, deviceId, { path: "m/44'/60'/0'/0/0", data: eip712Data, metamaskV4Compat: true, domainHash, messageHash, chainId: eip712Data.domain.chainId, }); if (result.success === false) throw new Error(result.payload.error);

Safe Tx

Use the canonical Safe v1.3.0 SafeTx schema below on supporting firmware. Set domain.verifyingContract to the Safe address, use the real network, and provide all ten message fields. operation is 0 for CALL or 1 for DELEGATECALL. This example uses placeholder addresses and a Safe nonce of zero; replace them with the exact transaction proposed by your application. It omits fallback hashes, so requests requiring hash signing fail instead of automatically supplying those hashes.

const safeTypedData = { types: { EIP712Domain: [ { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }, ], SafeTx: [ { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'data', type: 'bytes' }, { name: 'operation', type: 'uint8' }, { name: 'safeTxGas', type: 'uint256' }, { name: 'baseGas', type: 'uint256' }, { name: 'gasPrice', type: 'uint256' }, { name: 'gasToken', type: 'address' }, { name: 'refundReceiver', type: 'address' }, { name: 'nonce', type: 'uint256' }, ], }, primaryType: 'SafeTx', domain: { chainId: 1, verifyingContract: '0x1111111111111111111111111111111111111111', }, message: { to: '0x2222222222222222222222222222222222222222', value: '0', data: '0x', operation: '0', safeTxGas: '0', baseGas: '0', gasPrice: '0', gasToken: '0x0000000000000000000000000000000000000000', refundReceiver: '0x0000000000000000000000000000000000000000', nonce: '0', }, }; const result = await HardwareSDK.evmSignTypedData(connectId, deviceId, { path: "m/44'/60'/0'/0/0", data: safeTypedData, metamaskV4Compat: true, chainId: safeTypedData.domain.chainId, }); if (result.success === false) throw new Error(result.payload.error);

This returns an owner signature, not a broadcast transaction. Keep the typed data unchanged when verifying and collecting signatures. See Safe Tx integration for the distinction from on-chain Safe calls.

Result

{ success: true, payload: { address: string, signature: string, // hexadecimal string without the "0x" prefix } }

Error

{ success: false, payload: { error: string, // error message code?: string | number // error code } }
Last updated on