|
|
@ -4,9 +4,12 @@ import ( |
|
|
|
"context" |
|
|
|
"fmt" |
|
|
|
"net/http" |
|
|
|
"strings" |
|
|
|
"time" |
|
|
|
|
|
|
|
"github.com/dghubble/sling" |
|
|
|
ethCommon "github.com/ethereum/go-ethereum/common" |
|
|
|
"github.com/hermeznetwork/hermez-node/common" |
|
|
|
"github.com/hermeznetwork/hermez-node/db/historydb" |
|
|
|
"github.com/hermeznetwork/hermez-node/log" |
|
|
|
"github.com/hermeznetwork/tracerr" |
|
|
@ -23,12 +26,16 @@ type APIType string |
|
|
|
const ( |
|
|
|
// APITypeBitFinexV2 is the http API used by bitfinex V2
|
|
|
|
APITypeBitFinexV2 APIType = "bitfinexV2" |
|
|
|
// APITypeCoingeckoV3 is the http API used by copingecko V3
|
|
|
|
APITypeCoingeckoV3 APIType = "coingeckoV3" |
|
|
|
) |
|
|
|
|
|
|
|
func (t *APIType) valid() bool { |
|
|
|
switch *t { |
|
|
|
case APITypeBitFinexV2: |
|
|
|
return true |
|
|
|
case APITypeCoingeckoV3: |
|
|
|
return true |
|
|
|
default: |
|
|
|
return false |
|
|
|
} |
|
|
@ -36,24 +43,23 @@ func (t *APIType) valid() bool { |
|
|
|
|
|
|
|
// PriceUpdater definition
|
|
|
|
type PriceUpdater struct { |
|
|
|
db *historydb.HistoryDB |
|
|
|
apiURL string |
|
|
|
apiType APIType |
|
|
|
tokenSymbols []string |
|
|
|
db *historydb.HistoryDB |
|
|
|
apiURL string |
|
|
|
apiType APIType |
|
|
|
tokens []historydb.TokenSymbolAndAddr |
|
|
|
} |
|
|
|
|
|
|
|
// NewPriceUpdater is the constructor for the updater
|
|
|
|
func NewPriceUpdater(apiURL string, apiType APIType, db *historydb.HistoryDB) (*PriceUpdater, |
|
|
|
error) { |
|
|
|
tokenSymbols := []string{} |
|
|
|
if !apiType.valid() { |
|
|
|
return nil, tracerr.Wrap(fmt.Errorf("Invalid apiType: %v", apiType)) |
|
|
|
} |
|
|
|
return &PriceUpdater{ |
|
|
|
db: db, |
|
|
|
apiURL: apiURL, |
|
|
|
apiType: apiType, |
|
|
|
tokenSymbols: tokenSymbols, |
|
|
|
db: db, |
|
|
|
apiURL: apiURL, |
|
|
|
apiType: apiType, |
|
|
|
tokens: []historydb.TokenSymbolAndAddr{}, |
|
|
|
}, nil |
|
|
|
} |
|
|
|
|
|
|
@ -74,6 +80,37 @@ func getTokenPriceBitfinex(ctx context.Context, client *sling.Sling, |
|
|
|
return state[6], nil |
|
|
|
} |
|
|
|
|
|
|
|
func getTokenPriceCoingecko(ctx context.Context, client *sling.Sling, |
|
|
|
tokenAddr ethCommon.Address) (float64, error) { |
|
|
|
responseObject := make(map[string]map[string]float64) |
|
|
|
var url string |
|
|
|
var id string |
|
|
|
if tokenAddr == common.EmptyAddr { // Special case for Ether
|
|
|
|
url = "simple/price?ids=ethereum&vs_currencies=usd" |
|
|
|
id = "ethereum" |
|
|
|
} else { // Common case (ERC20)
|
|
|
|
id = strings.ToLower(tokenAddr.String()) |
|
|
|
url = "simple/token_price/ethereum?contract_addresses=" + |
|
|
|
id + "&vs_currencies=usd" |
|
|
|
} |
|
|
|
req, err := client.New().Get(url).Request() |
|
|
|
if err != nil { |
|
|
|
return 0, tracerr.Wrap(err) |
|
|
|
} |
|
|
|
res, err := client.Do(req.WithContext(ctx), &responseObject, nil) |
|
|
|
if err != nil { |
|
|
|
return 0, tracerr.Wrap(err) |
|
|
|
} |
|
|
|
if res.StatusCode != http.StatusOK { |
|
|
|
return 0, tracerr.Wrap(fmt.Errorf("http response is not is %v", res.StatusCode)) |
|
|
|
} |
|
|
|
price := responseObject[id]["usd"] |
|
|
|
if price <= 0 { |
|
|
|
return 0, tracerr.Wrap(fmt.Errorf("price not found for %v", id)) |
|
|
|
} |
|
|
|
return price, nil |
|
|
|
} |
|
|
|
|
|
|
|
// UpdatePrices is triggered by the Coordinator, and internally will update the
|
|
|
|
// token prices in the db
|
|
|
|
func (p *PriceUpdater) UpdatePrices(ctx context.Context) { |
|
|
@ -85,33 +122,35 @@ func (p *PriceUpdater) UpdatePrices(ctx context.Context) { |
|
|
|
httpClient := &http.Client{Transport: tr} |
|
|
|
client := sling.New().Base(p.apiURL).Client(httpClient) |
|
|
|
|
|
|
|
for _, tokenSymbol := range p.tokenSymbols { |
|
|
|
for _, token := range p.tokens { |
|
|
|
var tokenPrice float64 |
|
|
|
var err error |
|
|
|
switch p.apiType { |
|
|
|
case APITypeBitFinexV2: |
|
|
|
tokenPrice, err = getTokenPriceBitfinex(ctx, client, tokenSymbol) |
|
|
|
tokenPrice, err = getTokenPriceBitfinex(ctx, client, token.Symbol) |
|
|
|
case APITypeCoingeckoV3: |
|
|
|
tokenPrice, err = getTokenPriceCoingecko(ctx, client, token.Addr) |
|
|
|
} |
|
|
|
if ctx.Err() != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
if err != nil { |
|
|
|
log.Warnw("token price not updated (get error)", |
|
|
|
"err", err, "token", tokenSymbol, "apiType", p.apiType) |
|
|
|
"err", err, "token", token.Symbol, "apiType", p.apiType) |
|
|
|
} |
|
|
|
if err = p.db.UpdateTokenValue(tokenSymbol, tokenPrice); err != nil { |
|
|
|
if err = p.db.UpdateTokenValue(token.Symbol, tokenPrice); err != nil { |
|
|
|
log.Errorw("token price not updated (db error)", |
|
|
|
"err", err, "token", tokenSymbol, "apiType", p.apiType) |
|
|
|
"err", err, "token", token.Symbol, "apiType", p.apiType) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// UpdateTokenList get the registered token symbols from HistoryDB
|
|
|
|
func (p *PriceUpdater) UpdateTokenList() error { |
|
|
|
tokenSymbols, err := p.db.GetTokenSymbols() |
|
|
|
tokens, err := p.db.GetTokenSymbolsAndAddrs() |
|
|
|
if err != nil { |
|
|
|
return tracerr.Wrap(err) |
|
|
|
} |
|
|
|
p.tokenSymbols = tokenSymbols |
|
|
|
p.tokens = tokens |
|
|
|
return nil |
|
|
|
} |