Error message
ambiguous primary types or unused types: "EIP712Domain", "Mail"
Also printed as
- ambiguous primary types or unused types
The listed names are your unreferenced types. `err.message` adds the full `types` value and the ethers version; `err.code` is `INVALID_ARGUMENT`.
ethers v6 (JavaScript) — TypedDataEncoder used by signTypedData / verifyTypedData
What it means
ethers infers the primary type of EIP-712 typed data as the single type that no other type references. In your `types` object several types are unreferenced, so it cannot tell which one is being signed — and in practice the extra one is almost always `EIP712Domain`. ethers builds the domain type itself from the `domain` argument and must not receive it in `types`, while the `eth_signTypedData_v4` JSON format (MetaMask, WalletConnect) includes it.
Why it happens
EIP712Domain left in types
commonTyped data copied from a wallet request, a back end that produces `eth_signTypedData_v4` payloads, or a test fixture includes `EIP712Domain`. ethers treats it as a second root type.
A type that nothing references
occasionalLeftover struct definitions (for example from an older version of the message) are roots too and make the primary type ambiguous.
Two independent structs in one object
occasionalTypes for different messages (Order and Cancel) combined into one `types` object. Pass only the types of the message you sign.
How to fix it
- 1.
Remove EIP712Domain before calling ethers
Destructure it away; the signature is identical to the one a wallet produces for the v4 payload, because the domain type is derived from the same fields.
js const { EIP712Domain, ...types } = typedData.types; // ethers derives the domain type itself const signature = await signer.signTypedData(typedData.domain, types, typedData.message); const signer2 = verifyTypedData(typedData.domain, types, typedData.message, signature); - 2.
Check the inferred primary type
`TypedDataEncoder.from(types).primaryType` throws the same error or returns the type ethers will hash — a quick check in tests.
js import { TypedDataEncoder } from "ethers"; // Inspect what ethers considers the primary type before signing. console.log(TypedDataEncoder.from(types).primaryType);