From 5f0bd1e1feefb2f2b323fa6ae727e71e0638660d Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Mon, 16 Dec 2024 18:46:18 -0500 Subject: [PATCH] ethcoder: auto-detect primaryType if unspecified (#151) --- ethcoder/typed_data.go | 52 ++++++++++++++++++++++++++++++++++++- ethcoder/typed_data_test.go | 6 +++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/ethcoder/typed_data.go b/ethcoder/typed_data.go index 5cca8c3..9551b55 100644 --- a/ethcoder/typed_data.go +++ b/ethcoder/typed_data.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math/big" + "slices" "sort" "strings" @@ -320,7 +321,12 @@ func (t *TypedData) UnmarshalJSON(data []byte) error { // Ensure primary type is defined if raw.PrimaryType == "" { - return fmt.Errorf("primary type is required") + // detect primary type if its unspecified + primaryType, err := typedDataDetectPrimaryType(raw.Types.Map(), raw.Message) + if err != nil { + return err + } + raw.PrimaryType = primaryType } _, ok = raw.Types[raw.PrimaryType] if !ok { @@ -346,6 +352,50 @@ func (t *TypedData) UnmarshalJSON(data []byte) error { return nil } +func typedDataDetectPrimaryType(typesMap map[string]map[string]string, message map[string]interface{}) (string, error) { + // If there are only two types, and one is the EIP712Domain, then the other is the primary type + if len(typesMap) == 2 { + _, ok := typesMap["EIP712Domain"] + if ok { + for typ := range typesMap { + if typ == "EIP712Domain" { + continue + } + return typ, nil + } + } + } + + // Otherwise search for the primary type by looking for the first type that has a message field keys + messageKeys := []string{} + for k := range message { + messageKeys = append(messageKeys, k) + } + sort.Strings(messageKeys) + + for typ := range typesMap { + if typ == "EIP712Domain" { + continue + } + if len(typesMap[typ]) != len(messageKeys) { + continue + } + + typKeys := []string{} + for k := range typesMap[typ] { + typKeys = append(typKeys, k) + } + sort.Strings(typKeys) + + if !slices.Equal(messageKeys, typKeys) { + continue + } + return typ, nil + } + + return "", fmt.Errorf("no primary type found") +} + func typedDataDecodeRawMessageMap(typesMap map[string]map[string]string, primaryType string, data interface{}) (interface{}, error) { // Handle array types if arr, ok := data.([]interface{}); ok { diff --git a/ethcoder/typed_data_test.go b/ethcoder/typed_data_test.go index 44492cd..28eee59 100644 --- a/ethcoder/typed_data_test.go +++ b/ethcoder/typed_data_test.go @@ -310,9 +310,12 @@ func TestTypedDataFromJSONPart3(t *testing.T) { {"name": "count", "type": "uint8"}, {"name": "data", "type": "bytes"}, {"name": "hash", "type": "bytes32"} + ], + "Blah": [ + {"name": "name", "type": "string"}, + {"name": "another", "type": "address"} ] }, - "primaryType": "Person", "domain": { "name": "Ether Mail", "version": "1", @@ -379,7 +382,6 @@ func TestTypedDataFromJSONPart4(t *testing.T) { "verifyingContract": "0xc0ffee254729296a45a3885639AC7E10F9d54979", "salt": "0x70736575646f2d72616e646f6d2076616c756500000000000000000000000000" }, - "primaryType": "ExampleMessage", "message": { "message": "Test message", "value": 10000,