Goodbye Eth_sign, Hello Eip-712: The Safer Way To Sign Data
INTRODUCTION
What is eth_sign?
In the early days of Ethereum development, the eth_sign method was introduced to allow users to cryptographically sign arbitrary messages using their private key. This cryptographic operation is fundamental in blockchain applications for enabling authentication, identity verification, and off-chain message integrity (Wood, 2014).
When a dApp (decentralized application) calls eth_sign, the user is prompted to sign a hash of the provided data. The returned signature is valid from a cryptographic standpoint, but the signed content is often opaque, offering little or no readability to users.
Why It's a Problem for Users and Developers
Despite its simplicity and broad support in tools like MetaMask and Web3.js, eth_sign has critical flaws that compromise both user experience and security.
- Lack of Human-Readable Information
The most immediate problem with eth_sign is that users typically see only a hash or an unintelligible message, like 0x6f1a...e9bc, during signing. This makes it impossible for users to verify what they’re actually signing. Consequently, attackers can exploit this by prompting users to sign malicious data, such as a token approval or a contract interaction, under the guise of a harmless operation (OpenZeppelin, 2022).
- Ambiguity and Misinterpretation
Another critical issue is ambiguity. There is no standardized format for the data being signed, which means developers must establish off-chain agreements about data structure. Any mismatch in interpretation between systems can lead to failed verifications or worse — unintended actions on-chain (Buterin, 2018).
- Signature Reuse and Replay Attacks
Because the signature generated by eth_sign isn’t tied to any specific domain, chain ID, or smart contract, it becomes susceptible to replay attacks. A valid signature from one dApp could potentially be reused maliciously in another context, leading to unauthorized transactions (EIP-712, 2018).
- Developer Pain Points
From a developer’s perspective, using eth_sign increases the implementation burden. There’s no type safety or validation framework, so developers must:
Define custom data schemas.
Serialize and deserialize raw bytes manually.
Implement off-chain logic to verify the authenticity and meaning of signed data.
This fragmentation creates room for bugs, increases audit complexity, and makes integration across platforms more fragile.
WHAT IS EIP-712?
Overview of Ethereum Improvement Proposal 712
Ethereum Improvement Proposal 712 (EIP-712) is a standardized method for hashing and signing typed structured data on Ethereum. Proposed by Vitalik Buterin and others in 2017, and finalized in 2018, it introduces a secure, human-readable way for users to sign off-chain data while maintaining compatibility with Ethereum's cryptographic signature system (Buterin, 2018).
EIP-712 differs from traditional eth_sign methods by requiring that all data to be signed is explicitly structured and typed — similar to how data is defined in programming languages like C or Solidity. This structure enables precise verification of what is being signed, by whom, and for what context.
At its core, EIP-712 provides:
A typed data schema for consistent message formatting.
A domain separator to ensure signatures are application-specific.
A secure signing algorithm that hashes both the schema and the data.
These improvements reduce ambiguity, enhance security, and offer transparency during the signing process.
Key Goals and Benefits
Human-Readable Messages: EIP-712 enables wallets and signing interfaces (e.g., MetaMask) to display message content in a structured, readable format. This empowers users to make informed decisions before signing — a huge leap forward in usability and safety.
Contextual Security via Domain Separator: One of EIP-712’s cornerstone innovations is the domain separator. This ties the signed data to a specific context — such as a contract address, chain ID, app name, and version — preventing replay attacks across different dApps or networks.
Gasless Meta-Transactions: Because EIP-712 separates the data hashing/signing process from on-chain execution, it enables gasless transactions. A user can sign a structured message off-chain, and a relayer can then submit it to the blockchain and pay the gas fees — useful for onboarding new users or enabling mobile dApp interactions (OpenZeppelin, 2022).
Improved Developer Ergonomics: Developers benefit from standardized data structures and reduced guesswork. EIP-712 improves interoperability between frontends, smart contracts, and verification libraries — streamlining both implementation and security audits.
Use Cases:
Token approvals via Permit (EIP-2612)
Off-chain voting (e.g., Snapshot)
Gasless NFT listings (e.g., OpenSea)
Identity verification
Cross-chain signatures
PROBLEMS WITH TRADITIONAL SIGNATURES
Before the introduction of EIP-712, Ethereum dApps relied heavily on primitive signing methods like eth_sign. While functional, these approaches introduced serious issues related to usability, security, and trust — ultimately limiting the adoption and scalability of decentralized applications.
Poor User Experience (UX) and Readability
When users are prompted to sign a message using eth_sign, they’re typically shown a raw hexadecimal hash (e.g., 0x4e944ce...). This offers no visibility into what they're actually approving, especially for non-technical users.
This disconnect results in a high risk of users blindly signing critical operations like token transfers, contract approvals, or identity assertions.
"Most users can't interpret cryptographic hashes, which makes phishing significantly easier" (Buterin, 2018).
Wallets like MetaMask have struggled with this limitation, often displaying unintelligible data or requiring developers to create workaround modals — a fragile and error-prone approach.
Security Vulnerabilities
Lack of readable content directly contributes to multiple attack vectors, including:
Phishing attacks: Users tricked into signing malicious transactions because they can't see the message.
Replay attacks: Signatures can be reused on other networks or dApps due to lack of contextual separation.
Ambiguity: Without typed data, two different dApps might interpret the same message differently.
In one well-known incident, attackers used an innocuous-looking message to trick users into signing transactions that drained their wallets of ERC-20 tokens. Since the users couldn’t decipher the meaning of the hash, they unknowingly approved a malicious contract (Wong, 2019).
Lack of Domain Context
Traditional signatures lack domain separation — the metadata that defines the signing context (e.g., chain ID, dApp name, contract address). Without this, a valid signature from one dApp could be replayed on another, enabling cross-application attacks.
This absence of scoped security makes signatures dangerously portable and undermines trust between users and applications.
CORE CONCEPTS OF EIP-712
EIP-712 introduces a powerful mechanism for signing data on Ethereum: typed structured data signing. Unlike traditional approaches, this standard offers semantic clarity and cryptographic precision, enabling secure and user-friendly message approvals.
The core of EIP-712 is based on four primary concepts:
Structured Data
At the heart of EIP-712 is the idea of signing structured data, not arbitrary byte strings. This data is organized into readable and typed objects (like JSON), which make it easier for users and applications to interpret.
For example, instead of signing:

