EIP-712 Typed Data Signing: Explained and Implemented

EIP-712 is the Ethereum standard for signing typed, structured data. Instead of asking a user to sign an unreadable hexadecimal string, a wallet shows the actual fields: the token, the amount, the spender, the deadline. The signature can then be verified by a smart contract or a backend. EIP-712 is the basis for gasless approvals, off-chain order books, meta-transactions and off-chain voting.

How EIP-712 Works

  • Types: the message is described as a struct with named, typed fields, such as Order(address maker, uint256 amount, uint256 nonce, uint256 deadline).
  • Domain separator: a hash of the application's name, version, chain ID and verifying contract. It binds the signature to one contract on one chain, so it cannot be replayed elsewhere.
  • Hashing: the struct is hashed according to fixed rules (type hash, encoded fields, nested structs and arrays hashed separately) and combined with the domain separator.
  • Signing and verifying: the wallet signs with eth_signTypedData_v4; the contract recovers the signer with ECDSA, or calls EIP-1271 when the signer is a smart contract wallet.

Where EIP-712 Is Used

  • Permit signatures (EIP-2612, Permit2): token approvals without a separate transaction.
  • Off-chain orders for exchanges and NFT marketplaces, settled on-chain only when matched.
  • Meta-transactions and gasless transactions, where a relayer pays the gas.
  • Off-chain governance votes, login messages and signed attestations.

Common Mistakes

Most EIP-712 bugs come from small mismatches: a type string that differs from the contract by one character, dynamic types (strings, bytes, arrays) encoded instead of hashed, a missing chain ID, or no nonce and deadline, which allows a signature to be reused. On the user side, signature requests are a favourite phishing tool, so the wallet display matters; see blind signing prevention.

How We Implement It

We define the types once and generate both the contract code and the front-end signing code from them, use proven libraries (OpenZeppelin's EIP712 and ECDSA in Solidity, ethers or viem in TypeScript), add nonces and deadlines by default, and test that signatures created in the browser verify in the contract, including for smart contract wallets.

Frequently Asked Questions

What is the difference between EIP-712 and personal_sign?

personal_sign signs a plain message the user can only read as text. EIP-712 signs structured fields that wallets display one by one and contracts can verify field by field.

Do EIP-712 signatures work across chains?

Not if they are built correctly: the chain ID in the domain separator ties each signature to one chain.

Do smart contract wallets support EIP-712?

Yes, through EIP-1271: the contract checks the signature by calling the wallet, which decides whether it is valid.

Who owns the code?

Ownership of the source code is set out in the contract, and the code can be fully yours, including documentation and tests.

Part of our work on signatures and signing and smart contract development.

Implement EIP-712 Signing

Tell us what your users need to sign and where it is verified. We will design the types, domain and checks so signatures are clear and cannot be reused.

Book a free call about EIP-712 signing