# `Ethers.TypedData`
[🔗](https://github.com/ExWeb3/elixir_ethers/blob/v0.8.0/lib/ethers/typed_data.ex#L1)

Models an [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed structured data payload.

An `Ethers.TypedData` mirrors the canonical `eth_signTypedData_v4` JSON shape so the same
struct serves both hashing (the local signing pipeline) and JSON-RPC signing. It is built from:

- `types` - a map of `type_name` (`String.t()`) to an ordered list of
  `Ethers.TypedData.Field` structs describing that struct's members. The synthetic
  `"EIP712Domain"` entry must **not** be included here - it is derived from the `domain`.
- `primary_type` - the name (`String.t()`) of the top-level struct being signed. Must be a key
  of `types`.
- `message` - a map of member name to value. Keys are normalized to strings internally.
- `domain` - an `Ethers.TypedData.Domain` struct scoping the signature.

## Example (the canonical Mail/Person example)

The following builds the `Mail` message from the EIP-712 specification and reproduces the
reference `encodeType` string, domain separator and signing digest published by the spec:

    iex> typed_data =
    ...>   Ethers.TypedData.new!(
    ...>     types: %{
    ...>       "Person" => [
    ...>         %{name: "name", type: "string"},
    ...>         %{name: "wallet", type: "address"}
    ...>       ],
    ...>       "Mail" => [
    ...>         %{name: "from", type: "Person"},
    ...>         %{name: "to", type: "Person"},
    ...>         %{name: "contents", type: "string"}
    ...>       ]
    ...>     },
    ...>     primary_type: "Mail",
    ...>     domain: [
    ...>       name: "Ether Mail",
    ...>       version: "1",
    ...>       chain_id: 1,
    ...>       verifying_contract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
    ...>     ],
    ...>     message: %{
    ...>       "from" => %{"name" => "Cow", "wallet" => "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},
    ...>       "to" => %{"name" => "Bob", "wallet" => "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},
    ...>       "contents" => "Hello, Bob!"
    ...>     }
    ...>   )
    iex> Ethers.TypedData.encode_type(typed_data, "Mail")
    "Mail(Person from,Person to,string contents)Person(string name,address wallet)"
    iex> Ethers.TypedData.hash(typed_data, :hex)
    "0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"
    iex> Ethers.Utils.hex_encode(Ethers.TypedData.domain_separator(typed_data))
    "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"

# `t`

```elixir
@type t() :: %Ethers.TypedData{
  domain: Ethers.TypedData.Domain.t(),
  message: %{required(String.t()) =&gt; term()},
  primary_type: String.t(),
  types: %{required(String.t()) =&gt; [Ethers.TypedData.Field.t()]}
}
```

An EIP-712 typed-data payload.

- `domain` - the `Ethers.TypedData.Domain` scoping the signature.
- `types` - map of struct type name to ordered list of `Ethers.TypedData.Field` structs.
- `primary_type` - the top-level struct type name being signed.
- `message` - the message data as a map with string keys.

# `domain_separator`

```elixir
@spec domain_separator(t()) :: binary()
```

Returns the 32-byte EIP-712 `domainSeparator` (`hashStruct("EIP712Domain", domain)`).

# `encode_data`

```elixir
@spec encode_data(t(), String.t(), term()) :: binary()
```

Returns the EIP-712 `encodeData` bytes for `value` as type `type_name`
(`typeHash ‖ enc(memberᵢ)…`).

# `encode_type`

```elixir
@spec encode_type(t(), String.t()) :: String.t()
```

Returns the EIP-712 `encodeType` string for `type_name` (e.g.
`"Mail(Person from,Person to,string contents)Person(string name,address wallet)"`).

# `hash`

```elixir
@spec hash(t()) :: binary()
```

Returns the EIP-712 signing digest
`keccak256(0x19 ‖ 0x01 ‖ domainSeparator ‖ hashStruct(primaryType, message))`.

With `:bin` (default) returns the raw 32-byte binary; with `:hex` returns `0x`-prefixed hex.

# `hash`

```elixir
@spec hash(t(), :bin | :hex) :: binary()
```

Returns the EIP-712 signing digest of `typed_data` in the requested `format`.

With `:bin` returns the raw 32-byte binary; with `:hex` returns a `0x`-prefixed hex string.
See `hash/1` for the digest definition.

# `hash_struct`

```elixir
@spec hash_struct(t(), String.t(), term()) :: binary()
```

Returns the 32-byte `hashStruct(type_name, value)` = `keccak256(encodeData(type_name, value))`.

# `new`

```elixir
@spec new(struct()) :: {:ok, t()} | {:error, term()}
@spec new(keyword() | map()) :: {:ok, t()} | {:error, term()}
```

Builds an `Ethers.TypedData` from the given parameters, normalizing and validating them.

## Parameters (keyword list or map)

- `:types` - a map of type name to a list of field definitions. Each field may be a
  `%Ethers.TypedData.Field{}`, a map with atom keys (`%{name: ..., type: ...}`), or a map with
  string keys (`%{"name" => ..., "type" => ...}`). All are normalized to `Field` structs.
- `:primary_type` - the top-level struct type name. Must exist in `:types`.
- `:domain` - an `Ethers.TypedData.Domain` struct, or a keyword/map accepted by
  `Ethers.TypedData.Domain.new/1`.
- `:message` - a map of member name to value. Both atom and string keys are accepted and
  normalized to string keys.

## Validation

Returns `{:error, reason}` when:

- `:primary_type` is not a key of `:types` (`{:error, {:unknown_primary_type, name}}`).
- a member references a struct type that is not defined in `:types`
  (`{:error, {:undefined_type, name}}`).

Otherwise returns `{:ok, %Ethers.TypedData{}}`.

# `new`

```elixir
@spec new(struct(), keyword()) :: {:ok, t()} | {:error, term()}
```

Builds an `Ethers.TypedData` from a schema struct instance (see `Ethers.TypedData.Schema`).

Declare the EIP-712 struct types as modules with `use Ethers.TypedData.Schema`, then pass an
instance of the top-level struct. The struct is expanded into `types`/`primary_type`/`message`
(walking referenced schema modules transitively) and validated via `new/1`, so the result is
identical to the equivalent map-based `new/1` call.

`opts` may carry `:domain` (a keyword/map accepted by `Ethers.TypedData.Domain.new/1`).

## Example

    # given `Person`/`Mail` schema modules (see `Ethers.TypedData.Schema`)
    Ethers.TypedData.new!(
      %Mail{
        from: %Person{name: "Cow", wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},
        to: %Person{name: "Bob", wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},
        contents: "Hello, Bob!"
      },
      domain: [name: "Ether Mail", version: "1", chain_id: 1,
               verifying_contract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"]
    )

# `new!`

```elixir
@spec new!(struct()) :: t() | no_return()
@spec new!(keyword() | map()) :: t() | no_return()
```

Same as `new/1` but raises an `ArgumentError` on error.

# `new!`

```elixir
@spec new!(struct(), keyword()) :: t() | no_return()
```

Same as `new/2` but raises an `ArgumentError` on error.

# `recover_signer`

```elixir
@spec recover_signer(t(), binary()) :: Ethers.Types.t_address() | {:error, term()}
```

Recovers the signer address from an EIP-712 typed-data payload and its signature.

The signing digest is recomputed from `typed_data` (via `hash/1`) and the signer's public key
is recovered from the signature, then converted to its checksummed `0x` address.

`signature` may be given as a `0x`-prefixed hex string (130 hex chars) or a raw 65-byte binary
(`r ‖ s ‖ v`).

Recovery is `ecrecover`-based and therefore works for externally-owned accounts (EOAs)
only — smart-contract wallets (ERC-1271/ERC-6492) do not have a recoverable signer.

## Returns

- a checksummed `0x` address string on success.
- `{:error, reason}` if the public key could not be recovered.

## Examples

```elixir
{:ok, sig} =
  Ethers.sign_typed_data(typed_data,
    signer: Ethers.Signer.Local,
    signer_opts: [private_key: key]
  )

Ethers.TypedData.recover_signer(typed_data, sig)
#=> "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
```

# `to_eip712_json`

```elixir
@spec to_eip712_json(t()) :: %{required(String.t()) =&gt; term()}
```

Serializes an `Ethers.TypedData` into the canonical `eth_signTypedData_v4` JSON-compatible map.

The returned map uses string keys and JSON-ready scalar values so it can be encoded with
`Jason.encode!/1` and handed to a wallet / node. Its shape is:

    %{
      "types" => %{
        "EIP712Domain" => [%{"name" => ..., "type" => ...}, ...],
        <primary type and every referenced type> => [%{"name" => ..., "type" => ...}, ...]
      },
      "primaryType" => "Mail",
      "domain" => %{...present domain fields...},
      "message" => %{...}
    }

## Value serialization

- `address` - `0x` checksummed hex (accepts a `0x`-hex string or a 20-byte binary).
- `bytesN` / `bytes` - `0x` hex (accepts a `0x`-hex string or a binary).
- `uintN` / `intN` - decimal string (accepts an integer, a decimal string, or a `0x`-hex
  string). Always serialized as a decimal string for full `uint256`-range interoperability.
- `bool` / `string` - passed through unchanged.
- reference struct types - recursed into as nested maps.
- array types (`T[]` / `T[n]`, including nested arrays) - serialized as lists element by element.

The `"EIP712Domain"` entry is derived from the domain's present (non-nil) fields in canonical
order; the caller must not include it in `types`.

# `type_hash`

```elixir
@spec type_hash(t(), String.t()) :: binary()
```

Returns the 32-byte `typeHash` (`keccak256(encodeType(type_name))`).

# `valid_signature?`

```elixir
@spec valid_signature?(t(), binary(), Ethers.Types.t_address()) :: boolean()
```

Checks whether `signature` over `typed_data` was produced by `expected_address`.

Recovers the signer via `recover_signer/2` and compares it to `expected_address`. The
comparison is done on the decoded 20-byte addresses, so checksum/case differences are ignored.

`signature` may be a `0x`-prefixed hex string or a raw 65-byte binary.

EOA-only — signatures from smart-contract wallets (ERC-1271/ERC-6492) always fail this
check. Prefer `Ethers.Signature.verify_typed_data/4`, which verifies EOA and
smart-contract wallet signatures alike; use this function when you specifically need a
pure, RPC-free check for signatures known to come from EOA keys.

## Examples

```elixir
Ethers.TypedData.valid_signature?(typed_data, sig, "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1")
#=> true
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