you sign:

Structured data makes signatures transparent, meaning users can clearly see and understand what they are signing (Buterin, 2018).
Type Definitions
EIP-712 uses type definitions — similar to structs in programming — to define the shape of the data being signed. These types are encoded in a deterministic way, which contributes to the hash that is ultimately signed.
Example:

This explicit definition helps ensure that everyone — wallet, dApp, and smart contract — interprets the data the same way.
Domain Separator
One of the most important safeguards in EIP-712 is the domain separator. This is a unique fingerprint that ties the signature to a specific context. It prevents a signed message in one application from being reused in another (known as a replay attack).
The domain separator includes:
name: Application name
version: Version of the signing protocol
chainId: Ethereum chain ID
verifyingContract: The contract address verifying the signature
Together, this creates a namespace that protects both the user and the dApp from cross-domain exploits (EIP-712, 2018).
Message Signing Flow
The EIP-712 signing process consists of the following steps:
Define typed data: Include all struct types and the message content.
Generate domain separator: Contextual metadata is hashed.
Encode and hash: Typed data and domain separator are encoded and hashed according to the EIP-712 rules.
Sign: The resulting digest is signed using eth_signTypedData_v4.
Diagrammatically:

This ensures the signature is both verifiable and human-readable, and can only be validated within the correct application context.
ANATOMY OF AN EIP-712 MESSAGE
To truly grasp how EIP-712 revolutionizes Ethereum signatures, it's essential to understand the internal anatomy of a typed data message. An EIP-712 message is composed of three main parts:
Domain Separator
Type Definitions
Message Object
Each plays a critical role in ensuring the security, readability, and contextual accuracy of signatures. Together, they are hashed and then signed to produce a verifiable digital signature.
Domain Separator
The domain separator establishes the "scope" of the data being signed. It's like a namespace that ensures a signature is only valid within a specific application, contract, and blockchain context. Without this, the same signed message could be maliciously replayed in another environment — a serious security risk.
Standard domain fields:
name: Name of your application or protocol.
version: Version of your app or schema.
chainId: Network identifier (e.g., 1 for Ethereum mainnet).
verifyingContract: Smart contract address expected to verify the signature.
This domain is encoded and hashed using EIP-712's encoding rules, and it ensures the signature is tied to a specific dApp and blockchain environment.
Type Definitions
EIP-712 allows defining complex, nested types (structs), similar to Solidity's structs. These definitions make the data self-descriptive and verifiable across platforms (frontends, wallets, and smart contracts).
Example:

