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

Compile-time DSL for declaring [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed-data
struct types as native Elixir modules.

Instead of hand-writing the `types`/`primary_type`/`message` maps that `Ethers.TypedData.new/1`
consumes, you declare each EIP-712 struct type as an Elixir module and build an
`Ethers.TypedData` straight from struct instances with `Ethers.TypedData.new/2` (or
`new!/2`). This is a thin front-end over the existing engine - the resulting payload hashes and
serializes identically to the map-based form.

## Usage

`use Ethers.TypedData.Schema` imports the `typed_schema/1,2` and `field/2,3` macros. A
`typed_schema` block declares an ordered list of fields; the macro generates a matching
`defstruct` plus an introspection function `__typed_data_schema__/0`.

    defmodule Person do
      use Ethers.TypedData.Schema

      typed_schema "Person" do
        field :name, :string
        field :wallet, :address
      end
    end

    defmodule Mail do
      use Ethers.TypedData.Schema

      typed_schema "Mail" do
        field :from, Person
        field :to, Person
        field :contents, :string
      end
    end

`typed_schema/1` derives the EIP-712 type name from the module's last segment
(`MyApp.Messages.Mail` -> `"Mail"`); `typed_schema/2` takes an explicit name string.

## Field types

The second argument to `field/2,3` is the member's type, in one of these forms:

- a **schema module** reference (`Person`) - another module that `use`s this DSL. The EIP-712
  type string is that schema's declared type name, and it is registered (recursively) in the
  generated `types` map.
- an **atom** (`:string`, `:address`, `:uint256`) - an atomic Solidity type; the type string is
  `Atom.to_string/1` of it.
- a **string** (`"uint256"`, `"bytes32"`) - a literal EIP-712 type string.
- `{:array, inner}` - a dynamic array of `inner` (`"<inner>[]"`).
- `{:array, inner, n}` - a fixed-size array of `inner` (`"<inner>[n]"`).

Field **order matters** - EIP-712 `encodeType` is order-sensitive, so the sequence of
`field/2,3` calls is the source of truth (which is why the block also generates the
`defstruct`). Type references (a schema module atom such as `Person`) are stored **unresolved**
and resolved at runtime by the expander, not at macro-expansion time - so mutually referencing
modules do not create compile-ordering problems.

## Building typed data from a schema

Given the `Person`/`Mail` schemas above, build and hash the canonical EIP-712 `Mail` message.
(These example modules ship in the test suite as `Ethers.Support.EIP712.Person` and
`Ethers.Support.EIP712.Mail`.)

    iex> alias Ethers.Support.EIP712.{Mail, Person}
    iex> mail = %Mail{
    ...>   from: %Person{name: "Cow", wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},
    ...>   to: %Person{name: "Bob", wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},
    ...>   contents: "Hello, Bob!"
    ...> }
    iex> td =
    ...>   Ethers.TypedData.new!(mail,
    ...>     domain: [
    ...>       name: "Ether Mail",
    ...>       version: "1",
    ...>       chain_id: 1,
    ...>       verifying_contract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
    ...>     ]
    ...>   )
    iex> Ethers.TypedData.encode_type(td, "Mail")
    "Mail(Person from,Person to,string contents)Person(string name,address wallet)"
    iex> Ethers.TypedData.hash(td, :hex)
    "0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"

## Introspection contract

The generated `__typed_data_schema__/0` returns:

    {type_name :: String.t(), [%{key: atom(), name: String.t(), type: term()}]}

where each `type` term is the raw declared type: a module atom (`Person`), an atom
(`:string`), a string (`"uint256"`), or `{:array, inner}` / `{:array, inner, n}`. The
per-field `:default` (used only for `defstruct`) is intentionally dropped from this tuple.

# `field`
*macro* 

Declares a single field of the enclosing `typed_schema`.

- `key` - the struct key (must be an atom). Declaring the same `key` twice raises.
- `type` - the raw, unresolved type term: a schema module atom (`Person`), an atom
  (`:string`, `:uint256`), a type string (`"uint256"`), or `{:array, inner}` /
  `{:array, inner, n}`.
- `opts`:
  - `:name` - the EIP-712 member name (defaults to `to_string(key)`).
  - `:default` - the `defstruct` default for this key (defaults to `nil`).

# `to_params`

```elixir
@spec to_params(struct(), keyword()) :: %{
  types: %{required(String.t()) =&gt; [Ethers.TypedData.Field.t()]},
  primary_type: String.t(),
  message: %{required(String.t()) =&gt; term()},
  domain: keyword() | map()
}
```

Expands a schema struct instance into the parameters consumed by `Ethers.TypedData.new/1`.

Walks the schema module referenced by `struct` transitively, resolving every referenced type
into the EIP-712 `types` map and building the string-keyed `message` from the struct instance.

Returns `%{types:, primary_type:, message:, domain:}` ready to hand to `Ethers.TypedData.new/1`.

Raises `ArgumentError` if `struct` is not a schema module (does not export
`__typed_data_schema__/0`), if a referenced type is not a schema module, or if two distinct
modules resolve to the same EIP-712 type name with different fields.

# `typed_schema`
*macro* 

Declares an EIP-712 typed-data schema.

`typed_schema/1` derives the EIP-712 type name from the module's last segment
(`MyApp.Messages.Mail` -> `"Mail"`). `typed_schema/2` takes an explicit type-name string.

The `do` block must contain one or more `field/2,3` declarations, in order. The macro emits a
`defstruct` and the `__typed_data_schema__/0` introspection function.

---

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