package verifyer import ( "bytes" "encoding/hex" "fmt" "io" "net/http" "strconv" "strings" "time" "slices" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" ) // esplora is a client for the Esplora API, which provides access to Bitcoin // blockchain data. type esplora struct { httpClient *http.Client baseURL string } func NewEsploraClient(url string, timeout time.Duration) Bitcoin { url = strings.TrimSuffix(url, "/") h := &http.Client{ Timeout: timeout, } return esplora{h, url} } func (e esplora) GetBlockHash(height int64) (*chainhash.Hash, error) { resp, err := e.httpClient.Get(e.baseURL + "/block-height/" + strconv.FormatInt(height, 10)) if err != nil { return nil, fmt.Errorf("failed to get block hash: %w", err) } defer resp.Body.Close() hexb, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read block hash: %w", err) } hash, err := hex.DecodeString(string(hexb)) if err != nil { return nil, fmt.Errorf("failed to decode block hash: %w", err) } if len(hash) != chainhash.HashSize { return nil, fmt.Errorf("got block hash (%x) of invalid size (expected %d)", hash, chainhash.HashSize) } slices.Reverse(hash) var chash chainhash.Hash copy(chash[:], hash) return &chash, nil } func (e esplora) GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error) { resp, err := e.httpClient.Get(fmt.Sprintf("%s/block/%s/header", e.baseURL, hash.String())) if err != nil { return nil, err } defer resp.Body.Close() hexb, err := io.ReadAll(resp.Body) if err != nil { return nil, err } headerHash, err := hex.DecodeString(string(hexb)) if err != nil { return nil, err } header := &wire.BlockHeader{} if err := header.BtcDecode(bytes.NewBuffer(headerHash), 0, 0); err != nil { return nil, err } return header, nil }