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  }