Paste eth_signTypedData_v4 JSON to get the EIP-712 digest, domain separator, typeHash and every field encoding. Check a wallet prompt without signing.
When a dapp asks your wallet to “sign typed data”, the wallet does not sign the JSON you see. It hashes it first, in a fixed order set by EIP-712, and signs a single 32-byte digest. If a contract computes that digest even slightly differently, every signature fails with nothing more helpful than “invalid signature”. This calculator shows each step of the hash.
Paste the object a dapp passes to eth_signTypedData_v4, with its types, primaryType, domain and message. You get the digest a wallet signs, the domain separator, hashStruct of the message, the typeHash and the encodeType string behind it, and the 32-byte word every field turns into. Nothing is signed and no wallet is connected, so it is also a safe way to see what a signature request actually commits you to.
The tool opens with the example from the EIP-712 specification: a Mail from Cow to Bob reading "Hello, Bob!", on the “Ether Mail” domain at chain 1. The encodeType reads Mail(Person from,Person to,string contents)Person(string name,address wallet), the domain separator is 0xf2cee375…a912090f, and the digest is 0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2, the same value the specification publishes. Press Load Sample to switch to an ERC-2612 Permit on the USDC contract. That is the gasless-approval message most wallets show, and its digest is 0x106980d0…3ede1bd9d7.
Two outputs are easy to miss. Solidity constant gives a ready line such as bytes32 constant MAIL_TYPEHASH = keccak256("…"); for the verifying contract. The field tables let you copy any single encoded word, which is the fastest way to find the one field where your contract and your frontend disagree.
intN values use two's complement.bytes1 to bytes32: right-padded to 32 bytes, and the value must be exactly N bytes long.string and bytes: replaced by their keccak256, so a 5,000-character string still takes one word.The digest is keccak256 of 0x19 0x01, the domain separator and hashStruct(message). Results were checked against the ethers.js 6.13.4 TypedDataEncoder on 2,000 randomly generated schemas and messages, and a signature made by ethers recovered to the same key from this digest.
uint is rejected because EIP-712 needs the full name, uint256.Big numbers go in quotes: "1000000000000000000".
0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2keccak256(0x19 ‖ 0x01 ‖ domain separator ‖ hashStruct(message)). This is what ecrecover is given on-chain.
0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090fEIP712Domain(string name,string version,uint256 chainId,address verifyingContract)
0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371ekeccak256(typeHash ‖ encoded fields)
0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2keccak256 of the encodeType string below
Mail(Person from,Person to,string contents)Person(string name,address wallet)Referenced structs follow the primary type, sorted by name.
bytes32 constant MAIL_TYPEHASH = keccak256("Mail(Person from,Person to,string contents)Person(string name,address wallet)");Paste into the verifying contract.
0xc70ef06638535b4881fafcac8287e210e3769ff1a8e91f1b95d6246e61e4d3c6keccak256 of 10-byte UTF-8 string
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6keccak256 of 1-byte UTF-8 string
0x0000000000000000000000000000000000000000000000000000000000000001uint256, left-padded to 32 bytes
0x000000000000000000000000ccccccccccccccccccccccccccccccccccccccccaddress, left-padded to 32 bytes
0xfc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8hashStruct(Person)
0xcd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1hashStruct(Person)
0xb5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8keccak256 of 11-byte UTF-8 string
Hashing only: nothing is signed, no wallet is connected and no key is asked for. Compare the digest here with what a dapp asks your wallet to sign before you approve it.