The above defines a Mail message with nested Person objects. These types are then ABI-encoded and hashed in a deterministic way, forming part of the signature digest.
Message Object
This is the actual data that users approve and sign. It must conform exactly to the type structure defined above.
Example:
The frontend app displays this to the user in a readable format, and if approved, the data proceeds to hashing and signing.
Hashing & Signature Generation
The final step involves hashing the combination of:
Domain Separator Hash
Typed Data Hash
The full signature hash is constructed using this formula:
The prefix \x19\x01 ensures compatibility with EIP-191.
domainSeparator is the keccak256 hash of the domain struct.
hashStruct(message) is the keccak256 of the message using its defined types.
Then, the resulting digest is signed with the user’s private key using the eth_signTypedData_v4 method (commonly in MetaMask and other wallets).
The final output is a signature (v, r, s) tuple, which can be verified on-chain using ecrecover or via helper libraries like OpenZeppelin’s ECDSA.sol.
CODE EXAMPLES
Signing with Ethers.js & Verifying in Solidity
One of the most powerful aspects of EIP-712 is how easily it integrates across the stack — from frontend wallets to backend smart contracts. Here, we walk through two essential parts of the workflow:
Signing typed data off-chain using Ethers.js (frontend)
Verifying the signature on-chain using Solidity
Signing Typed Data with Ethers.js
Ethers.js supports EIP-712 through the signTypedData or eth_signTypedData_v4 RPC method via the wallet provider.
Here’s a complete example using Ethers.js v6+:
Note: Make sure the wallet (like MetaMask) supports eth_signTypedData_v4, which ensures proper domain and message structure support as per EIP-712.
Verifying EIP-712 Signature in Solidity
Here’s how you can verify that a message was signed by the correct address using Solidity and the EIP712 utility from OpenZeppelin:

Explanation:
_hashTypedDataV4(...) combines the domain separator and the typed struct hash.
ECDSA.recover() returns the signer’s address if the signature is valid.
You can compare this recovered address to an expected signer on-chain.
REAL-WORLD USE CASES OF EIP-712
EIP-712 isn’t just a theoretical improvement — it's already transforming the way Ethereum applications handle off-chain signing. Below are key real-world implementations that show how typed structured data improves security, reduces friction, and enables new UX patterns.
Permit-Based Token Approvals (EIP-2612)
Traditionally, ERC-20 token holders had to make two on-chain transactions:
approve() to allow a contract to spend their tokens
Followed by the actual transferFrom() call by the spender
With EIP-2612, token approvals can be signed off-chain using EIP-712 and submitted by anyone on-chain — removing the need for gas-spending by the token holder.

Benefits:
No need for approve() transactions
Enables one-click onboarding
Reduced gas costs and friction
Example: Uniswap and DAI token both support permit() to allow meta-approvals.
Gasless Meta-Transactions
EIP-712 enables meta-transactions, where users sign a message off-chain, and a relayer (or dApp backend) pays the gas fee to submit the transaction.
How it works:
User signs a structured message (intent to execute a function)
A relayer sends it on-chain, often getting reimbursed in tokens or fees
Benefits:
Removes the barrier of owning ETH
Great for onboarding new users or mobile-first wallets
Tools: Biconomy, OpenZeppelin Defender Relay, and Gelato Network utilize this pattern.
DAO Voting
Decentralized Autonomous Organizations (DAOs) often use off-chain vote signing to capture member intent before final execution.
Projects like Snapshot use EIP-712 to allow users to vote without gas fees, while maintaining signature authenticity and integrity.
Example Message:

