github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/btc/whatthefee.go (about) 1 package btc 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "math" 7 "net/http" 8 "strconv" 9 "time" 10 11 "github.com/golang/glog" 12 "github.com/juju/errors" 13 "github.com/trezor/blockbook/bchain" 14 ) 15 16 // https://whatthefee.io returns 17 // {"index": [3, 6, 9, 12, 18, 24, 36, 48, 72, 96, 144], 18 // "columns": ["0.0500", "0.2000", "0.5000", "0.8000", "0.9500"], 19 // "data": [[60, 180, 280, 400, 440], [20, 120, 180, 380, 440], 20 // [0, 120, 160, 360, 420], [0, 80, 160, 300, 380], [0, 20, 120, 220, 360], 21 // [0, 20, 100, 180, 300], [0, 0, 80, 140, 240], [0, 0, 60, 100, 180], 22 // [0, 0, 40, 60, 140], [0, 0, 20, 20, 60], [0, 0, 0, 0, 20]]} 23 24 type whatTheFeeServiceResult struct { 25 Index []int `json:"index"` 26 Columns []string `json:"columns"` 27 Data [][]int `json:"data"` 28 } 29 30 type whatTheFeeParams struct { 31 URL string `json:"url"` 32 PeriodSeconds int `periodSeconds:"url"` 33 } 34 35 type whatTheFeeProvider struct { 36 *alternativeFeeProvider 37 params whatTheFeeParams 38 probabilities []string 39 } 40 41 // NewWhatTheFee initializes https://whatthefee.io provider 42 func NewWhatTheFee(chain bchain.BlockChain, params string) (alternativeFeeProviderInterface, error) { 43 var p whatTheFeeProvider 44 err := json.Unmarshal([]byte(params), &p.params) 45 if err != nil { 46 return nil, err 47 } 48 if p.params.URL == "" || p.params.PeriodSeconds == 0 { 49 return nil, errors.New("NewWhatTheFee: Missing parameters") 50 } 51 p.chain = chain 52 go p.whatTheFeeDownloader() 53 return &p, nil 54 } 55 56 func (p *whatTheFeeProvider) whatTheFeeDownloader() { 57 period := time.Duration(p.params.PeriodSeconds) * time.Second 58 timer := time.NewTimer(period) 59 counter := 0 60 for { 61 var data whatTheFeeServiceResult 62 err := p.whatTheFeeGetData(&data) 63 if err != nil { 64 glog.Error("whatTheFeeGetData ", err) 65 } else { 66 if p.whatTheFeeProcessData(&data) { 67 if counter%60 == 0 { 68 p.compareToDefault() 69 } 70 counter++ 71 } 72 } 73 <-timer.C 74 timer.Reset(period) 75 } 76 } 77 78 func (p *whatTheFeeProvider) whatTheFeeProcessData(data *whatTheFeeServiceResult) bool { 79 if len(data.Index) == 0 || len(data.Index) != len(data.Data) || len(data.Columns) == 0 { 80 glog.Errorf("invalid data %+v", data) 81 return false 82 } 83 p.mux.Lock() 84 defer p.mux.Unlock() 85 p.probabilities = data.Columns 86 p.fees = make([]alternativeFeeProviderFee, len(data.Index)) 87 for i, blocks := range data.Index { 88 if len(data.Columns) != len(data.Data[i]) { 89 glog.Errorf("invalid data %+v", data) 90 return false 91 } 92 fees := make([]int, len(data.Columns)) 93 for j, l := range data.Data[i] { 94 fees[j] = int(1000 * math.Exp(float64(l)/100)) 95 } 96 p.fees[i] = alternativeFeeProviderFee{ 97 blocks: blocks, 98 feePerKB: fees[len(fees)/2], 99 } 100 } 101 p.lastSync = time.Now() 102 glog.Infof("whatTheFees: %+v", p.fees) 103 return true 104 } 105 106 func (p *whatTheFeeProvider) whatTheFeeGetData(res interface{}) error { 107 var httpData []byte 108 httpReq, err := http.NewRequest("GET", p.params.URL, bytes.NewBuffer(httpData)) 109 if err != nil { 110 return err 111 } 112 httpRes, err := http.DefaultClient.Do(httpReq) 113 if httpRes != nil { 114 defer httpRes.Body.Close() 115 } 116 if err != nil { 117 return err 118 } 119 if httpRes.StatusCode != 200 { 120 return errors.New("whatthefee.io returned status " + strconv.Itoa(httpRes.StatusCode)) 121 } 122 return safeDecodeResponse(httpRes.Body, &res) 123 }