Skip to content

vaariance/eip-712

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EIP-712

Coverage Status

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.

Features

  • 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 validation
    • bool - Boolean values with flexible string parsing
    • bytes and bytesN - Dynamic and fixed-size byte arrays
    • string - UTF-8 encoded strings
    • uint/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)

Getting Started

Prerequisites

  • Dart SDK 2.17.0 or higher
  • Dependencies: web3dart and wallet

Installation

Add this package to your pubspec.yaml:

dependencies:
  eip712: ^0.0.1

Usage

Basic Usage

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)}');

Supported Data Types

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[]');

Version Differences

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,
);

Advanced Usage

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
);

Error Handling

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}');
}

Data Models

  • TypedMessage - Complete typed data structure
  • TypedDataVersion - Enum for v3/v4 versions
  • MessageTypeProperty - Individual field definition
  • EIP712Domain - Domain separator structure

Additional Information

  • Always validate contract addresses in the domain
  • Use appropriate chain IDs for network identification
  • Validate all input data before hashing

Contributing

Contributions are welcome! Please ensure:

  • All tests pass
  • Code follows Dart style guidelines
  • New features include comprehensive tests
  • Documentation is updated for API changes

Issues and Support

For bug reports and feature requests, please use the GitHub issue tracker.

About

Dart EIP-712 for structured data signing.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages