-
Notifications
You must be signed in to change notification settings - Fork 3
/
gas_price.go
160 lines (132 loc) · 5.06 KB
/
gas_price.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Package gas provides a client for the ETH Gas Station API and convenience functions.
//
// It includes type aliases for each priority level supported by ETH Gas Station, functions to get the lastest price
// from the API, and a closure that can be used to cache results for a user-defined period of time.
package gas
import (
"encoding/json"
"errors"
"math/big"
"net/http"
"sync"
"time"
)
// ETHGasStationURL is the API URL for the ETH Gas Station API.
//
// More information available at https://ethgasstation.info
const ETHGasStationURL = "https://ethgasstation.info/json/ethgasAPI.json"
// GasPriority is a type alias for a string, with supported priorities included in this package.
type GasPriority string
// GasPriceSuggester is type alias for a function that returns a reccomended gas price in base units for a given priority level.
type GasPriceSuggester func(GasPriority) (*big.Int, error)
const (
// GasPriorityFast is the recommended gas price for a transaction to be mined in less than 2 minutes.
GasPriorityFast = GasPriority("fast")
// GasPriorityFastest is the recommended gas price for a transaction to be mined in less than 30 seconds.
GasPriorityFastest = GasPriority("fastest")
// GasPrioritySafeLow is the recommended cheapest gas price for a transaction to be mined in less than 30 minutes.
GasPrioritySafeLow = GasPriority("safeLow")
// GasPriorityAverage is the recommended average gas price for a transaction to be mined in less than 5 minutes.
GasPriorityAverage = GasPriority("average")
)
// SuggestGasPrice returns a suggested gas price value in wei (base units) for timely transaction execution. It always
// makes a new call to the ETH Gas Station API. Use NewGasPriceSuggester to leverage cached results.
//
// The returned price depends on the priority specified, and supports all priorities supported by the ETH Gas Station API.
func SuggestGasPrice(priority GasPriority) (*big.Int, error) {
prices, err := loadGasPrices()
if err != nil {
return nil, err
}
return parseSuggestedGasPrice(priority, prices)
}
// SuggestFastGasPrice is a helper method that calls SuggestGasPrice with GasPriorityFast
//
// It always makes a new call to the ETH Gas Station API. Use NewGasPriceSuggester to leverage cached results.
func SuggestFastGasPrice() (*big.Int, error) {
return SuggestGasPrice(GasPriorityFast)
}
// NewGasPriceSuggester returns a function that can be used to either load a new gas price response, or use a cached
// response if it is within the age range defined by maxResultAge.
//
// The returned function loads from the cache or pulls a new response if the stored result is older than maxResultAge.
func NewGasPriceSuggester(maxResultAge time.Duration) (GasPriceSuggester, error) {
prices, err := loadGasPrices()
if err != nil {
return nil, err
}
m := gasPriceManager{
latestResponse: prices,
fetchedAt: time.Now(),
maxResultAge: maxResultAge,
}
return func(priority GasPriority) (*big.Int, error) {
return m.suggestCachedGasPrice(priority)
}, nil
}
type gasPriceManager struct {
sync.Mutex
fetchedAt time.Time
maxResultAge time.Duration
latestResponse ethGasStationResponse
}
func (m *gasPriceManager) suggestCachedGasPrice(priority GasPriority) (*big.Int, error) {
m.Lock()
defer m.Unlock()
// fetch new values if stored result is older than the maximum age
if time.Since(m.fetchedAt) > m.maxResultAge {
prices, err := loadGasPrices()
if err != nil {
return nil, err
}
m.latestResponse = prices
m.fetchedAt = time.Now()
}
return parseSuggestedGasPrice(priority, m.latestResponse)
}
// conversion factor to go from (gwei * 10) to wei
// equal to: (raw / 10) => gwei => gwei * 1e9 => wei
// simplifies to: raw * 1e8 => wei
var conversionFactor = big.NewFloat(100000000)
type ethGasStationResponse struct {
Fast float64 `json:"fast"`
Fastest float64 `json:"fastest"`
SafeLow float64 `json:"safeLow"`
Average float64 `json:"average"`
}
func loadGasPrices() (ethGasStationResponse, error) {
var prices ethGasStationResponse
res, err := http.Get(ETHGasStationURL)
if err != nil {
return prices, err
}
if err := json.NewDecoder(res.Body).Decode(&prices); err != nil {
return prices, err
}
return prices, nil
}
func parseSuggestedGasPrice(priority GasPriority, prices ethGasStationResponse) (*big.Int, error) {
switch priority {
case GasPriorityFast:
return parseGasPriceToWei(prices.Fast)
case GasPriorityFastest:
return parseGasPriceToWei(prices.Fastest)
case GasPrioritySafeLow:
return parseGasPriceToWei(prices.SafeLow)
case GasPriorityAverage:
return parseGasPriceToWei(prices.Average)
default:
return nil, errors.New("eth: unknown/unsupported gas priority")
}
}
// convert eth gas station units to wei
// (raw result / 10) * 1e9 = base units (wei)
func parseGasPriceToWei(raw float64) (*big.Int, error) {
gwei := new(big.Float).Mul(big.NewFloat(raw), conversionFactor)
if !gwei.IsInt() {
return nil, errors.New("eth: unable to represent gas price as integer")
}
// we can skip the accuracy check because we know from above that gwei is an integer
wei, _ := gwei.Int(new(big.Int))
return wei, nil
}