> ## 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.

# EIP-712 Typed Data

> Sign structured data with domain separation

<Info>
  **Skill** — Copyable reference implementation. Use as-is or customize. See [Skills Philosophy](/concepts/skills).
</Info>

EIP-712 enables human-readable message signing with domain separation. Instead of signing opaque hex blobs, users see structured data in their wallet prompts.

## Domain Separator

The domain separator cryptographically binds signatures to specific contract + chain combinations, preventing replay attacks.

```typescript theme={null}
import * as EIP712 from '@voltaire/crypto/eip712';
import * as Address from '@voltaire/primitives/address';

const domain = {
  name: 'MyDApp',           // Application name
  version: '1',             // Version string
  chainId: 1n,              // Chain ID (bigint)
  verifyingContract: Address.from('0x742d35Cc6634C0532925a3b844Bc9e7595f251e3')
};

// Hash the domain separator
const domainHash = EIP712.Domain.hash(domain);
// Returns: Uint8Array(32) - keccak256 hash
```

Domain fields:

* **name**: DApp name shown to user
* **version**: Versioning for signature schema changes
* **chainId**: Prevents cross-chain replay (mainnet=1, polygon=137)
* **verifyingContract**: Contract that will verify the signature
* **salt** (optional): Additional entropy for extra separation

## Type Definitions

Define message structure using Solidity-like type definitions:

```typescript theme={null}
const types = {
  // Simple struct
  Person: [
    { name: 'name', type: 'string' },
    { name: 'wallet', type: 'address' }
  ],

  // Struct with nested reference
  Mail: [
    { name: 'from', type: 'Person' },      // References Person type
    { name: 'to', type: 'Person' },
    { name: 'contents', type: 'string' }
  ]
};

// Encode type for debugging
const typeString = EIP712.encodeType('Mail', types);
// "Mail(Person from,Person to,string contents)Person(string name,address wallet)"

// Get type hash
const typeHash = EIP712.hashType('Mail', types);
```

Supported types:

* **Integers**: `uint8` through `uint256`, `int8` through `int256`
* **Address**: `address`
* **Boolean**: `bool`
* **Fixed bytes**: `bytes1` through `bytes32`
* **Dynamic**: `bytes`, `string`
* **Arrays**: `type[]`, `type[N]`
* **Custom structs**: Referenced by name

## Hashing Typed Data

Complete typed data combines domain, types, primary type, and message:

```typescript theme={null}
import * as EIP712 from '@voltaire/crypto/eip712';
import * as Address from '@voltaire/primitives/address';

const typedData = {
  domain: {
    name: 'Ether Mail',
    version: '1',
    chainId: 1n,
    verifyingContract: Address.from('0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC')
  },
  types: {
    Person: [
      { name: 'name', type: 'string' },
      { name: 'wallet', type: 'address' }
    ],
    Mail: [
      { name: 'from', type: 'Person' },
      { name: 'to', type: 'Person' },
      { name: 'contents', type: 'string' }
    ]
  },
  primaryType: 'Mail',
  message: {
    from: {
      name: 'Alice',
      wallet: Address.from('0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826')
    },
    to: {
      name: 'Bob',
      wallet: Address.from('0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB')
    },
    contents: 'Hello, Bob!'
  }
};

// Hash for signing: keccak256("\x19\x01" || domainSeparator || hashStruct(message))
const hash = EIP712.hashTypedData(typedData);
// Returns: Uint8Array(32)
```

## Signing Typed Data

Sign with a private key to produce ECDSA signature:

```typescript theme={null}
import * as EIP712 from '@voltaire/crypto/eip712';
import * as PrivateKey from '@voltaire/primitives/private-key';

// Your private key (32 bytes)
const privateKey = PrivateKey.from('0x...');

// Sign - produces { r, s, v }
const signature = EIP712.signTypedData(typedData, privateKey);

console.log(signature.r);  // Uint8Array(32)
console.log(signature.s);  // Uint8Array(32)
console.log(signature.v);  // 27 or 28
```

The signature can be submitted to:

* Smart contracts via `ecrecover`
* Relayers for meta-transactions
* Order books for DEX orders

## Verifying Typed Signatures

Recover the signer's address and verify it matches expected:

```typescript theme={null}
import * as EIP712 from '@voltaire/crypto/eip712';
import * as Address from '@voltaire/primitives/address';

// Recover signer address from signature
const recoveredAddress = EIP712.recoverAddress(signature, typedData);

// Verify against expected signer
const expectedSigner = Address.from('0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826');
const isValid = EIP712.verifyTypedData(signature, typedData, expectedSigner);

if (isValid) {
  console.log('Signature is valid');
} else {
  console.log('Invalid signature or wrong signer');
}
```

