-
Notifications
You must be signed in to change notification settings - Fork 33
/
pricefetcher.go
124 lines (111 loc) · 3.37 KB
/
pricefetcher.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
package fetcher
import (
"context"
"encoding/json"
"fmt"
"github.com/jpillora/backoff"
"net/http"
"strconv"
"time"
)
// PriceService --output=mocks --case=underscore.
type PriceService interface {
// GetDefiLlamaData historical price data
GetDefiLlamaData(context.Context, int, string) *float64
}
// tokenMetadataMaxRetry is the maximum number of times to retry requesting token metadata
// from the DeFi llama API.
const tokenMetadataMaxRetry = 20
const tokenMetadataMaxRetrySecondary = 1
// GetDefiLlamaData does a get request to defi llama for the symbol and price for a token.
//
//nolint:cyclop,gocognit
func GetDefiLlamaData(ctx context.Context, timestamp int, coinGeckoID string) *float64 {
zero := float64(0)
if coinGeckoID == "NO_TOKEN" || coinGeckoID == "NO_PRICE" {
// if there is no data on the token, the amount returned will be 1:1 (price will be same as the amount of token
// and the token symbol will say "no symbol"
return &zero
}
client := http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
ResponseHeaderTimeout: 10 * time.Second,
},
}
b := &backoff.Backoff{
Factor: 2,
Jitter: true,
Min: 10 * time.Millisecond,
Max: 1 * time.Second,
}
timeout := time.Duration(0)
retries := 0
retriesSecondary := 0
RETRY:
select {
case <-ctx.Done():
logger.Errorf("context canceled %s, %v", coinGeckoID, ctx.Err())
return nil
case <-time.After(timeout):
if retriesSecondary > tokenMetadataMaxRetrySecondary {
retriesSecondary = 0
retries++
}
if retries >= tokenMetadataMaxRetry {
logger.Errorf("Max retries reached, could not get token metadata for %s", coinGeckoID)
return nil
}
var granularityStr string
if retries > 1 {
granularityStr = fmt.Sprintf("?searchWidth=%dh", 15*(retries-2)+24)
}
url := fmt.Sprintf("https://coins.llama.fi/prices/historical/%d/coingecko:%s%s", timestamp, coinGeckoID, granularityStr)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
timeout = b.Duration()
logger.Errorf("error getting response from defi llama %v", err)
retries++
goto RETRY
}
resRaw, err := client.Do(req)
if err != nil {
timeout = b.Duration()
logger.Errorf("error getting response from defi llama %v", err)
retries++
goto RETRY
}
res := make(map[string]map[string]map[string]interface{})
err = json.NewDecoder(resRaw.Body).Decode(&res)
if err != nil {
if retriesSecondary > retriesSecondary-2 && retries > 18 {
logger.Errorf("Failed decoding defillama data after retries %d, retries secondary: %d id %s: timestamp: %d error %v %s", retries, retriesSecondary, coinGeckoID, timestamp, err, url)
}
timeout = b.Duration()
retriesSecondary++
goto RETRY
}
priceRaw := res["coins"][fmt.Sprintf("coingecko:%s", coinGeckoID)]["price"]
if priceRaw == nil {
if retries >= 6 {
logger.Errorf("error getting price from defi llama: retries: %d %s %d %v %s", retries, coinGeckoID, timestamp, res, url)
}
timeout = b.Duration()
retries++
goto RETRY
}
priceStr := fmt.Sprintf("%.10f", priceRaw)
priceFloat, err := strconv.ParseFloat(priceStr, 64)
if err != nil {
timeout = b.Duration()
logger.Errorf("error unwrapping price from defi llama %v", err)
retries++
goto RETRY
}
price := &priceFloat
if resRaw.Body.Close() != nil {
logger.Error("Error closing http connection.")
}
return price
}
}