EIP712.Typed (eip712 v0.2.0)

Module to build EIP-712 typed data, which can then be signed or recovered from.

Summary

Functions

Deserializes a Typed value from JSON or a map into a struct.

Builds a domain struct for a given type, per the EIP-712 spec.

Encodes a given typed value such that it can be signed or recovered.

Encodes the struct type per EIP-712. For this, we basically build an ABI-style value like Mail(Person from,Person to,string contents), but then to that we need to append any other types we've seen, like

Hashes a struct value, per the EIP-712 spec.

Serializes a Typed value, such that it can be passed to JSON or JavaScript.

Types

@type t() :: %EIP712.Typed{domain: Domain.t(), types: type_map(), value: value_map()}
@type type_map() :: %{required(String.t()) => Type.t()}
@type value_map() :: %{required(String.t()) => term()}

Functions

Link to this function

deserialize(map)

@spec deserialize(%{}) :: t()

Deserializes a Typed value from JSON or a map into a struct.

Examples

iex> %{
...>   "domain" => %{
...>     "name" => "Ether Mail",
...>     "version" => "1",
...>     "chainId" => 1,
...>     "verifyingContract" => "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
...>   },
...>   "types" => %{
...>     "Person" => [
...>       %{
...>         "name" => "name",
...>         "type" => "string"
...>       },
...>       %{
...>         "name" => "wallet",
...>         "type" => "address"
...>       },
...>     ],
...>     "Mail" => [
...>       %{
...>         "name" => "from",
...>         "type" => "Person"
...>       },
...>       %{
...>         "name" => "to",
...>         "type" => "Person"
...>       },
...>       %{
...>         "name" => "contents",
...>         "type" => "string"
...>       },
...>     ]
...>   },
...>   "value" => %{
...>     "from" => %{ "name" => "Cow", "wallet" => "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" },
...>     "to" => %{ "name" => "Bob", "wallet" => "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" },
...>     "contents" => "Hello, Bob!"
...>   }
...> }
...> |> EIP712.Typed.deserialize()
%EIP712.Typed{
  domain: %EIP712.Typed.Domain{
    chain_id: 1,
    name: "Ether Mail",
    verifying_contract: "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC",
    version: "1"
  },
  types: %{
    "Mail" => %EIP712.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
    "Person" => %EIP712.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
  },
  value: %{
    "contents" => "Hello, Bob!",
    "from" => %{
      "name" => "Cow",
      "wallet" => <<205, 42, 61, 159, 147, 142, 19, 205, 148, 126, 192, 90, 188, 127, 231, 52, 223, 141, 216, 38>>
    },
    "to" => %{
      "name" => "Bob",
      "wallet" =>
        <<187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187>>
    }
  }
}
Link to this function

domain_seperator(typed)

@spec domain_seperator(t()) :: binary()

Builds a domain struct for a given type, per the EIP-712 spec.

Examples

iex> %EIP712.Typed{
...>   domain: %EIP712.Typed.Domain{
...>     chain_id: 1,
...>     name: "Ether Mail",
...>     verifying_contract: EIP712.Util.decode_hex!("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"),
...>     version: "1"
...>   },
...>   types: %{
...>     "Mail" => %EIP712.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>     "Person" => %EIP712.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...>   },
...>   value: %{
...>     "contents" => "Hello, Bob!",
...>     "from" => %{
...>       "name" => "Cow",
...>       "wallet" => EIP712.Util.decode_hex!("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826")
...>     },
...>     "to" => %{
...>       "name" => "Bob",
...>       "wallet" => EIP712.Util.decode_hex!("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB")
...>     }
...>   }
...> }
...> |> EIP712.Typed.domain_seperator()
...> |> EIP712.Util.encode_hex()
"0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"
@spec encode(t()) :: binary()

Encodes a given typed value such that it can be signed or recovered.

Examples

iex> %EIP712.Typed{
...>   domain: %EIP712.Typed.Domain{
...>     chain_id: 1,
...>     name: "Ether Mail",
...>     verifying_contract: EIP712.Util.decode_hex!("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"),
...>     version: "1"
...>   },
...>   types: %{
...>     "Mail" => %EIP712.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>     "Person" => %EIP712.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...>   },
...>   value: %{
...>     "contents" => "Hello, Bob!",
...>     "from" => %{
...>       "name" => "Cow",
...>       "wallet" => EIP712.Util.decode_hex!("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826")
...>     },
...>     "to" => %{
...>       "name" => "Bob",
...>       "wallet" => EIP712.Util.decode_hex!("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB")
...>     }
...>   }
...> }
...> |> EIP712.Typed.encode()
...> |> EIP712.Util.encode_hex()
"0x1901f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090fc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"

