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_map()
@type type_map() :: %{required(String.t()) => Type.t()}
value_map()
Functions
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>>
}
}
}
domain_seperator(typed)
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"
encode(typed)
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"
encode_type(name, types)
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)"
hash_struct(name, value, types)
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"
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!"
}
}