LayerZeroFault
hardware fallback

Fix: Trezor Error 0x6985 'Action Cancelled' on EIP-712 Typed Signatures

VV

Written by

Fact-Checked on September 11, 2026

Verified Expert

Fix: Trezor Error 0x6985 “Action Cancelled” on EIP-712 Typed Signatures

During high-value decentralized finance interactions—such as signing Uniswap Permit2 approvals, executing CowSwap zero-gas orders, or managing multi-signature policies on Safe—users operating hardware wallets like the Trezor Model T, Trezor Safe 3, or Trezor Safe 5 frequently encounter an abrupt signing failure:

TransportError: Trezor error: Failure_ActionCancelled (code 0x6985)
  Message: Action cancelled by user
  at TrezorConnect.ethereumSignTypedData (trezor-connect.js:412)
  at MetaMaskController.signTypedMessage (background.js:1894)

The confusing aspect of this error is that the user never touched the ‘Cancel’ button on the device touchscreen. Instead, the Trezor screen either flashes for a millisecond before resetting to the lock screen, or the host application immediately throws error 0x6985 before the physical device even displays the domain confirmation prompt.

Placeholder: Trezor Model T Displaying EIP-712 Signing Abort Screen


1. Technical Mechanics of EIP-712 Parsing on Trezor Core

Unlike raw transaction signing—where the hardware device only needs to decode recursive length prefix (RLP) bytes—EIP-712 Typed Data signing requires the hardware security enclave to construct and hash structured data in two separate passes:

$$\text{Sign}(\text{DomainSeparator}, \text{HashStruct}(P))$$

To prevent blind signing exploits, modern Trezor Core firmware (v2.6.0 and later) attempts to parse and visualize the entire tree structure on-device:

  1. Domain Separator Validation: Checks name, version, chainId, and verifyingContract.
  2. Schema Type Decomposition: Validates that all custom structs declared in types are strictly acyclic and conform to Ethereum ABI types (address, uint256, bytes32, etc.).
  3. Recursive Value Serialization: Traverses each field and hashes dynamic members (strings and byte arrays) using Keccak-256.

If any field violates strict type constraints, the hardware parser considers the payload malformed and immediately aborts the session with protocol code Failure_ActionCancelled (0x6985).


2. Primary Root Causes

1. The chainId Type Mismatch (Hex String vs. BigInt / Number)

The EIP-712 specification states that chainId in EIP712Domain is a uint256. However, several decentralized applications (dApps) serialize chainId as a hexadecimal string (e.g., "0x01" or "0x2105" for Base) instead of a numeric value or plain decimal string (1 or 8453).

While software wallets like MetaMask silently coerce hex strings to BigInts, Trezor Core rejects string-encoded uint256 scalars in domain headers, causing an instantaneous 0x6985 abort.

2. Deeply Nested Struct Arrays Exceeding SRAM Buffer

Trezor Model T and Safe 3 hardware chips operate within tight static RAM constraints (approx. 128 KB - 256 KB total SRAM).

When signing batch trades or complex order routing payloads (e.g., UniswapX dutch orders containing arrays of 10+ filled input and output tokens with nested permits), the deserialization tree overflows Trezor’s temporary heap allocation. To prevent memory corruption or stack smashes, the firmware triggers a defensive abort.

3. WebUSB Chunk Desynchronization in Chromium

Under Chrome and Brave (versions 124+), WebUSB communication uses a 64-byte frame delimiter. If an extension initiates multiple concurrent RPC calls (for instance, fetching account balance while requesting an EIP-712 signature), the USB endpoint receives interleaved control packets, causing the Trezor bridge (trezord or WebUSB driver) to drop the signature packet and report Action cancelled.

Placeholder: Architecture Diagram of WebUSB Chunk Desynchronization in Trezor Bridge


3. Diagnostic & Troubleshooting Procedure

Diagnostic Test: Verify Payload with trezorctl

To isolate whether the issue stems from your browser extension or the typed payload itself, test the signature directly using the official trezorctl CLI utility:

# Save your EIP-712 JSON payload to eip712_payload.json
trezorctl ethereum sign-typed-data \
  --address "m/44'/60'/0'/0/0" \
  --chain-id 1 \
  eip712_payload.json

If trezorctl outputs:

Error: Data error: Invalid field type for chainId (expected integer, received string)

The root cause is a malformed JSON schema submitted by the dApp.


4. Remediation & Recovery Protocols

Method A: Sanitize the EIP-712 Payload in Front-End Integration

If you are developing a dApp or integrating Viem / Ethers.js with Trezor Connect, sanitize your domain and message structures before passing them to the hardware provider:

import { type TypedDataDefinition } from 'viem';

