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. The primary type is encoded first as
Name(type1 field1,type2 field2,...). Referenced struct types are then
collected transitively, sorted by name, and appended, e.g.
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() :: %Cartouche.Typed{ domain: Cartouche.Typed.Domain.t(), types: type_map(), value: value_map() }
@type type_map() :: %{required(String.t()) => Cartouche.Typed.Type.t()}
Functions
@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!"
...> }
...> }
...> |> Cartouche.Typed.deserialize()
%Cartouche.Typed{
domain: %Cartouche.Typed.Domain{
chain_id: 1,
name: "Ether Mail",
verifying_contract: ~h[CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC],
version: "1"
},
types: %{
"Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
"Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
},
value: %{
"contents" => "Hello, Bob!",
"from" => %{
"name" => "Cow",
"wallet" => ~h[CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826]
},
"to" => %{
"name" => "Bob",
"wallet" => ~h[BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB]
}
}
}
Builds a domain struct for a given type, per the EIP-712 spec.
Examples
iex> %Cartouche.Typed{
...> domain: %Cartouche.Typed.Domain{
...> chain_id: 1,
...> name: "Ether Mail",
...> verifying_contract: ~h[0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC],
...> version: "1"
...> },
...> types: %{
...> "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...> "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...> },
...> value: %{
...> "contents" => "Hello, Bob!",
...> "from" => %{
...> "name" => "Cow",
...> "wallet" => ~h[0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826]
...> },
...> "to" => %{
...> "name" => "Bob",
...> "wallet" => ~h[0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]
...> }
...> }
...> }
...> |> Cartouche.Typed.domain_seperator()
...> |> to_hex()
"0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"
Encodes a given typed value such that it can be signed or recovered.
Examples
iex> %Cartouche.Typed{
...> domain: %Cartouche.Typed.Domain{
...> chain_id: 1,
...> name: "Ether Mail",
...> verifying_contract: ~h[0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC],
...> version: "1"
...> },
...> types: %{
...> "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...> "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...> },
...> value: %{
...> "contents" => "Hello, Bob!",
...> "from" => %{
...> "name" => "Cow",
...> "wallet" => ~h[0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826]
...> },
...> "to" => %{
...> "name" => "Bob",
...> "wallet" => ~h[0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]
...> }
...> }
...> }
...> |> Cartouche.Typed.encode()
...> |> to_hex()
"0x1901f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090fc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"
iex> %Cartouche.Typed{
...> domain: %Cartouche.Typed.Domain{
...> chain_id: 1,
...> name: "Complex Array",
...> verifying_contract: ~h[0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC],
...> version: "1"
...> },
...> types: %{
...> "Array" => %Cartouche.Typed.Type{fields: [{"a", {:uint, 256}}, {"b", {:uint, 256}}, {"c", :string}, {"d", {:array, :bytes}}]}
...> },
...> value: %{
...> "a" => 55,
...> "b" => 66,
...> "c" => "Hello",
...> "d" => [<<0x11, 0x22>>, <<0x33, 0x44>>]
...> }
...> }
...> |> Cartouche.Typed.encode()
...> |> to_hex()
"0x190103bd1627b4c5f7540c63d7ee347524dcef247eed29c833dd3b1455b8dec4009fcc95538bfc3f979ca59d9ef7de5ed402a4e403857b3de87d1fc8ed4a2a7cddd9"
iex> %Cartouche.Typed{
...> domain: %Cartouche.Typed.Domain{
...> name: "Complex Array",
...> version: "1"
...> },
...> types: %{
...> "Array" => %Cartouche.Typed.Type{fields: [{"a", {:uint, 256}}, {"b", {:uint, 256}}, {"c", :string}, {"d", {:array, :bytes}}]}
...> },
...> value: %{
...> "a" => 55,
...> "b" => 66,
...> "c" => "Hello",
...> "d" => [<<0x11, 0x22>>, <<0x33, 0x44>>]
...> }
...> }
...> |> Cartouche.Typed.encode()
...> |> to_hex()
"0x1901f4806c1a9dae718712eca4906bfca239a3a4a6dea2e9b9a1284fee5ff4df4b1ccc95538bfc3f979ca59d9ef7de5ed402a4e403857b3de87d1fc8ed4a2a7cddd9"
iex> %Cartouche.Typed{
...> domain: %Cartouche.Typed.Domain{
...> name: "Complex Array",
...> version: "1"
...> },
...> types: %{
...> "Array" => %Cartouche.Typed.Type{fields: [{"a", {:uint, 256}}, {"b", {:uint, 256}}, {"c", :string}, {"d", :bool}]}
...> },
...> value: %{
...> "a" => 55,
...> "b" => 66,
...> "c" => "Hello",
...> "d" => true
...> }
...> }
...> |> Cartouche.Typed.encode()
...> |> to_hex()
"0x1901f4806c1a9dae718712eca4906bfca239a3a4a6dea2e9b9a1284fee5ff4df4b1c8c56315a01fe3937526fe8c2b472b7e9e1c21728f6c14d5ffb0e0c156f74aca0"
Encodes the struct type per EIP-712. The primary type is encoded first as
Name(type1 field1,type2 field2,...). Referenced struct types are then
collected transitively, sorted by name, and appended, e.g.
Mail(Person from,Person to,string contents)Person(string name,address wallet).
Examples
iex> Cartouche.Typed.encode_type("Mail", %{
...> "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...> "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...> })
"Mail(Person from,Person to,string contents)Person(string name,address wallet)"
Hashes a struct value, per the EIP-712 spec.
Examples
iex> types = %{
...> "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...> "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...> }
...> value = %{
...> "contents" => "Hello, Bob!",
...> "from" => %{
...> "name" => "Cow",
...> "wallet" => ~h[0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826]
...> },
...> "to" => %{
...> "name" => "Bob",
...> "wallet" => ~h[0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]
...> }
...> }
iex> to_hex(Cartouche.Typed.hash_struct("Mail", value, types))
"0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"
@spec serialize(t()) :: %{}
Serializes a Typed value, such that it can be passed to JSON or JavaScript.
Examples
iex> %Cartouche.Typed{
...> domain: %Cartouche.Typed.Domain{
...> chain_id: 1,
...> name: "Ether Mail",
...> verifying_contract: ~h[CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC],
...> version: "1"
...> },
...> types: %{
...> "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...> "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...> },
...> value: %{
...> "contents" => "Hello, Bob!",
...> "from" => %{
...> "name" => "Cow",
...> "wallet" => ~h[CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826]
...> },
...> "to" => %{
...> "name" => "Bob",
...> "wallet" => ~h[BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB]
...> }
...> }
...> }
...> |> Cartouche.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!"
}
}