Snapshot verifies the EIP-712 signature off-chain and records the vote.
Benefits:
Free voting for DAO members
Better UX than on-chain governance
Still cryptographically secure
Off-Chain Order Signing (e.g., DEXs like 0x)
Decentralized exchanges like 0x Protocol, dYdX, and CowSwap rely heavily on off-chain order creation and signing.
Users sign a structured order (e.g., sell X token for Y token) using EIP-712, which is then matched and submitted on-chain only when a trade is finalized.
Example Order:

Benefits:
Lower gas costs
Enables order books
Efficient high-frequency trading on-chain
WALLET AND LIBRARY SUPPORT
A major reason for the growing adoption of EIP-712 is its strong ecosystem compatibility across wallets and development libraries. As security and UX have become top priorities in web3 applications, most modern wallets and toolkits have implemented support for structured data signing.
Compatibility with Wallets
Several leading wallets have implemented native support for EIP-712, allowing users to sign structured data safely and understandably.
Wallet | EIP-712 Support | Notes |
MetaMask | Full support via eth_signTypedData_v4 | Shows readable message |
WalletConnect | Supported in v1 and v2 | Works with most mobile wallets |
Coinbase Wallet | Supported | Also shows domain info |
Trust Wallet | Supported | EIP-712 support added in updates |
Frame, Rabby, etc. | Supported | Focused on secure, readable signing |
MetaMask is the most widely used wallet supporting the eth_signTypedData_v4 method — the most secure and standard-conformant version of EIP-712. It renders the structured message in a human-readable UI, reducing phishing risks.
JavaScript/TypeScript Libraries
For developers, various JavaScript/TypeScript libraries make it easy to build and verify EIP-712 signatures across frontend and smart contracts.
Common Libraries
Library | Description |
ethers.js | Popular lightweight Ethereum lib; supports signTypedData |
viem | Modern typed library built for speed & DX |
Older but widely-used Ethereum lib; support varies | |
MetaMask’s internal tool for encoding/decoding EIP-712 data | |
Maintained NPM package for hashing and recovering signatures |
Why This Matters
Security: Wallets show human-readable messages, reducing phishing risk.
Developer Experience: Frontend and smart contracts use shared, typed schemas.
Interoperability: Consistent signing flow across wallets and dApps.
SECURITY BENEFITS OF EIP-712
One of the strongest motivations behind Ethereum Improvement Proposal 712 is to mitigate common security vulnerabilities associated with traditional signing methods. By introducing structured, typed data and domain-specific signing, EIP-712 provides a robust framework for secure off-chain message signing.
Prevention of Phishing Attacks
Traditional signing methods like eth_sign ask users to sign raw hexadecimal strings. These are unintelligible to most users and leave them vulnerable to phishing:
Malicious dApps could trick users into signing arbitrary transactions or permission grants.
Users often have no way of understanding what they're agreeing to.
EIP-712 prevents this by enforcing the display of human-readable, structured messages. Instead of a hex blob, users see clearly labeled fields — e.g., “Permit spender 0xABC to transfer 100 tokens from your account.”
✅ This drastically reduces the risk of social engineering and phishing attacks.
App-Specific Signatures (Domain Separation)
A key feature of EIP-712 is the domain separator, which includes:
App name (e.g., MyDApp)
Version (e.g., 1.0)
Chain ID (e.g., 1 for Ethereum Mainnet)
Verifying contract address
This domain data gets included in the signature hash, making it unique to a specific application.
🔐 Why this matters:
Even if an attacker reuses the same structured data on another site, the domain mismatch makes the signature invalid.
Prevents cross-site replay attacks and spoofing.
Improved User Awareness
EIP-712 makes signing more transparent:
Users see what they’re signing: the sender, recipient, amount, deadline, etc.
Many wallets (e.g., MetaMask, Rabby) render this information in a clean, readable format.
This gives users:
Greater confidence in what they approve.
The ability to review before committing.
A reduction in accidental approvals or token losses.
CHALLENGES AND GOTCHAS OF EIP-712
While EIP-712 brings powerful improvements to data signing in Ethereum, it also comes with non-trivial implementation hurdles. Developers need to be mindful of various challenges that can arise during integration across the frontend, backend, and smart contract layers.
Complex Implementation Details
Unlike eth_sign, which simply signs a message string or hash, EIP-712 requires:
Structuring typed data using JSON
Computing nested type hashes
Encoding domain and message data following ABI encoding rules
Concatenating hashes and signing the final digest
This complexity increases the potential for bugs — particularly in how types are defined and encoded.
Common pitfalls include:
Incorrect field ordering
Mismatched types between frontend and smart contracts
Errors in ABI-encoding nested structs
Pro Tip: Use established libraries like ethers.js or eth-sig-util that abstract away most of this logic.
Wallet Compatibility Quirks
Not all wallets fully support EIP-712, especially across mobile vs. desktop or older versions. While modern wallets like MetaMask, WalletConnect, and Rabby offer strong support, there are still edge cases:
Some wallets do not support complex nested types
Certain versions only support eth_signTypedData (deprecated) or eth_signTypedData_v4
UI inconsistencies may lead to confusing UX for users
Recommendation: Check wallet compatibility in your user base, and fallback to eth_sign (with user warnings) if necessary. Always test across devices and environments.
Keeping Frontend and Smart Contracts in Sync
One of the biggest sources of errors in EIP-712 implementation is a mismatch between frontend and on-chain struct definitions.
Example issues:
The frontend uses uint256 but the contract expects uint8
The frontend omits a field that the smart contract requires for signature validation
Differences in struct ordering or nesting
These subtle differences will cause signature verification to fail in Solidity (ecrecover returns invalid signer).
Best Practices:
Define your types in a shared schema (e.g., TypeScript interface or JSON schema)
Use code generators or tools like typechain to reduce desyncs
Include end-to-end tests for signing and verification
BEST PRACTICES FOR IMPLEMENTING EIP-712
Implementing EIP-712 correctly ensures that your dApp is not only secure and user-friendly, but also interoperable across wallets and tools. Below are key best practices to follow when integrating EIP-712 typed structured data signing into your Ethereum-based applications.
Structure Your Types Clearly
Why it matters: EIP-712 relies on consistent and predictable type definitions. The order, naming, and structure of types directly affect how the data is hashed and verified on-chain.
Recommendations:
Define a clear and concise set of types, starting from the domain struct.
Use meaningful names (Person, Order, Permit, etc.) to improve readability.
Keep the struct fields ordered exactly as expected by the Solidity contract.
Prefer flat structures when possible — deep nesting adds complexity and can cause compatibility issues.
Example:

