github.com/status-im/status-go@v1.1.0/services/wallet/thirdparty/fourbyte/client.go (about) 1 package fourbyte 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "regexp" 10 "sort" 11 "strings" 12 "time" 13 14 "github.com/ethereum/go-ethereum/accounts/abi" 15 "github.com/status-im/status-go/services/wallet/thirdparty" 16 ) 17 18 type Signature struct { 19 ID int `json:"id"` 20 Text string `json:"text_signature"` 21 } 22 23 type ByID []Signature 24 25 func (s ByID) Len() int { return len(s) } 26 func (s ByID) Less(i, j int) bool { return s[i].ID > s[j].ID } 27 func (s ByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 28 29 type SignatureList struct { 30 Count int `json:"count"` 31 Results []Signature `json:"results"` 32 } 33 34 type Client struct { 35 Client *http.Client 36 URL string 37 } 38 39 func NewClient() *Client { 40 return &Client{Client: &http.Client{Timeout: time.Minute}, URL: "https://www.4byte.directory"} 41 } 42 43 func (c *Client) DoQuery(url string) (*http.Response, error) { 44 resp, err := c.Client.Get(url) 45 46 if err != nil { 47 return nil, err 48 } 49 return resp, nil 50 } 51 52 func (c *Client) Run(data string) (*thirdparty.DataParsed, error) { 53 if len(data) < 10 || !strings.HasPrefix(data, "0x") { 54 return nil, errors.New("input is badly formatted") 55 } 56 methodSigData := data[2:10] 57 url := fmt.Sprintf("%s/api/v1/signatures/?hex_signature=%s", c.URL, methodSigData) 58 resp, err := c.DoQuery(url) 59 if err != nil { 60 return nil, err 61 } 62 defer resp.Body.Close() 63 64 body, err := ioutil.ReadAll(resp.Body) 65 if err != nil { 66 return nil, err 67 } 68 var signatures SignatureList 69 err = json.Unmarshal(body, &signatures) 70 if err != nil { 71 return nil, err 72 } 73 if signatures.Count == 0 { 74 return nil, err 75 } 76 rgx := regexp.MustCompile(`\((.*?)\)`) 77 results := signatures.Results 78 sort.Sort(ByID(results)) 79 for _, signature := range results { 80 id := fmt.Sprintf("0x%x", signature.ID) 81 name := strings.Split(signature.Text, "(")[0] 82 rs := rgx.FindStringSubmatch(signature.Text) 83 inputsMapString := make(map[string]string) 84 if len(rs[1]) > 0 { 85 inputs := make([]string, 0) 86 rawInputs := strings.Split(rs[1], ",") 87 for index, typ := range rawInputs { 88 if index == len(rawInputs)-1 && typ == "bytes" { 89 continue 90 } 91 inputs = append(inputs, fmt.Sprintf("{\"name\":\"%d\",\"type\":\"%s\"}", index, typ)) 92 } 93 functionABI := fmt.Sprintf("[{\"constant\":true,\"inputs\":[%s],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\", \"name\": \"%s\"}], ", strings.Join(inputs, ","), name) 94 contractABI, err := abi.JSON(strings.NewReader(functionABI)) 95 if err != nil { 96 continue 97 } 98 method := contractABI.Methods[name] 99 inputsMap := make(map[string]interface{}) 100 if err := method.Inputs.UnpackIntoMap(inputsMap, []byte(data[10:])); err != nil { 101 continue 102 } 103 104 for key, value := range inputsMap { 105 inputsMapString[key] = fmt.Sprintf("%v", value) 106 } 107 } 108 109 return &thirdparty.DataParsed{ 110 Name: name, 111 ID: id, 112 Signature: signature.Text, 113 Inputs: inputsMapString, 114 }, nil 115 } 116 117 return nil, errors.New("couldn't find a corresponding signature") 118 }