> ## Documentation Index
> Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# TypedData

> EIP-712 typed structured data for human-readable signatures

# TypedData

EIP-712 typed structured data - enables human-readable, type-safe signatures in Ethereum.

## Overview

TypedData implements EIP-712 for typed structured data signing. Instead of signing opaque hex strings, users sign structured data with clear field names and types. This improves UX and security by making signatures human-readable in wallets like MetaMask.

## Type Definition

```typescript theme={null}
type TypedDataType<T = Record<string, unknown>> = {
  readonly types: {
    readonly EIP712Domain: readonly TypedDataField[];
    readonly [key: string]: readonly TypedDataField[];
  };
  readonly primaryType: string;
  readonly domain: DomainType;
  readonly message: T;
};

type TypedDataField = {
  readonly name: string;
  readonly type: string;
};
```

## Usage

### Define Typed Data

```typescript theme={null}
import * as TypedData from './primitives/TypedData/index.js';

const typedData = TypedData.from({
  types: {
    EIP712Domain: [
      { name: 'name', type: 'string' },
      { name: 'version', type: 'string' },
      { name: 'chainId', type: 'uint256' },
      { name: 'verifyingContract', type: 'address' },
    ],
    Person: [
      { name: 'name', type: 'string' },
      { name: 'wallet', 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,
  },
  message: {
    from: {
      name: 'Alice',
      wallet: '0xaaa...',
    },
    to: {
      name: 'Bob',
      wallet: '0xbbb...',
    },
    contents: 'Hello, Bob!',
  },
});
```

### Hash for Signing

```typescript theme={null}
import { keccak256 } from './crypto/keccak256/index.js';

const hash = TypedData.hash(typedData, { keccak256 });
// Sign with wallet
const signature = await wallet.signMessage(hash);
```

### Encode Message

```typescript theme={null}
const encoded = TypedData.encode(typedData, { keccak256 });
```

### Validate Structure

```typescript theme={null}
TypedData.validate(typedData); // throws if invalid
```

## Type System

### Atomic Types

```typescript theme={null}
// Integers
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256'
'int8', 'int16', 'int32', 'int64', 'int128', 'int256'

// Other
'address'  // Ethereum address
'bool'     // Boolean
'string'   // UTF-8 string
'bytes'    // Dynamic bytes

// Fixed bytes
'bytes1', 'bytes2', ..., 'bytes32'
```

### Struct Types

Define custom types with named fields:

```typescript theme={null}
Person: [
  { name: 'name', type: 'string' },
  { name: 'wallet', type: 'address' },
]
```

### Array Types

Use `[]` suffix for arrays:

```typescript theme={null}
Group: [
  { name: 'name', type: 'string' },
  { name: 'members', type: 'Person[]' },
]
```

## Common Use Cases

### ERC-20 Permit (Gasless Approval)

```typescript theme={null}
const permitData = TypedData.from({
  types: {
    EIP712Domain: [
      { name: 'name', type: 'string' },
      { name: 'version', type: 'string' },
      { name: 'chainId', type: 'uint256' },
      { name: 'verifyingContract', type: 'address' },
    ],
    Permit: [
      { name: 'owner', type: 'address' },
      { name: 'spender', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'nonce', type: 'uint256' },
      { name: 'deadline', type: 'uint256' },
    ],
  },
  primaryType: 'Permit',
  domain: {
    name: 'USD Coin',
    version: '2',
    chainId: 1,
    verifyingContract: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
  },
  message: {
    owner: '0x123...',
    spender: '0x456...',
    value: 1000000n,
    nonce: 0n,
    deadline: 1234567890n,
  },
});

const hash = TypedData.hash(permitData, { keccak256 });
const signature = await wallet.signMessage(hash);

// Submit permit transaction
await token.permit(owner, spender, value, deadline, v, r, s);
```

### DAI Permit

```typescript theme={null}
const daiPermit = TypedData.from({
  types: {
    EIP712Domain: [
      { name: 'name', type: 'string' },
      { name: 'version', type: 'string' },
      { name: 'chainId', type: 'uint256' },
      { name: 'verifyingContract', type: 'address' },
    ],
    Permit: [
      { name: 'holder', type: 'address' },
      { name: 'spender', type: 'address' },
      { name: 'nonce', type: 'uint256' },
      { name: 'expiry', type: 'uint256' },
      { name: 'allowed', type: 'bool' },
    ],
  },
  primaryType: 'Permit',
  domain: {
    name: 'Dai Stablecoin',
    version: '1',
    chainId: 1,
    verifyingContract: '0x6b175474e89094c44da98b954eedeac495271d0f',
  },
  message: {
    holder: '0x123...',
    spender: '0x456...',
    nonce: 0n,
    expiry: 0n, // 0 = infinite
    allowed: true,
  },
});
```

### Meta-Transactions

