Live video on the AT Protocol
1package schema
2
3import (
4 "encoding/json"
5 "fmt"
6 "reflect"
7
8 "github.com/ethereum/go-ethereum/signer/core/apitypes"
9)
10
11type Schema interface {
12 EIP712() (*EIP712SchemaStruct, error)
13}
14
15type SchemaStruct struct {
16 name string
17 version string
18 schema any
19}
20
21func MakeSchema(name, version string, schema any) (Schema, error) {
22 return &SchemaStruct{
23 name: name,
24 version: version,
25 schema: schema,
26 }, nil
27}
28
29type EIP712SchemaStruct struct {
30 Types apitypes.Types `json:"types"`
31 Domain *apitypes.TypedDataDomain `json:"domain"`
32 TypeToName map[reflect.Type]string
33 NameToType map[string]reflect.Type
34}
35
36func (schema *SchemaStruct) EIP712() (*EIP712SchemaStruct, error) {
37 var eip712Types = apitypes.Types{
38 "EIP712Domain": {
39 {
40 Name: "name",
41 Type: "string",
42 },
43 {
44 Name: "version",
45 Type: "string",
46 },
47 },
48 }
49
50 stype := reflect.TypeOf(schema.schema)
51 if stype.Kind() != reflect.Struct {
52 return nil, fmt.Errorf("schema parameter of MakeEIP712Signer is not a struct")
53 }
54 fields := reflect.VisibleFields(stype)
55 typeToName := map[reflect.Type]string{}
56 nameToType := map[string]reflect.Type{}
57 for _, field := range fields {
58 name := field.Name
59 eip712TypeName := fmt.Sprintf("%sData", name)
60 if field.Type.Kind() != reflect.Struct {
61 return nil, fmt.Errorf("field '%s' in provided schema is not a struct", name)
62 }
63 typeToName[field.Type] = name
64 nameToType[name] = field.Type
65 parentType := []apitypes.Type{
66 {
67 Name: "signer",
68 Type: "address",
69 },
70 {
71 Name: "time",
72 Type: "int64",
73 },
74 {
75 Name: "data",
76 Type: eip712TypeName,
77 },
78 }
79 typeSlice := []apitypes.Type{}
80
81 subfields := reflect.VisibleFields(field.Type)
82 for _, subfield := range subfields {
83 eipType, err := goToEIP712(subfield)
84 if err != nil {
85 return nil, fmt.Errorf("error handling type %s: %w", name, err)
86 }
87 typeSlice = append(typeSlice, eipType)
88 }
89 eip712Types[name] = parentType
90 eip712Types[eip712TypeName] = typeSlice
91 }
92 return &EIP712SchemaStruct{
93 Types: eip712Types,
94 Domain: &apitypes.TypedDataDomain{
95 Version: schema.version,
96 Name: schema.name,
97 },
98 TypeToName: typeToName,
99 NameToType: nameToType,
100 }, nil
101}
102
103func (eip *EIP712SchemaStruct) JSON() ([]byte, error) {
104 out := map[string]any{
105 "domain": eip.Domain,
106 "types": eip.Types,
107 }
108 bs, err := json.MarshalIndent(out, "", " ")
109 if err != nil {
110 return []byte{}, err
111 }
112 return bs, nil
113}
114
115// turns a go type into an eip712 type
116func goToEIP712(field reflect.StructField) (apitypes.Type, error) {
117 var typ string
118 kind := field.Type.Kind()
119 if kind == reflect.String {
120 typ = "string"
121 } else if kind == reflect.Int64 {
122 typ = "int64"
123 }
124 jsonTag := field.Tag.Get("json")
125 if jsonTag == "" {
126 return apitypes.Type{}, fmt.Errorf("could not find field name for %s", field.Name)
127 }
128 return apitypes.Type{
129 Name: jsonTag,
130 Type: typ,
131 }, nil
132}