Initial fee distribution implementation
This commit is contained in:
parent
6d6631fc62
commit
ef1b2e12ba
@ -1,6 +1,17 @@
|
||||
package client
|
||||
|
||||
import "github.com/gorilla/websocket"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
API_URL = "https://mempool.space/api/v1/"
|
||||
)
|
||||
|
||||
type MempoolInfo struct {
|
||||
Size int `json:"size"`
|
||||
@ -75,3 +86,48 @@ func (c *Client) Read() (*Response, error) {
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
type Fees []struct {
|
||||
FPV float64 `json:"fpv"`
|
||||
}
|
||||
|
||||
func (f Fees) Len() int { return len(f) }
|
||||
func (f Fees) Less(i, j int) bool { return f[i].FPV < f[j].FPV }
|
||||
func (f Fees) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||
|
||||
func Get(ctx context.Context, path string, v interface{}) error {
|
||||
req, err := http.NewRequest("GET", API_URL+path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
r, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
if s := r.StatusCode; s != 200 {
|
||||
return fmt.Errorf("status %d", s)
|
||||
}
|
||||
|
||||
return json.NewDecoder(r.Body).Decode(v)
|
||||
}
|
||||
|
||||
func GetProjectedFee(ctx context.Context, n int) (Fees, error) {
|
||||
var fees Fees
|
||||
if err := Get(ctx, fmt.Sprintf("transactions/projected/%d", n), &fees); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fees, nil
|
||||
}
|
||||
|
||||
func GetBlockFee(ctx context.Context, n int) (Fees, error) {
|
||||
var fees Fees
|
||||
if err := Get(ctx, fmt.Sprintf("transactions/height/%d", n), &fees); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fees, nil
|
||||
}
|
||||
|
||||
143
ui/fee_distribution.go
Normal file
143
ui/fee_distribution.go
Normal file
@ -0,0 +1,143 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/gchaincl/mempool/client"
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
type FeeDistribution struct {
|
||||
gui *gocui.Gui
|
||||
|
||||
m sync.Mutex
|
||||
loading bool
|
||||
isProjected bool
|
||||
cancelFn context.CancelFunc
|
||||
fees client.Fees
|
||||
}
|
||||
|
||||
func NewFeeDistribution(g *gocui.Gui) *FeeDistribution {
|
||||
return &FeeDistribution{gui: g}
|
||||
}
|
||||
|
||||
func (fd *FeeDistribution) newCtx() context.Context {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
fd.cancelFn = cancel
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (fd *FeeDistribution) FetchProjection(n int) error {
|
||||
fd.m.Lock()
|
||||
defer fd.m.Unlock()
|
||||
|
||||
if fn := fd.cancelFn; fn != nil {
|
||||
fn()
|
||||
}
|
||||
fd.loading = true
|
||||
|
||||
ctx := fd.newCtx()
|
||||
go func() {
|
||||
fees, err := client.GetProjectedFee(ctx, n)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fd.fees = fees
|
||||
fd.loading = false
|
||||
fd.gui.Update(fd.Layout)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fd *FeeDistribution) FetchBlock(n int) error {
|
||||
fd.m.Lock()
|
||||
defer fd.m.Unlock()
|
||||
|
||||
if fn := fd.cancelFn; fn != nil {
|
||||
fn()
|
||||
}
|
||||
fd.loading = true
|
||||
|
||||
ctx := fd.newCtx()
|
||||
go func() {
|
||||
fees, err := client.GetBlockFee(ctx, n)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fd.fees = fees
|
||||
fd.loading = false
|
||||
fd.gui.Update(fd.Layout)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fd *FeeDistribution) Layout(g *gocui.Gui) error {
|
||||
fd.m.Lock()
|
||||
defer fd.m.Unlock()
|
||||
|
||||
if fd.loading == false && (fd.fees == nil || len(fd.fees) == 0) {
|
||||
return nil
|
||||
}
|
||||
|
||||
x, y := g.Size()
|
||||
name := "fee_distribution"
|
||||
v, err := g.SetView(name, x/2-20, y/2-7, x/2+20, y/2+7)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
|
||||
v.Title = "Fee distribution"
|
||||
g.SetCurrentView(name)
|
||||
g.SetViewOnTop(name)
|
||||
g.SetKeybinding(name, gocui.KeyEsc, gocui.ModNone, fd.close)
|
||||
}
|
||||
|
||||
v.Clear()
|
||||
|
||||
if fd.loading == true {
|
||||
fmt.Fprint(v, "Loading...")
|
||||
return nil
|
||||
}
|
||||
|
||||
min, max := 99999, 0
|
||||
for _, f := range fd.fees {
|
||||
fee := int(f.FPV)
|
||||
if fee < min {
|
||||
min = fee
|
||||
}
|
||||
if fee > max {
|
||||
max = fee
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(v, "Fee span: %d - %d sat/vByte\n", min, max)
|
||||
|
||||
fmt.Fprintf(v, "Tx count: %d transactions\n", len(fd.fees))
|
||||
|
||||
sort.Sort(fd.fees)
|
||||
fmt.Fprintf(v, "Median: ~%d sat/vBytes", int(fd.fees[len(fd.fees)/2].FPV))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fd *FeeDistribution) close(g *gocui.Gui, v *gocui.View) error {
|
||||
fd.m.Lock()
|
||||
defer fd.m.Unlock()
|
||||
|
||||
if fn := fd.cancelFn; fn != nil {
|
||||
fn()
|
||||
}
|
||||
|
||||
fd.cancelFn = nil
|
||||
fd.loading = false
|
||||
fd.fees = nil
|
||||
g.DeleteView(v.Name())
|
||||
return nil
|
||||
}
|
||||
26
ui/ui.go
26
ui/ui.go
@ -3,6 +3,7 @@ package ui
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
@ -24,6 +25,7 @@ type state struct {
|
||||
|
||||
type UI struct {
|
||||
gui *gocui.Gui
|
||||
fd *FeeDistribution
|
||||
state state
|
||||
}
|
||||
|
||||
@ -34,12 +36,13 @@ func New() (*UI, error) {
|
||||
}
|
||||
|
||||
ui := &UI{gui: gui}
|
||||
gui.SetManager(ui)
|
||||
ui.fd = NewFeeDistribution(gui)
|
||||
gui.SetManager(ui, ui.fd)
|
||||
gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit)
|
||||
gui.SetKeybinding("", 'q', gocui.ModNone, quit)
|
||||
gui.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, ui.click)
|
||||
gui.Mouse = true
|
||||
gui.Highlight = true
|
||||
gui.InputEsc = true
|
||||
gui.SelFgColor = gocui.ColorWhite
|
||||
|
||||
return ui, nil
|
||||
@ -128,6 +131,7 @@ func (ui *UI) Layout(g *gocui.Gui) error {
|
||||
return err
|
||||
}
|
||||
v.BgColor = gocui.ColorBlack
|
||||
g.SetKeybinding(v.Name(), gocui.MouseLeft, gocui.ModNone, ui.onBlockClick)
|
||||
}
|
||||
|
||||
v.Clear()
|
||||
@ -142,7 +146,7 @@ func (ui *UI) Layout(g *gocui.Gui) error {
|
||||
|
||||
// draw blockchain blocks
|
||||
for i, block := range ui.state.blocks {
|
||||
name := fmt.Sprintf("block-%d", i)
|
||||
name := fmt.Sprintf("block-%d", block.Height)
|
||||
var x0, x1, y0, y1 int
|
||||
if vertical {
|
||||
x0 = x - (BLOCK_WIDTH+2)*(i+1)
|
||||
@ -162,6 +166,7 @@ func (ui *UI) Layout(g *gocui.Gui) error {
|
||||
return err
|
||||
}
|
||||
v.BgColor = gocui.ColorBlack
|
||||
g.SetKeybinding(v.Name(), gocui.MouseLeft, gocui.ModNone, ui.onBlockClick)
|
||||
}
|
||||
|
||||
v.Title = fmt.Sprintf("#%d", block.Height)
|
||||
@ -296,6 +301,19 @@ func fmtSize(s int) string {
|
||||
return fmt.Sprintf("%dMB", ceil(m))
|
||||
}
|
||||
|
||||
func (ui *UI) click(g *gocui.Gui, v *gocui.View) error {
|
||||
func (ui *UI) onBlockClick(g *gocui.Gui, v *gocui.View) error {
|
||||
name := v.Name()
|
||||
if strings.HasPrefix(name, "projected-block-") {
|
||||
id := strings.TrimPrefix(name, "projected-block-")
|
||||
n, _ := strconv.Atoi(id)
|
||||
return ui.fd.FetchProjection(n)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "block-") {
|
||||
id := strings.TrimPrefix(name, "block-")
|
||||
n, _ := strconv.Atoi(id)
|
||||
return ui.fd.FetchBlock(n)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user