```typescript theme={null}
const metaTx = TypedData.from({
  types: {
    EIP712Domain: [
      { name: 'name', type: 'string' },
      { name: 'version', type: 'string' },
      { name: 'chainId', type: 'uint256' },
      { name: 'verifyingContract', type: 'address' },
    ],
    ForwardRequest: [
      { name: 'from', type: 'address' },
      { name: 'to', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'gas', type: 'uint256' },
      { name: 'nonce', type: 'uint256' },
      { name: 'data', type: 'bytes' },
    ],
  },
  primaryType: 'ForwardRequest',
  domain: {
    name: 'MinimalForwarder',
    version: '0.0.1',
    chainId: 1,
    verifyingContract: '0x123...',
  },
  message: {
    from: '0x123...',
    to: '0x456...',
    value: 0n,
    gas: 100000n,
    nonce: 0n,
    data: '0xabcd...',
  },
});
```

### Voting/Governance

```typescript theme={null}
const vote = TypedData.from({
  types: {
    EIP712Domain: [
      { name: 'name', type: 'string' },
      { name: 'version', type: 'string' },
      { name: 'chainId', type: 'uint256' },
      { name: 'verifyingContract', type: 'address' },
    ],
    Ballot: [
      { name: 'proposalId', type: 'uint256' },
      { name: 'support', type: 'uint8' },
    ],
  },
  primaryType: 'Ballot',
  domain: {
    name: 'MyDAO',
    version: '1',
    chainId: 1,
    verifyingContract: '0x123...',
  },
  message: {
    proposalId: 42n,
    support: 1, // 0=against, 1=for, 2=abstain
  },
});
```

## Hash Calculation

EIP-712 hash is computed as:

```typescript theme={null}
hash = keccak256(
  "\x19\x01" ||
  domainSeparator ||
  keccak256(encodeData(primaryType, message, types))
)
```

Where:

* `\x19\x01` - EIP-191 structured data prefix
* `domainSeparator` - keccak256(encodeData('EIP712Domain', domain))
* `encodeData()` - Recursive encoding of message fields

## Type Encoding

### Simple Type

```typescript theme={null}
encodeType('Person', types)
// "Person(string name,address wallet)"
```

### Nested Types

```typescript theme={null}
encodeType('Mail', types)
// "Mail(Person from,Person to,string contents)Person(string name,address wallet)"
```

Dependencies are sorted alphabetically after the primary type.

## Security

### Benefits

1. **Human-readable** - Users see structured data, not hex
2. **Type-safe** - Field types are validated
3. **Domain-specific** - Signatures bound to contract/chain
4. **Replay-protected** - Domain separator prevents replay

### Validation

Always validate typed data:

```typescript theme={null}
try {
  TypedData.validate(typedData);
} catch (error) {
  console.error('Invalid typed data:', error);
}
```

### MetaMask Display

MetaMask shows structured data clearly:

```
Sign TypedData

Domain
  name: "USD Coin"
  version: "2"
  chainId: 1
  verifyingContract: 0xa0b8...

Message
  owner: 0x123...
  spender: 0x456...
  value: 1000000
  nonce: 0
  deadline: 1234567890
```

## Error Handling

TypedData operations throw typed errors for precise error handling:

### InvalidTypedDataError

Thrown when typed data structure is invalid.

```typescript theme={null}
import * as TypedData from './primitives/TypedData/index.js';
import { InvalidTypedDataError } from './primitives/TypedData/errors.js';

try {
  TypedData.from({ types: undefined, primaryType: 'Mail', domain: {}, message: {} });
} catch (e) {
  if (e instanceof InvalidTypedDataError) {
    console.log(e.name);     // "InvalidTypedDataError"
    console.log(e.code);     // "INVALID_TYPED_DATA"
    console.log(e.value);    // The invalid typed data object
    console.log(e.expected); // "valid EIP-712 typed data"
  }
}
```

### Validation Errors

The `validate()` function throws `InvalidTypedDataError` for various issues:

```typescript theme={null}
try {
  TypedData.validate(typedData);
} catch (e) {
  if (e instanceof InvalidTypedDataError) {
    // Common validation messages:
    // - "types must be an object"
    // - "types must include EIP712Domain"
    // - "primaryType must be a string"
    // - "primaryType 'X' not found in types"
    // - "domain must be an object"
    // - "message is required"
    // - "type 'X' not found in types"
    console.log(e.message);
  }
}
```

## Specification

* **EIP-712**: [https://eips.ethereum.org/EIPS/eip-712](https://eips.ethereum.org/EIPS/eip-712)
* **eth\_signTypedData\_v4**: [https://docs.metamask.io/wallet/reference/eth\_signtypeddata\_v4/](https://docs.metamask.io/wallet/reference/eth_signtypeddata_v4/)

## See Also

* [TypedData (Effect)](https://voltaire-effect.tevm.sh/primitives/typed-data) - Effect.ts integration with Schema validation
* [Domain](/primitives/domain) - Domain separator structure
* [DomainSeparator](/primitives/domain-separator) - Domain separator hash
* [Hash](/primitives/hash) - Hash primitive
* [Signature](/primitives/signature) - Signature primitive
