Загрузка данных


// Package abiselectors извлекает из ABI JSON контракта Ethereum
// тип, каноническую сигнатуру и селектор для function / event / error.
package abiselectors

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"

	"golang.org/x/crypto/sha3"
)

// Item — результат для одного элемента ABI.
type Item struct {
	Type      string `json:"type"`      // function | event | error
	Name      string `json:"name"`      // имя
	Signature string `json:"signature"` // каноническая сигнатура, напр. transfer(address,uint256)
	// Selector:
	//   function, error — первые 4 байта keccak256(signature), напр. 0xa9059cbb
	//   event           — полный keccak256(signature) (topic0, 32 байта);
	//                     для anonymous-событий topic0 не пишется в лог, но хеш всё равно считается
	Selector  string `json:"selector"`
	Anonymous bool   `json:"anonymous,omitempty"` // только для event
}

type abiParam struct {
	Name       string     `json:"name"`
	Type       string     `json:"type"`
	Components []abiParam `json:"components,omitempty"`
}

type abiEntry struct {
	Type      string     `json:"type"`
	Name      string     `json:"name"`
	Inputs    []abiParam `json:"inputs"`
	Anonymous bool       `json:"anonymous"`
}

// ExtractSelectors разбирает ABI JSON и возвращает элементы типов function, event, error.
// Остальные элементы (constructor, fallback, receive) пропускаются.
func ExtractSelectors(abiJSON []byte) ([]Item, error) {
	var entries []abiEntry
	if err := json.Unmarshal(abiJSON, &entries); err != nil {
		return nil, fmt.Errorf("parse abi json: %w", err)
	}

	items := make([]Item, 0, len(entries))
	for i, e := range entries {
		typ := e.Type
		if typ == "" { // по спецификации ABI тип по умолчанию — function
			typ = "function"
		}
		if typ != "function" && typ != "event" && typ != "error" {
			continue
		}
		if e.Name == "" {
			return nil, fmt.Errorf("entry %d (%s): empty name", i, typ)
		}

		sig, err := buildSignature(e.Name, e.Inputs)
		if err != nil {
			return nil, fmt.Errorf("entry %d (%s %s): %w", i, typ, e.Name, err)
		}

		hash := keccak256([]byte(sig))
		item := Item{Type: typ, Name: e.Name, Signature: sig}
		if typ == "event" {
			item.Selector = "0x" + hex.EncodeToString(hash)
			item.Anonymous = e.Anonymous
		} else {
			item.Selector = "0x" + hex.EncodeToString(hash[:4])
		}
		items = append(items, item)
	}
	return items, nil
}

func buildSignature(name string, inputs []abiParam) (string, error) {
	types := make([]string, len(inputs))
	for i, in := range inputs {
		t, err := canonicalType(in)
		if err != nil {
			return "", fmt.Errorf("input %d: %w", i, err)
		}
		types[i] = t
	}
	return name + "(" + strings.Join(types, ",") + ")", nil
}

// canonicalType приводит тип к каноническому виду:
// tuple → (t1,t2,...), tuple[] → (t1,...)[], uint → uint256 и т.д.
func canonicalType(p abiParam) (string, error) {
	if p.Type == "" {
		return "", fmt.Errorf("empty type")
	}
	if strings.HasPrefix(p.Type, "tuple") {
		suffix := strings.TrimPrefix(p.Type, "tuple") // "", "[]", "[2][]" ...
		if suffix != "" && !strings.HasPrefix(suffix, "[") {
			return "", fmt.Errorf("invalid tuple type %q", p.Type)
		}
		parts := make([]string, len(p.Components))
		for i, c := range p.Components {
			t, err := canonicalType(c)
			if err != nil {
				return "", fmt.Errorf("component %d: %w", i, err)
			}
			parts[i] = t
		}
		return "(" + strings.Join(parts, ",") + ")" + suffix, nil
	}

	base, suffix := p.Type, ""
	if idx := strings.IndexByte(p.Type, '['); idx >= 0 {
		base, suffix = p.Type[:idx], p.Type[idx:]
	}
	switch base {
	case "uint":
		base = "uint256"
	case "int":
		base = "int256"
	case "byte":
		base = "bytes1"
	case "fixed":
		base = "fixed128x18"
	case "ufixed":
		base = "ufixed128x18"
	}
	return base + suffix, nil
}

func keccak256(data []byte) []byte {
	h := sha3.NewLegacyKeccak256()
	h.Write(data)
	return h.Sum(nil)
}