iex> %EIP712.Typed{
...>   domain: %EIP712.Typed.Domain{
...>     chain_id: 1,
...>     name: "Test",
...>     verifying_contract: EIP712.Util.decode_hex!("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"),
...>     version: "1"
...>   },
...>   types: %{
...>     "Test" => %EIP712.Typed.Type{fields: [{"items", {:array, :string}}]}
...>   },
...>   value: %{
...>     "items" => ["item1", "item2"]
...>   }
...> }
...> |> EIP712.Typed.encode()
...> |> EIP712.Hash.keccak()
...> |> EIP712.Util.encode_hex()
"0xb5a8bfaa80915247e3a8709778e0e57da2dd5c1c04e07fc4b086d7e4bc9c1715"
Link to this function

encode_type(name, types)

@spec encode_type(String.t(), type_map()) :: String.t()

Encodes the struct type per EIP-712. For this, we basically build an ABI-style value like Mail(Person from,Person to,string contents), but then to that we need to append any other types we've seen, like:

Mail(Person from,Person to,string contents)Person(string name,address wallet).

This is a tail-call optimized implementation to build the types then track and append types that need to be added.

Examples

iex> EIP712.Typed.encode_type("Mail", %{
...>   "Mail" => %EIP712.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>   "Person" => %EIP712.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...> })
"Mail(Person from,Person to,string contents)Person(string name,address wallet)"
Link to this function

hash_struct(name, value, types)

@spec hash_struct(String.t(), value_map(), type_map()) :: binary()

Hashes a struct value, per the EIP-712 spec.

Examples

iex> types = %{
...>   "Mail" => %EIP712.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>   "Person" => %EIP712.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...> }
...> value = %{
...>   "contents" => "Hello, Bob!",
...>   "from" => %{
...>     "name" => "Cow",
...>     "wallet" => EIP712.Util.decode_hex!("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826")
...>   },
...>   "to" => %{
...>     "name" => "Bob",
...>     "wallet" => EIP712.Util.decode_hex!("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB")
...>   }
...> }
...> EIP712.Typed.hash_struct("Mail", value, types)
...> |> EIP712.Util.encode_hex()
"0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"
Link to this function

serialize(typed)

@spec serialize(t()) :: %{}

Serializes a Typed value, such that it can be passed to JSON or JavaScript.

Examples

iex> %EIP712.Typed{
...>   domain: %EIP712.Typed.Domain{
...>     chain_id: 1,
...>     name: "Ether Mail",
...>     verifying_contract: "\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC",
...>     version: "1"
...>   },
...>   types: %{
...>     "Mail" => %EIP712.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>     "Person" => %EIP712.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...>   },
...>   value: %{
...>     "contents" => "Hello, Bob!",
...>     "from" => %{
...>       "name" => "Cow",
...>       "wallet" => <<205, 42, 61, 159, 147, 142, 19, 205, 148, 126, 192, 90, 188, 127, 231, 52, 223, 141, 216, 38>>
...>     },
...>     "to" => %{
...>       "name" => "Bob",
...>       "wallet" =>
...>         <<187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187>>
...>     }
...>   }
...> }
...> |> EIP712.Typed.serialize()
%{
  "domain" => %{
    "name" => "Ether Mail",
    "version" => "1",
    "chainId" => 1,
    "verifyingContract" => "0xcccccccccccccccccccccccccccccccccccccccc"
  },
  "types" => %{
    "Person" => [
      %{
        "name" => "name",
        "type" => "string"
      },
      %{
        "name" => "wallet",
        "type" => "address"
      },
    ],
    "Mail" => [
      %{
        "name" => "from",
        "type" => "Person"
      },
      %{
        "name" => "to",
        "type" => "Person"
      },
      %{
        "name" => "contents",
        "type" => "string"
      },
    ]
  },
  "value" => %{
    "from" => %{ "name" => "Cow", "wallet" => "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826" },
    "to" => %{ "name" => "Bob", "wallet" => "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },
    "contents" => "Hello, Bob!"
  }
}