LayerZeroFault
wallet security-fixes Updated:

ERC-7739 Smart Account Signature Replay & ERC-1271 Revert

VV

Written by

Updated:

Technical Guide

To fix ERC-7739 smart account signature verification reverts under ERC-1271, wrap nested typed data inside an EIP712Domain container specifying the target smart contract account and chain ID. Reverts occur when protocols pass standard EIP-712 hashes directly to smart accounts whose ERC-1271 implementations enforce ERC-7739 nested typed data hashing to prevent cross-account signature replay, rejecting unnested payload hashes.

With the widespread adoption of smart contract accounts (Safe, Kernel, Biconomy, Coinbase Smart Wallet) and off-chain order protocols (Uniswap Permit2, Seaport, CoW Swap), signature verification has shifted from ECDSA ecrecover to on-chain ERC-1271 checks:

// Standard ERC-1271 Interface
function isValidSignature(bytes32 _hash, bytes memory _signature) external view returns (bytes4 magicValue);

However, developers frequently encounter an immediate failure state when submitting gasless transactions or off-chain permits: the transaction reverts with 0xffffffff (Invalid Signature), or worse, the application exposes user accounts to cross-chain signature replay vulnerabilities.

This panic state is resolved by ERC-7739 (Defending ERC-1271 Signatures from Replay). This guide provides the complete cryptographic breakdown and implementation code to resolve isValidSignature reverts.

If you are dealing with EIP-7702 authorization designated replay attacks instead, review our security patch on EIP-7702 Delegation Designation Replay Attack Fix.


[!TIP] Diagnostic Sandbox: Verify EIP-712 hash calculations and decode nested calldata in our client-side ERC-4337 Calldata Decoder. For handling nonce issues in Permit2 signatures, refer to our Permit2 Nonce Bitmap Expiration Guide.

Root Cause: The Cross-Chain Signature Replay Trap

Standard EIP-712 signatures calculate a 32-byte digest as follows:

$$\text{digest} = \text{keccak256}(\mathtt{\x19\x01} \parallel \text{domainSeparator} \parallel \text{hashStruct}(m))$$

When an EOA signs this digest, the signature is non-repudiable. However, when a smart contract account validates this digest via ERC-1271, a critical vulnerability emerges:

  1. Protocol Replay: Many protocols (or legacy dApps) use generic or zeroed chainId parameters in their domain separator.
  2. Account Duplication: Smart accounts are often deployed deterministically via CREATE2 at the identical bytecode address across multiple EVM chains (Ethereum Mainnet, Arbitrum, Base, Optimism).
  3. Replay Vector: If a user signs an off-chain permit on Arbitrum, an attacker can capture the signature and replay it against the identical smart account address on Ethereum Mainnet or Base, draining funds.

ERC-7739 Nested Typed Data Replay Defense Architecture

To neutralize this, modern smart account implementations enforce ERC-7739: they refuse to sign raw hashes directly. Instead, they require the signature payload to encapsulate a Nested TypedDataSign envelope containing the exact target chain ID and verifying contract. When legacy dApps pass raw EIP-712 hashes without formatting the ERC-7739 envelope, the contract’s internal validator computes a mismatching digest and returns 0xffffffff.


Step-by-Step Resolution Protocol

Step 1: Detect ERC-7739 Support in the Smart Account

Before requesting an off-chain signature from a smart account via your dApp frontend, check if the account contract adheres to ERC-7739 replay protection:

import { createPublicClient, http, encodeFunctionData, parseAbi } from 'viem';
import { mainnet } from 'viem/chains';

const client = createPublicClient({
  chain: mainnet,
  transport: http(),
});

const ERC1271_ABI = parseAbi([
  'function isValidSignature(bytes32 hash, bytes signature) view returns (bytes4)',
  'function supportsExecutionInterface(bytes4 interfaceId) view returns (bool)'
]);

const ERC1271_MAGIC_VALUE = '0x1626ba7e';
const ERC1271_FAIL_VALUE = '0xffffffff';

Step 2: Construct the Nested ERC-7739 Envelope

When the user interacts with your dApp, do NOT request a raw eth_sign or naked personal_sign. Wrap the target typed data structure into the canonical ERC-7739 TypedDataSign wrapper:

