LayerZeroFault
hardware fallback Updated:

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

VV

Written by

Updated:

Technical Guide

The Trezor hardware wallet error 0x6985 Action Cancelled or Failure_ActionCancelled during EIP-712 typed data signatures (such as Uniswap Permit2, CowSwap, or Blur orders) occurs when the firmware encounters nested structs exceeding buffer memory or when the browser WebUSB bridge drops connection before confirmation. Updating Trezor firmware, enabling verbose domain display, or using desktop bridges prevents premature 0x6985 aborts.

To inspect the underlying contract call and verify EIP-712 parameters offline, use our interactive ERC-4337 Calldata Decoder Tool. For companion hardware signing errors, check Ledger blind signing auto-reset troubleshooting and Ledger EthAppCommandError 0x6a80 fixes.


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.

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).

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.

EIP-712 Signature & Hardware Signing Reference Matrix

Cross-reference EIP-712 typed data signing failures, hardware wallet firmware errors, and smart contract verification codes:

Failure Code / PlatformSubsystemRoot CauseResolution Guide
0x6a80 (Ledger)Arbitrum / EVM AppNested struct depth or blind signing disabledLedger EthAppCommandError 6a80 Fix
0x6985 (Trezor)Firmware Clear SigningStruct member overflow or rejected by userTrezor EIP-712 0x6985 Firmware Fix
Blind Signing RevertLedger Live Web3Blind signing turned OFF after app updateLedger Live EIP-712 Blind Signing Revert
Multicall Auto-ResetFirmware SessionFirmware resets blind signing on power cycleLedger Auto-Reset Blind Signing Multicall Fix
Type Hash MismatchViem / TypeScriptNested custom struct alphabetization bugViem EIP-712 Nested Types Hash Mismatch
ERC-7739 ReplaySmart Account / ERC-1271Cross-account signature replay on Permit2ERC-7739 Smart Account Signature Replay Fix

[!TIP] Decode packed EIP-712 structs and inspect UserOperation hashes locally in our client-side /tools/erc4337-calldata-decoder/.

Interactive Diagnostic Sandbox

Local Hardware & Derivation Sandboxes

Simulate BIP derivation paths, check gap limits, and verify BIP-39 checksums in your browser without transmitting sensitive keys.

Companion Diagnostic Guide Ledger Blind Signing Auto-Reset & EIP-712 Multicall Revert Fix →

Fix Ledger Ethereum firmware auto-resetting blind signing during EIP-712 multicalls. Re-enable debug data, clear 0x6a80 errors, and restore transaction execution.

Read Guide
Contextual Architecture

Related Diagnostic Guides

View All in Silo →

Related Inquiries

What does Trezor error 0x6985 Failure_ActionCancelled mean?

Error 0x6985 indicates that the Trezor hardware device or Trezor Connect bridge cancelled the signing session. It triggers when the user taps reject, when the USB transport times out, or when an EIP-712 typed data payload contains unparsed nested types that exceed device memory.

Why does Uniswap Permit2 fail with 0x6985 on Trezor Model T and Safe 3?

Permit2 uses complex EIP-712 typed structures with nested witness parameters. Older Trezor firmware cannot stream large EIP-712 structs, causing the device to reject the hash with Action Cancelled.

How do I fix Trezor 0x6985 on WebUSB and Chrome?

Update Trezor firmware to the latest version in Trezor Suite, close competing wallet extensions, and ensure Trezor Bridge is running if WebUSB disconnects during prompts.

How can I inspect the EIP-712 typed data parameters before signing?

Use our interactive ERC-4337 Calldata Decoder tool to unpack contract addresses, values, and nested struct hashes offline.