A Comprehensive and Strictly Typed Dart implementation of EIP-712 (Ethereum Typed Structured Data Hashing and Signing) that provides secure and standardized message signing for Ethereum applications.
- Complete EIP-712 Implementation: Full support for typed structured data hashing according to the EIP-712 specification
- Multi-Version Support: Compatible with both EIP-712 v3 and v4 standards
- Comprehensive Type Support: Handles all Ethereum data types including:
address
- Ethereum addresses with automatic hex validationbool
- Boolean values with flexible string parsingbytes
andbytesN
- Dynamic and fixed-size byte arraysstring
- UTF-8 encoded stringsuint
/int
- Signed and unsigned integers (8 to 256 bits) with range validation- Arrays - Support for dynamic and fixed-size arrays (v4 only)
- Custom struct types with automatic dependency resolution
- Type Safety: Strict typing and Built-in range checking for integer types and comprehensive error handling
- Domain Separation: Proper EIP-712 domain hashing
- Null Handling: Smart null value processing for optional fields (v4)
- Dart SDK 2.17.0 or higher
- Dependencies:
web3dart
andwallet
Add this package to your pubspec.yaml
:
dependencies:
eip712: ^0.0.1
import 'package:eip712/eip712.dart';
// Define your typed message structure
final typedMessage = TypedMessage(
types: {
'EIP712Domain': [
MessageTypeProperty(name: 'name', type: 'string'),
MessageTypeProperty(name: 'version', type: 'string'),
MessageTypeProperty(name: 'chainId', type: 'uint256'),
MessageTypeProperty(name: 'verifyingContract', type: 'address'),
],
'Person': [
MessageTypeProperty(name: 'name', type: 'string'),
MessageTypeProperty(name: 'wallet', type: 'address'),
],
'Mail': [
MessageTypeProperty(name: 'from', type: 'Person'),
MessageTypeProperty(name: 'to', type: 'Person'),
MessageTypeProperty(name: 'contents', type: 'string'),
],
},
primaryType: 'Mail',
domain: EIP712Domain(
name: 'Ether Mail',
version: '1',
chainId: BigInt.from(1),
verifyingContract: EthereumAddress.fromHex('0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'),
salt: null,
),
message: {
'from': {
'name': 'Cow',
'wallet': EthereumAddress.fromHex('0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826'),
},
'to': {
'name': 'Bob',
'wallet': EthereumAddress.fromHex('0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'),
},
'contents': 'Hello, Bob!',
},
);
Alternatively you can use the TypedMessage.fromJson
method.
import 'package:eip712/eip712.dart';
final typedMessage = TypedMessage.fromJson(
{
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"Person": [
{"name": "name", "type": "string"},
{"name": "wallets", "type": "address"},
],
"Mail": [
{"name": "from", "type": "Person"},
{"name": "to", "type": "Person"},
{"name": "contents", "type": "string"},
],
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
},
"message": {
"from": {
"name": "Cow",
"wallet": 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826,
},
"to": [
{
"name": "Bob",
"wallet": 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826,
},
],
"contents": "Hello, Bob!",
},
}
)
Generate the hash using EIP-712 v4/v3
final hash = hashTypedData(
typedData: typedMessage,
version: TypedDataVersion.v4,
);
print('EIP-712 Hash: ${bytesToHex(hash, include0x: true)}');
Primitive Types
// Address type with automatic validation
final addressField = MessageTypeProperty(name: 'recipient', type: 'address');
// Boolean with flexible parsing
final boolField = MessageTypeProperty(name: 'approved', type: 'bool');
// Integer types with range validation
final uint256Field = MessageTypeProperty(name: 'amount', type: 'uint256');
final int128Field = MessageTypeProperty(name: 'balance', type: 'int128');
// String type (UTF-8 encoded and hashed)
final stringField = MessageTypeProperty(name: 'message', type: 'string');
// Bytes types (dynamic and fixed-size)
final bytesField = MessageTypeProperty(name: 'data', type: 'bytes');
final bytes32Field = MessageTypeProperty(name: 'hash', type: 'bytes32');
Array Types (EIP-712 v4 only)
// Dynamic arrays
final dynamicArray = MessageTypeProperty(name: 'items', type: 'string[]');
// Fixed-size arrays
final fixedArray = MessageTypeProperty(name: 'coordinates', type: 'uint256[2]');
// Arrays of custom types
final structArray = MessageTypeProperty(name: 'people', type: 'Person[]');
EIP-712 v3:
- Basic type support without arrays
- Simpler encoding scheme
EIP-712 v4 (Recommended):
- Full array support (dynamic and fixed-size)
- Enhanced null value handling
- Improved type dependency resolution
// Use v3 for compatibility with older systems
final hashV3 = hashTypedData(
typedData: typedMessage,
version: TypedDataVersion.v3,
);
// Use v4 for full feature support (default)
final hashV4 = hashTypedData(
typedData: typedMessage,
version: TypedDataVersion.v4,
);
Custom Struct Types
final customTypes = {
'Asset': [
MessageTypeProperty(name: 'token', type: 'address'),
MessageTypeProperty(name: 'amount', type: 'uint256'),
],
'Order': [
MessageTypeProperty(name: 'maker', type: 'address'),
MessageTypeProperty(name: 'taker', type: 'address'),
MessageTypeProperty(name: 'assets', type: 'Asset[]'), // Array of custom type
MessageTypeProperty(name: 'expiry', type: 'uint256'),
],
};
Domain Separation
// Each domain creates a unique signing context
final domain = EIP712Domain(
name: 'MyDApp',
version: '1.0',
chainId: BigInt.from(1), // Ethereum mainnet
verifyingContract: EthereumAddress.fromHex('0x1234...'), // Your contract address
salt: intToBytes(2), // Optional additional entropy
);
The package provides comprehensive error handling:
try {
final hash = hashTypedData(
typedData: typedMessage,
version: TypedDataVersion.v4,
);
} on ArgumentError catch (e) {
// Handle missing values or invalid types
print('Argument error: ${e.message}');
} on RangeError catch (e) {
// Handle integer overflow/underflow
print('Range error: ${e.message}');
} on AssertionError catch (e) {
// Handle unrecognized type primitives
print('Unsupported error: ${e.message}');
}
TypedMessage
- Complete typed data structureTypedDataVersion
- Enum for v3/v4 versionsMessageTypeProperty
- Individual field definitionEIP712Domain
- Domain separator structure
- Always validate contract addresses in the domain
- Use appropriate chain IDs for network identification
- Validate all input data before hashing
Contributions are welcome! Please ensure:
- All tests pass
- Code follows Dart style guidelines
- New features include comprehensive tests
- Documentation is updated for API changes
For bug reports and feature requests, please use the GitHub issue tracker.