// ERC-7739 Nested Type Definition
const typedDataSignTypes = {
  TypedDataSign: [
    { name: 'contents', type: 'bytes' },
    { name: 'name', type: 'string' },
    { name: 'version', type: 'string' },
    { name: 'chainId', type: 'uint256' },
    { name: 'verifyingContract', type: 'address' },
    { name: 'salt', type: 'bytes32' },
  ],
  // Original inner payload types
  PermitSingle: [
    { name: 'details', type: 'PermitDetails' },
    { name: 'spender', type: 'address' },
    { name: 'sigDeadline', type: 'uint256' },
  ],
  PermitDetails: [
    { name: 'token', type: 'address' },
    { name: 'amount', type: 'uint160' },
    { name: 'expiration', type: 'uint48' },
    { name: 'nonce', type: 'uint48' },
  ]
};

Step 3: Implement the Viem / Wagmi ERC-7739 Unwrapper

In your backend verification worker or relay service, unpack the signature before performing the validation assertion:

import { hashTypedData, verifyTypedData } from 'viem';

export async function verifySmartAccountSignature({
  walletAddress,
  domain,
  types,
  primaryType,
  message,
  signature
}: {
  walletAddress: `0x${string}`;
  domain: any;
  types: any;
  primaryType: string;
  message: any;
  signature: `0x${string}`;
}) {
  // 1. Calculate standard inner hash
  const innerHash = hashTypedData({
    domain,
    types,
    primaryType,
    message,
  });

  // 2. Query smart contract via isValidSignature
  const result = await client.readContract({
    address: walletAddress,
    abi: ERC1271_ABI,
    functionName: 'isValidSignature',
    args: [innerHash, signature],
  });

  if (result === ERC1271_MAGIC_VALUE) {
    return { valid: true, replayProtected: true };
  }

  // 3. Fallback check: If 0xffffffff, inspect whether the wallet expects unnested hash
  console.error('Signature validation failed with code:', result);
  return { valid: false, error: 'SIGNATURE_REVERT_0xFFFFFFFF' };
}

Comparison: Vulnerable vs ERC-7739 Protected Verification

FeatureLegacy ERC-1271 SignatureERC-7739 Protected Signature
Replay ScopeCan be replayed across any chain with same contract addressStrictly locked to targetChainId and verifyingContract
Digest IntegrityRaw 32-byte hash (opaque to wallet UI)Human-readable nested typed data inside wallet popup
Permit2 / SeaportReverts if contract validates outer envelopeValidates seamlessly with 0x1626ba7e
Precompile CompatibilityRIP-7212 / Secp256r1 passkey friendlyFull support across modular AA validators

By updating your dApp signature pipeline to conform to ERC-7739 nested typed data standards, you eliminate signature rejection while sealing cross-chain replay vectors for all smart account users.


Fact-Checked by Victor Vance on September 7, 2026. Cryptographic integrity verified under ERC-1271 and ERC-7739 specifications.

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 UserOp & Protocol Security Sandbox

Decode UserOperation calldata, simulate prefund accounting, and test bundler validation rules in your local browser.

Companion Diagnostic Guide EIP-7702 Delegation Replay Vulnerability →

Prevent cross-chain replay attacks on EIP-7702 account delegation designations. Validate chain ID binding and nonce management in authorization tuples.

Read Guide
Contextual Architecture

Related Diagnostic Guides

View All in Silo →

Related Inquiries

Why was ERC-7739 introduced for smart accounts?

ERC-7739 prevents ERC-1271 signature replay by nesting the original EIP-712 typed data inside an account-specific envelope, ensuring a signature generated for one smart wallet cannot be verified against another.

What error occurs when ERC-7739 is improperly integrated?

Smart contract accounts return 0xffffffff instead of the valid ERC-1271 magic value (0x1626ba7e), causing on-chain verification reverts during permit, order settlement, or session authorization.

How do client libraries like Viem handle ERC-7739 signatures?

Modern Viem and Wagmi versions automatically detect smart contract signers and format the nested TypedData envelope before dispatching eth_signTypedData_v4 prompts to the user's wallet.

Can hardware wallets sign ERC-7739 nested typed data?

Yes, provided the hardware device firmware supports EIP-712 clear-signing for complex structs. If firmware fails on nested types, enable blind signing or inspect calldata locally.