diff --git a/lib/eip712/typed.ex b/lib/eip712/typed.ex index 8110bb5..0175f36 100644 --- a/lib/eip712/typed.ex +++ b/lib/eip712/typed.ex @@ -297,24 +297,42 @@ defmodule EIP712.Typed do defstruct [:name, :version, :chain_id, :verifying_contract] @type t() :: %__MODULE__{ - name: String.t(), - version: String.t(), - chain_id: number(), - verifying_contract: <<_::160>> + name: nil | String.t(), + version: nil | String.t(), + chain_id: nil | number(), + verifying_contract: nil | <<_::160>> } - def domain_type(), - do: %{ - "EIP712Domain" => %Type{ - fields: [ - {"name", :string}, - {"version", :string}, - {"chainId", {:uint, 256}}, - {"verifyingContract", :address} - # {"salt", {:bytes, 32}} - ] - } - } + @all_domain_fields [ + {:name, "name", :string}, + {:version, "version", :string}, + {:chain_id, "chainId", {:uint, 256}}, + {:verifying_contract, "verifyingContract", :address} + ] + + @doc """ + Returns the EIP712Domain type, including only fields that are non-nil + in the given domain. Per the EIP-712 spec, optional domain fields + (like `verifyingContract` or `salt`) should be omitted from the type + hash when not present. + + When called without arguments, includes all fields (backwards compatible). + """ + def domain_type(domain \\ nil) + + def domain_type(nil) do + fields = for {_key, eip_name, type} <- @all_domain_fields, do: {eip_name, type} + %{"EIP712Domain" => %Type{fields: fields}} + end + + def domain_type(%__MODULE__{} = domain) do + fields = + for {key, eip_name, type} <- @all_domain_fields, + not is_nil(Map.get(domain, key)), + do: {eip_name, type} + + %{"EIP712Domain" => %Type{fields: fields}} + end @doc ~S""" Deserializes a domain from JSON or JavaScript encoding to a struct. @@ -416,18 +434,13 @@ defmodule EIP712.Typed do "verifyingContract" => <<204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204>> } """ - def serialize_keys(%__MODULE__{ - name: name, - version: version, - chain_id: chain_id, - verifying_contract: verifying_contract - }) do - %{ - "name" => name, - "version" => version, - "chainId" => chain_id, - "verifyingContract" => verifying_contract - } + def serialize_keys(%__MODULE__{} = domain) do + for {key, eip_name, _type} <- @all_domain_fields, + value = Map.get(domain, key), + not is_nil(value), + into: %{} do + {eip_name, value} + end end end @@ -798,7 +811,7 @@ defmodule EIP712.Typed do """ @spec domain_seperator(t()) :: binary() def domain_seperator(%__MODULE__{domain: domain}) do - hash_struct("EIP712Domain", Domain.serialize_keys(domain), Domain.domain_type()) + hash_struct("EIP712Domain", Domain.serialize_keys(domain), Domain.domain_type(domain)) end @doc """