export function sanitizeForTrezor<T extends TypedDataDefinition>(typedData: T): T {
  const sanitized = JSON.parse(JSON.stringify(typedData));

  // 1. Ensure domain chainId is an integer
  if (sanitized.domain?.chainId) {
    if (typeof sanitized.domain.chainId === 'string') {
      sanitized.domain.chainId = sanitized.domain.chainId.startsWith('0x')
        ? parseInt(sanitized.domain.chainId, 16)
        : parseInt(sanitized.domain.chainId, 10);
    }
  }

  // 2. Strip unused struct definitions from types to conserve device SRAM
  const referencedTypes = new Set<string>();
  function findReferences(typeName: string) {
    referencedTypes.add(typeName);
    const fields = sanitized.types[typeName] || [];
    for (const f of fields) {
      const cleanType = f.type.replace('[]', '');
      if (sanitized.types[cleanType] && !referencedTypes.has(cleanType)) {
        findReferences(cleanType);
      }
    }
  }
  findReferences(sanitized.primaryType);

  for (const typeKey of Object.keys(sanitized.types)) {
    if (typeKey !== 'EIP712Domain' && !referencedTypes.has(typeKey)) {
      delete sanitized.types[typeKey];
    }
  }

  return sanitized;
}

Method B: Configure Trezor Suite & Trezor Bridge Settings

If signing as an end-user on a third-party dApp:

  1. Open Trezor Suite Desktop: Ensure your device is running the latest stable firmware (Model T $\ge$ 2.7.2, Safe 3 $\ge$ 2.7.2).
  2. Close Redundant Browser Tabs: Close background dApps or portfolio trackers querying your Trezor simultaneously via WebUSB.
  3. Switch to Trezor Bridge (Standalone Daemon):
    • In Trezor Suite settings, enable Trezor Bridge (http://127.0.0.1:21325/).
    • In your browser wallet (e.g., MetaMask Settings > Advanced), toggle the connection method from WebUSB to Trezor Bridge. Trezor Bridge handles USB packet framing natively in the OS kernel, preventing Chromium packet truncation.

Method C: Hardware Blind Signing Fallback

If the dApp uses ultra-complex nested struct arrays that inherently exceed device memory:

  1. Connect Trezor to Trezor Suite.
  2. Navigate to Settings > Device > Ethereum Safety Checks.
  3. Set the mode from Strict to Prompt.
  4. When prompted by the dApp, the Trezor screen will display Complex typed data. Review raw hash?.
  5. Confirm the primary struct hash matches the transaction to bypass on-device full tree deserialization.

(For similar blind signing and multicall configurations on hardware devices, consult our companion guide on Ledger Blind Signing Auto-Reset EIP-712 Multicall Revert).

Placeholder: Flowchart of Trezor EIP-712 Recovery and Sanitization Steps


5. Security Best Practices When Approving Off-Chain Hashes

  • Always Verify VerifyingContract: Before clicking confirm on device hashes, verify that the verifyingContract address on the hardware screen matches the verified contract on Etherscan or Blockscout.
  • Avoid Obsolete Firmware Versions: Firmware prior to 2.4.0 lacks full EIP-712 schema sanity checks and may silently sign arbitrary hashes.
  • Keep Backup Seed Phrases Air-Gapped: Never input your recovery seed into browser forms to “repair” a signing glitch. The 0x6985 error is strictly a transport/serialization error, not a cryptographic seed defect.
Partner Spotlight: Gate.io

Trade Securely on Gate.io

Don't risk your assets on centralized silos or unverified endpoints. Trade securely on Gate.io with deep liquidity and institutional-grade security protocols.

Claim $100 Sign-up Bonus

Official Partner Referral Link

Related Inquiries

What does Trezor error 0x6985 (Failure_ActionCancelled) mean?

Error 0x6985 (SW_CONDITIONS_NOT_SATISFIED in ISO 7816-4, or Failure_ActionCancelled in the Trezor Protocol Buffer specification) indicates that the device terminated the cryptographic signing workflow without returning an ECDSA signature. While it can occur if a user physically touches the 'Cancel' button, in automated dApps it almost always triggers automatically when the device firmware encounters an invalid, truncated, or unparseable EIP-712 typed data payload.

Why do EIP-712 Permit2 and CowSwap signatures fail on Trezor Model T and Safe 3?

Trezor Core firmware enforces strict parsing of EIP-712 schemas. If an application formats the domain separator's chainId as a hexadecimal string (e.g., '0x1') rather than an integer, or if the primaryType specifies an array of nested structs with dynamic byte slices that exceed the device's internal memory buffer (4 KB on Model T), the firmware fails the sanity check and cancels the session.

How does WebUSB / WebHID transport buffering contribute to 0x6985 errors?

When transmitting complex EIP-712 types over WebUSB in Chromium-based browsers, payloads larger than standard 64-byte HID chunks must be fragmented across multiple USB packets. If a browser extension (e.g., MetaMask or Rabby) drops a packet delimiter or timeouts while waiting for the user confirmation prompt, the device buffer aborts with 0x6985.