Files
bitcoin-ticker-pi/mempool/mempool.go
T

172 lines
3.1 KiB
Go

package mempool
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
"github.com/gorilla/websocket"
)
var (
ErrNotWebsocket = errors.New("provided addr was not websocket")
)
const (
WSEndpoint = "api/v1/ws"
)
type mempool struct {
baseUrl string
httpClient *http.Client
ws *websocket.Conn
}
func New(baseUrl string, httpClient *http.Client) *mempool {
return &mempool{
baseUrl: baseUrl,
httpClient: &http.Client{
Timeout: 5 * time.Second,
},
}
}
func (m *mempool) Init() error {
u, err := url.Parse(fmt.Sprintf("wss://%s/%s", m.baseUrl, WSEndpoint))
if err != nil {
return err
}
if u.Scheme != "wss" && u.Scheme != "ws" {
return fmt.Errorf("%w: got %s", ErrNotWebsocket, u.Scheme)
}
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
return err
}
b, err := json.Marshal(struct {
Action string `json:"action"`
Data []string `json:"data"`
}{
Action: "want",
Data: []string{"blocks", "stats"},
})
if err != nil {
return err
}
if err = c.WriteMessage(websocket.BinaryMessage, b); err != nil {
return err
}
m.ws = c
return nil
}
func (m *mempool) CurrentBlockHeight() (int, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://%s/api/blocks/tip/height", m.baseUrl), nil)
if err != nil {
return 0, err
}
resp, err := m.httpClient.Do(req)
if err != nil {
return 0, err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusInternalServerError {
return 0, errors.New("server error")
}
if resp.StatusCode >= http.StatusBadRequest {
return 0, errors.New("client error")
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return 0, err
}
return strconv.Atoi(string(b))
}
func (m *mempool) CurrentFees() (int, int, int, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://%s/api/v1/fees/recommended", m.baseUrl), nil)
if err != nil {
return 0, 0, 0, err
}
resp, err := m.httpClient.Do(req)
if err != nil {
return 0, 0, 0, err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusInternalServerError {
return 0, 0, 0, err
}
if resp.StatusCode >= http.StatusBadRequest {
return 0, 0, 0, err
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return 0, 0, 0, err
}
var data struct {
Fastest int `json:"fastestFee"`
HalfHour int `json:"halfHourFee"`
Hour int `json:"hourFee"`
}
err = json.Unmarshal(b, &data)
return data.Fastest, data.HalfHour, data.Hour, err
}
func (m *mempool) Listen(ctx context.Context, messages chan Message, errs chan error, done chan struct{}) {
for {
select {
case <-ctx.Done():
close(messages)
close(errs)
close(done)
return
default:
_, message, err := m.ws.ReadMessage()
if err != nil {
errs <- err
continue
}
var m Message
err = json.Unmarshal(message, &m)
if err != nil {
errs <- err
continue
}
messages <- m
}
}
}
func (m *mempool) Shutdown(ctx context.Context) error {
err := m.ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
return err
}
m.ws.Close()
return nil
}