Verification uses constant-time comparison to prevent timing attacks.

## Complete Example: ERC-2612 Permit

Gasless token approvals using EIP-712:

```typescript theme={null}
import * as EIP712 from '@voltaire/crypto/eip712';
import * as Address from '@voltaire/primitives/address';
import * as PrivateKey from '@voltaire/primitives/private-key';

// Token holder's private key
const ownerKey = PrivateKey.from('0x...');

// Permit message for gasless approval
const permitData = {
  domain: {
    name: 'USD Coin',
    version: '2',
    chainId: 1n,
    verifyingContract: Address.from('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')
  },
  types: {
    Permit: [
      { name: 'owner', type: 'address' },
      { name: 'spender', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'nonce', type: 'uint256' },
      { name: 'deadline', type: 'uint256' }
    ]
  },
  primaryType: 'Permit',
  message: {
    owner: Address.from('0xOwnerAddress...'),
    spender: Address.from('0xSpenderAddress...'),
    value: 1000000n,        // 1 USDC (6 decimals)
    nonce: 0n,              // Current nonce from contract
    deadline: 1735689600n   // Unix timestamp expiry
  }
};

// Sign the permit
const signature = EIP712.signTypedData(permitData, ownerKey);

// Submit to contract: permit(owner, spender, value, deadline, v, r, s)
// Relayer pays gas, owner's tokens get approved
```

## Validation

Validate typed data structure before signing:

```typescript theme={null}
import * as EIP712 from '@voltaire/crypto/eip712';

try {
  EIP712.validate(typedData);
  console.log('Valid typed data structure');
} catch (error) {
  console.error('Invalid:', error.message);
}
```

Validation checks:

* Primary type exists in types
* All referenced types are defined
* Message fields match type definitions
* No circular type references

## Error Handling

```typescript theme={null}
import * as EIP712 from '@voltaire/crypto/eip712';
import {
  Eip712Error,
  Eip712TypeNotFoundError,
  Eip712InvalidMessageError,
  Eip712EncodingError
} from '@voltaire/crypto/eip712';

try {
  const hash = EIP712.hashTypedData(typedData);
} catch (error) {
  if (error instanceof Eip712TypeNotFoundError) {
    // Referenced type not in types object
  } else if (error instanceof Eip712InvalidMessageError) {
    // Message doesn't match type definition
  } else if (error instanceof Eip712EncodingError) {
    // Value encoding failed (wrong type, size, etc.)
  }
}
```

## Security Best Practices

1. **Always include deadlines** - Prevent signature reuse after expiry
2. **Use nonces** - Contract tracks nonces to prevent replay
3. **Validate before signing** - Call `validate()` on user-provided data
4. **Verify recovered address** - Never trust signatures without verification
5. **Include chainId** - Prevents cross-chain replay attacks

```typescript theme={null}
// Good: Time-bounded with nonce
message: {
  action: 'approve',
  nonce: await contract.nonces(userAddress),
  deadline: BigInt(Math.floor(Date.now() / 1000) + 3600) // 1 hour
}
```

## API Reference

| Function                                         | Description                              |
| ------------------------------------------------ | ---------------------------------------- |
| `hashTypedData(typedData)`                       | Hash complete typed data for signing     |
| `signTypedData(typedData, privateKey)`           | Sign typed data, returns `{r, s, v}`     |
| `verifyTypedData(signature, typedData, address)` | Verify signature matches expected signer |
| `recoverAddress(signature, typedData)`           | Recover signer address from signature    |
| `Domain.hash(domain)`                            | Hash domain separator                    |
| `encodeType(primaryType, types)`                 | Get canonical type encoding string       |
| `hashType(primaryType, types)`                   | Hash type encoding                       |
| `hashStruct(primaryType, message, types)`        | Hash struct data                         |
| `encodeValue(type, value, types)`                | Encode single value                      |
| `validate(typedData)`                            | Validate typed data structure            |
| `format(typedData)`                              | Format for human-readable display        |

## Related

* [EIP-712 Specification](https://eips.ethereum.org/EIPS/eip-712)
* [ERC-2612 Permit](https://eips.ethereum.org/EIPS/eip-2612)
* [Secp256k1 Signing](/crypto/secp256k1)
* [Keccak256 Hashing](/crypto/keccak256)