Maintain a Consistent Domain Separator
Why it matters: The domain separator is what makes EIP-712 signatures unique to a specific dApp, network, and contract. Inconsistency can result in invalid or replayable signatures.
Recommendations:
Always include the following fields in your domain:
name: Your dApp's name
version: Schema or app version
chainId: Use dynamic chainId to prevent cross-network replay attacks
verifyingContract: The smart contract address that will verify the signature
Match these fields exactly in both the frontend (JS/TS) and backend (Solidity).
Sample Domain:

Test Thoroughly Across Networks and Wallets
Why it matters: Wallet implementations of eth_signTypedData_v4 can vary, and certain quirks may only appear under specific conditions or devices.
Recommendations:
Test signature generation and verification on mainnet, testnets (Goerli, Sepolia), and sidechains if applicable.
Run signing tests with:
MetaMask (desktop and mobile)
WalletConnect
Brave wallet
Coinbase Wallet
Hardware wallets (Ledger, Trezor)
Include both unit tests (e.g., hashing logic) and integration tests (e.g., full signing + contract verification).
Use mock users with various environments to simulate real-world usage and wallet behaviors.
CONCLUSION
Why EIP-712 Should Be the Default
EIP-712 represents a significant improvement over legacy signature methods like eth_sign. Its core advantage lies in its ability to make off-chain signed messages human-readable, structured, and secure. Where traditional raw byte signing is opaque and vulnerable to phishing, EIP-712 ensures that:
Users see exactly what they're signing, improving transparency.
Signatures are tied to a specific domain, preventing replay attacks across apps and networks.
Developers can build safer and more intuitive UX for signing data off-chain, including permits, orders, and meta-transactions.
In the evolving landscape of decentralized applications, especially those prioritizing usability and security, EIP-712 should be considered a default standard for any feature that requires off-chain signing.
Next Steps for Developers
For teams and builders ready to transition or start using EIP-712, here are practical next actions:
1. Audit Your Signature Flow
- If you're still using eth_sign or personal_sign, evaluate where structured signing could improve security and usability.
2. Adopt Ethers.js or Viem for Frontend Integration
Use Ethers.js' signTypedData method with MetaMask or WalletConnect.
Consider libraries like viem or @metamask/eth-sig-util for easier schema management.
3. Implement EIP-712 Verification in Smart Contracts
- Use Solidity's EIP712 and ECDSA libraries (OpenZeppelin provides robust utilities).
4. Test Across Wallets and Devices
- Don’t assume uniform behavior. Simulate signing on MetaMask mobile, Ledger, WalletConnect, and Brave.
5. Start with Common Use Cases
Migrate approve() flows to use permit() (see EIP-2612).
Implement gasless meta-transactions.
Use EIP-712 for DAO voting, trade orders, or content attestations.
REFERENCES
Buterin, V. (2018). EIP-712: Ethereum typed structured data hashing and signing. Ethereum Improvement Proposals. https://eips.ethereum.org/EIPS/eip-712
Buterin, V., & Wolovim, L. (2018). EIP-712: Ethereum typed structured data hashing and signing. Ethereum Foundation. https://eips.ethereum.org/EIPS/eip-712
EIP-2612: Permit Extension to ERC-20. (n.d.). Ethereum Improvement Proposals. https://eips.ethereum.org/EIPS/eip-2612
Ethers.js Docs. (2024). Signer.signTypedData. https://docs.ethers.org/v5/api/signer/#Signer-signTypedData
Ethers.js. (2024). Signing Typed Data. https://docs.ethers.org/v6/api/signer/#Signer-signTypedData
Ethereum Foundation. (2018). EIP-712: Ethereum typed structured data hashing and signing. https://eips.ethereum.org/EIPS/eip-712
Ethereum Foundation. (n.d.). EIP-191: Signed Data Standard. https://eips.ethereum.org/EIPS/eip-191
MetaMask. (2023). eth-sig-util GitHub Repository. https://github.com/MetaMask/eth-sig-util
MetaMask. (2023). eth_signTypedData: Signing structured messages. https://docs.metamask.io/guide/signing-data.html
MetaMask. (2023). eth_signTypedData_v4 Documentation. https://docs.metamask.io/guide/signing-data.html#signing-data-with-eip-712
MetaMask. (2023). Signing Typed Data. https://docs.metamask.io/guide/signing-data.html#signing-data-with-eip-712
MetaMask. (2023). Understanding Signature Requests. https://support.metamask.io
MetaMask Docs. (2024). Phishing Protection via Message Signing. https://docs.metamask.io/
MetaMask Docs. (2024). Signing structured data. https://docs.metamask.io/guide/signing-data.html
MetaMask Docs. (2024). Signing typed data. https://docs.metamask.io/guide/signing-data.html
OpenZeppelin. (2022). EIP-712 Signing. https://docs.openzeppelin.com/contracts/4.x/api/utils#EIP712
OpenZeppelin. (2022). Security Considerations in Ethereum dApps. https://docs.openzeppelin.com
OpenZeppelin. (2023). EIP712 - OpenZeppelin Contracts. https://docs.openzeppelin.com/contracts/4.x/api/utils#EIP712
OpenZeppelin. (2024). Contracts - EIP712. https://docs.openzeppelin.com/contracts/4.x/api/utils#EIP712
OpenZeppelin. (2024). Understanding EIP-712 and Meta-transactions. https://docs.openzeppelin.com/contracts/4.x/api/utils#EIP712
Snapshot. (2024). Gasless Governance. https://docs.snapshot.org/
Ternoa Devs. (2023). EIP-712: Gotchas and Workarounds. https://dev.ternoa.network/docs/tutorials/eip-712
WalletConnect. (2023). Secure Signing Methods. https://docs.walletconnect.com/security/signing
Wong, J. (2019). Ethereum phishing attack uses eth_sign to steal tokens. Medium. https://medium.com/mycrypto/stop-using-eth-sign-7c6c4d8c14c1
Wood, G. (2014). Ethereum: A secure decentralized generalized transaction ledger. Ethereum Project Yellow Paper. https://ethereum.github.io/yellowpaper/paper.pdf
0x Protocol. (2024). Off-Chain Orders. https://0x.org/docs/guides