Return token exchange rates via API
This commit is contained in:
parent
f5b179d5c2
commit
3f5980abdb
@ -1342,7 +1342,6 @@ func (w *Worker) setFiatRateToBalanceHistories(histories BalanceHistories, curre
|
|||||||
for i := range histories {
|
for i := range histories {
|
||||||
bh := &histories[i]
|
bh := &histories[i]
|
||||||
t := time.Unix(int64(bh.Time), 0)
|
t := time.Unix(int64(bh.Time), 0)
|
||||||
// TODO
|
|
||||||
ticker, err := w.db.FiatRatesFindTicker(&t, "", "")
|
ticker, err := w.db.FiatRatesFindTicker(&t, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Error finding ticker by date %v. Error: %v", t, err)
|
glog.Errorf("Error finding ticker by date %v. Error: %v", t, err)
|
||||||
@ -1594,8 +1593,20 @@ func removeEmpty(stringSlice []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getFiatRatesResult checks if CurrencyRatesTicker contains all necessary data and returns formatted result
|
// getFiatRatesResult checks if CurrencyRatesTicker contains all necessary data and returns formatted result
|
||||||
func (w *Worker) getFiatRatesResult(currencies []string, ticker *db.CurrencyRatesTicker) (*FiatTicker, error) {
|
func (w *Worker) getFiatRatesResult(currencies []string, ticker *db.CurrencyRatesTicker, token string) (*FiatTicker, error) {
|
||||||
currencies = removeEmpty(currencies)
|
if token != "" {
|
||||||
|
if len(currencies) != 1 {
|
||||||
|
return nil, NewAPIError("Rates for token only for a single currency", true)
|
||||||
|
}
|
||||||
|
rate := ticker.TokenRateInCurrency(token, currencies[0])
|
||||||
|
if rate <= 0 {
|
||||||
|
rate = -1
|
||||||
|
}
|
||||||
|
return &FiatTicker{
|
||||||
|
Timestamp: ticker.Timestamp.UTC().Unix(),
|
||||||
|
Rates: map[string]float32{currencies[0]: rate},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
if len(currencies) == 0 {
|
if len(currencies) == 0 {
|
||||||
// Return all available ticker rates
|
// Return all available ticker rates
|
||||||
return &FiatTicker{
|
return &FiatTicker{
|
||||||
@ -1620,7 +1631,7 @@ func (w *Worker) getFiatRatesResult(currencies []string, ticker *db.CurrencyRate
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFiatRatesForBlockID returns fiat rates for block height or block hash
|
// GetFiatRatesForBlockID returns fiat rates for block height or block hash
|
||||||
func (w *Worker) GetFiatRatesForBlockID(blockID string, currencies []string) (*FiatTicker, error) {
|
func (w *Worker) GetFiatRatesForBlockID(blockID string, currencies []string, token string) (*FiatTicker, error) {
|
||||||
var ticker *db.CurrencyRatesTicker
|
var ticker *db.CurrencyRatesTicker
|
||||||
bi, err := w.getBlockInfoFromBlockID(blockID)
|
bi, err := w.getBlockInfoFromBlockID(blockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1631,14 +1642,18 @@ func (w *Worker) GetFiatRatesForBlockID(blockID string, currencies []string) (*F
|
|||||||
}
|
}
|
||||||
dbi := &db.BlockInfo{Time: bi.Time} // get Unix timestamp from block
|
dbi := &db.BlockInfo{Time: bi.Time} // get Unix timestamp from block
|
||||||
tm := time.Unix(dbi.Time, 0) // convert it to Time object
|
tm := time.Unix(dbi.Time, 0) // convert it to Time object
|
||||||
// TODO
|
vsCurrency := ""
|
||||||
ticker, err = w.db.FiatRatesFindTicker(&tm, "", "")
|
currencies = removeEmpty(currencies)
|
||||||
|
if len(currencies) == 1 {
|
||||||
|
vsCurrency = currencies[0]
|
||||||
|
}
|
||||||
|
ticker, err = w.db.FiatRatesFindTicker(&tm, vsCurrency, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
|
return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
|
||||||
} else if ticker == nil {
|
} else if ticker == nil {
|
||||||
return nil, NewAPIError(fmt.Sprintf("No tickers available for %s", tm), true)
|
return nil, NewAPIError(fmt.Sprintf("No tickers available for %s", tm), true)
|
||||||
}
|
}
|
||||||
result, err := w.getFiatRatesResult(currencies, ticker)
|
result, err := w.getFiatRatesResult(currencies, ticker, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1646,22 +1661,29 @@ func (w *Worker) GetFiatRatesForBlockID(blockID string, currencies []string) (*F
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentFiatRates returns last available fiat rates
|
// GetCurrentFiatRates returns last available fiat rates
|
||||||
func (w *Worker) GetCurrentFiatRates(currencies []string) (*FiatTicker, error) {
|
func (w *Worker) GetCurrentFiatRates(currencies []string, token string) (*FiatTicker, error) {
|
||||||
// TODO
|
vsCurrency := ""
|
||||||
ticker, err := w.db.FiatRatesFindLastTicker("", "")
|
currencies = removeEmpty(currencies)
|
||||||
if err != nil {
|
if len(currencies) == 1 {
|
||||||
return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
|
vsCurrency = currencies[0]
|
||||||
} else if ticker == nil {
|
|
||||||
return nil, NewAPIError(fmt.Sprintf("No tickers found!"), true)
|
|
||||||
}
|
}
|
||||||
result, err := w.getFiatRatesResult(currencies, ticker)
|
ticker, err := w.db.FiatRatesGetCurrentTicker(vsCurrency, token)
|
||||||
|
if ticker == nil || err != nil {
|
||||||
|
ticker, err = w.db.FiatRatesFindLastTicker(vsCurrency, token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
|
||||||
|
} else if ticker == nil {
|
||||||
|
return nil, NewAPIError(fmt.Sprintf("No tickers found!"), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result, err := w.getFiatRatesResult(currencies, ticker, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeErrorRates returns a map of currrencies, with each value equal to -1
|
// makeErrorRates returns a map of currencies, with each value equal to -1
|
||||||
// used when there was an error finding ticker
|
// used when there was an error finding ticker
|
||||||
func makeErrorRates(currencies []string) map[string]float32 {
|
func makeErrorRates(currencies []string) map[string]float32 {
|
||||||
rates := make(map[string]float32)
|
rates := make(map[string]float32)
|
||||||
@ -1672,18 +1694,21 @@ func makeErrorRates(currencies []string) map[string]float32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFiatRatesForTimestamps returns fiat rates for each of the provided dates
|
// GetFiatRatesForTimestamps returns fiat rates for each of the provided dates
|
||||||
func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []string) (*FiatTickers, error) {
|
func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []string, token string) (*FiatTickers, error) {
|
||||||
if len(timestamps) == 0 {
|
if len(timestamps) == 0 {
|
||||||
return nil, NewAPIError("No timestamps provided", true)
|
return nil, NewAPIError("No timestamps provided", true)
|
||||||
}
|
}
|
||||||
|
vsCurrency := ""
|
||||||
currencies = removeEmpty(currencies)
|
currencies = removeEmpty(currencies)
|
||||||
|
if len(currencies) == 1 {
|
||||||
|
vsCurrency = currencies[0]
|
||||||
|
}
|
||||||
|
|
||||||
ret := &FiatTickers{}
|
ret := &FiatTickers{}
|
||||||
for _, timestamp := range timestamps {
|
for _, timestamp := range timestamps {
|
||||||
date := time.Unix(timestamp, 0)
|
date := time.Unix(timestamp, 0)
|
||||||
date = date.UTC()
|
date = date.UTC()
|
||||||
// TODO
|
ticker, err := w.db.FiatRatesFindTicker(&date, vsCurrency, token)
|
||||||
ticker, err := w.db.FiatRatesFindTicker(&date, "", "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Error finding ticker for date %v. Error: %v", date, err)
|
glog.Errorf("Error finding ticker for date %v. Error: %v", date, err)
|
||||||
ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)})
|
ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)})
|
||||||
@ -1692,8 +1717,13 @@ func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []stri
|
|||||||
ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)})
|
ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result, err := w.getFiatRatesResult(currencies, ticker)
|
result, err := w.getFiatRatesResult(currencies, ticker, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if apiErr, ok := err.(*APIError); ok {
|
||||||
|
if apiErr.Public {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)})
|
ret.Tickers = append(ret.Tickers, FiatTicker{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -1703,12 +1733,11 @@ func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAvailableVsCurrencies returns the list of available versus currencies for exchange rates
|
// GetAvailableVsCurrencies returns the list of available versus currencies for exchange rates
|
||||||
func (w *Worker) GetAvailableVsCurrencies(timestamp int64) (*AvailableVsCurrencies, error) {
|
func (w *Worker) GetAvailableVsCurrencies(timestamp int64, token string) (*AvailableVsCurrencies, error) {
|
||||||
date := time.Unix(timestamp, 0)
|
date := time.Unix(timestamp, 0)
|
||||||
date = date.UTC()
|
date = date.UTC()
|
||||||
|
|
||||||
// TODO
|
ticker, err := w.db.FiatRatesFindTicker(&date, "", strings.ToLower(token))
|
||||||
ticker, err := w.db.FiatRatesFindTicker(&date, "", "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
|
return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
|
||||||
} else if ticker == nil {
|
} else if ticker == nil {
|
||||||
|
|||||||
@ -525,6 +525,7 @@ func onNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
glog.Error("onNewFiatRatesTicker recovered from panic: ", r)
|
glog.Error("onNewFiatRatesTicker recovered from panic: ", r)
|
||||||
|
debug.PrintStack()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for _, c := range callbacksOnNewFiatRatesTicker {
|
for _, c := range callbacksOnNewFiatRatesTicker {
|
||||||
|
|||||||
95
db/fiat.go
95
db/fiat.go
@ -26,6 +26,49 @@ type CurrencyRatesTicker struct {
|
|||||||
TokenRates map[string]float32 // rates of the tokens (identified by the address of the contract) against the base currency
|
TokenRates map[string]float32 // rates of the tokens (identified by the address of the contract) against the base currency
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert converts value in base currency to toCurrency
|
||||||
|
func (t *CurrencyRatesTicker) Convert(baseValue float64, toCurrency string) float64 {
|
||||||
|
rate, found := t.Rates[toCurrency]
|
||||||
|
if !found {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return baseValue * float64(rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertTokenToBase converts token value to base currency
|
||||||
|
func (t *CurrencyRatesTicker) ConvertTokenToBase(value float64, token string) float64 {
|
||||||
|
if t.TokenRates != nil {
|
||||||
|
rate, found := t.TokenRates[token]
|
||||||
|
if found {
|
||||||
|
return value * float64(rate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertTokenToBase converts token value to toCurrency currency
|
||||||
|
func (t *CurrencyRatesTicker) ConvertToken(value float64, token string, toCurrency string) float64 {
|
||||||
|
baseValue := t.ConvertTokenToBase(value, token)
|
||||||
|
if baseValue > 0 {
|
||||||
|
return t.Convert(baseValue, toCurrency)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenRateInCurrency return token rate in toCurrency currency
|
||||||
|
func (t *CurrencyRatesTicker) TokenRateInCurrency(token string, toCurrency string) float32 {
|
||||||
|
if t.TokenRates != nil {
|
||||||
|
rate, found := t.TokenRates[token]
|
||||||
|
if found {
|
||||||
|
baseRate, found := t.Rates[toCurrency]
|
||||||
|
if found {
|
||||||
|
return baseRate * rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func packTimestamp(t *time.Time) []byte {
|
func packTimestamp(t *time.Time) []byte {
|
||||||
return []byte(t.UTC().Format(FiatRatesTimeFormat))
|
return []byte(t.UTC().Format(FiatRatesTimeFormat))
|
||||||
}
|
}
|
||||||
@ -117,6 +160,26 @@ func (d *RocksDB) FiatRatesStoreTicker(wb *gorocksdb.WriteBatch, ticker *Currenc
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSuitableTicker(ticker *CurrencyRatesTicker, vsCurrency string, token string) bool {
|
||||||
|
if vsCurrency != "" {
|
||||||
|
if ticker.Rates == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, found := ticker.Rates[vsCurrency]; !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if token != "" {
|
||||||
|
if ticker.TokenRates == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, found := ticker.TokenRates[token]; !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func getTickerFromIterator(it *gorocksdb.Iterator, vsCurrency string, token string) (*CurrencyRatesTicker, error) {
|
func getTickerFromIterator(it *gorocksdb.Iterator, vsCurrency string, token string) (*CurrencyRatesTicker, error) {
|
||||||
timeObj, err := time.Parse(FiatRatesTimeFormat, string(it.Key().Data()))
|
timeObj, err := time.Parse(FiatRatesTimeFormat, string(it.Key().Data()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -126,21 +189,8 @@ func getTickerFromIterator(it *gorocksdb.Iterator, vsCurrency string, token stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if vsCurrency != "" {
|
if !isSuitableTicker(ticker, vsCurrency, token) {
|
||||||
if ticker.Rates == nil {
|
return nil, nil
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if _, found := ticker.Rates[vsCurrency]; !found {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if token != "" {
|
|
||||||
if ticker.TokenRates == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if _, found := ticker.TokenRates[token]; !found {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ticker.Timestamp = timeObj.UTC()
|
ticker.Timestamp = timeObj.UTC()
|
||||||
return ticker, nil
|
return ticker, nil
|
||||||
@ -169,8 +219,8 @@ func (d *RocksDB) FiatRatesGetTicker(tickerTime *time.Time) (*CurrencyRatesTicke
|
|||||||
// FiatRatesFindTicker gets FiatRates data closest to the specified timestamp, of the base currency, vsCurrency or the token if specified
|
// FiatRatesFindTicker gets FiatRates data closest to the specified timestamp, of the base currency, vsCurrency or the token if specified
|
||||||
func (d *RocksDB) FiatRatesFindTicker(tickerTime *time.Time, vsCurrency string, token string) (*CurrencyRatesTicker, error) {
|
func (d *RocksDB) FiatRatesFindTicker(tickerTime *time.Time, vsCurrency string, token string) (*CurrencyRatesTicker, error) {
|
||||||
tickersMux.Lock()
|
tickersMux.Lock()
|
||||||
if currentTicker != nil && lastTickerInDB != nil {
|
if currentTicker != nil {
|
||||||
if tickerTime.After(lastTickerInDB.Timestamp) {
|
if !tickerTime.Before(currentTicker.Timestamp) || (lastTickerInDB != nil && tickerTime.After(lastTickerInDB.Timestamp)) {
|
||||||
f := true
|
f := true
|
||||||
if token != "" && currentTicker.TokenRates != nil {
|
if token != "" && currentTicker.TokenRates != nil {
|
||||||
_, f = currentTicker.TokenRates[token]
|
_, f = currentTicker.TokenRates[token]
|
||||||
@ -224,14 +274,17 @@ func (d *RocksDB) FiatRatesFindLastTicker(vsCurrency string, token string) (*Cur
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FiatRatesGetCurrentTicker return current ticker
|
// FiatRatesGetCurrentTicker returns current ticker
|
||||||
func (d *RocksDB) FiatRatesGetCurrentTicker(tickerTime *time.Time, token string) (*CurrencyRatesTicker, error) {
|
func (d *RocksDB) FiatRatesGetCurrentTicker(vsCurrency string, token string) (*CurrencyRatesTicker, error) {
|
||||||
tickersMux.Lock()
|
tickersMux.Lock()
|
||||||
defer tickersMux.Unlock()
|
defer tickersMux.Unlock()
|
||||||
return currentTicker, nil
|
if currentTicker != nil && isSuitableTicker(currentTicker, vsCurrency, token) {
|
||||||
|
return currentTicker, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FiatRatesCurrentTicker return current ticker
|
// FiatRatesCurrentTicker sets current ticker
|
||||||
func (d *RocksDB) FiatRatesSetCurrentTicker(t *CurrencyRatesTicker) {
|
func (d *RocksDB) FiatRatesSetCurrentTicker(t *CurrencyRatesTicker) {
|
||||||
tickersMux.Lock()
|
tickersMux.Lock()
|
||||||
defer tickersMux.Unlock()
|
defer tickersMux.Unlock()
|
||||||
|
|||||||
@ -157,6 +157,24 @@ func TestRocksTickers(t *testing.T) {
|
|||||||
t.Errorf("Ticker %v found unexpectedly for aud vsCurrency", ticker)
|
t.Errorf("Ticker %v found unexpectedly for aud vsCurrency", ticker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ticker, err = d.FiatRatesGetCurrentTicker("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TestRocksTickers err: %+v", err)
|
||||||
|
} else if ticker != nil {
|
||||||
|
t.Errorf("FiatRatesGetCurrentTicker %v found unexpectedly", ticker)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.FiatRatesSetCurrentTicker(ticker1)
|
||||||
|
ticker, err = d.FiatRatesGetCurrentTicker("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TestRocksTickers err: %+v", err)
|
||||||
|
} else if ticker == nil {
|
||||||
|
t.Errorf("Ticker not found")
|
||||||
|
} else if ticker.Timestamp.Format(FiatRatesTimeFormat) != ticker1.Timestamp.Format(FiatRatesTimeFormat) {
|
||||||
|
t.Errorf("Incorrect ticker found. Expected: %v, found: %+v", ticker1.Timestamp, ticker.Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.FiatRatesSetCurrentTicker(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_packUnpackCurrencyRatesTicker(t *testing.T) {
|
func Test_packUnpackCurrencyRatesTicker(t *testing.T) {
|
||||||
@ -208,3 +226,64 @@ func Test_packUnpackCurrencyRatesTicker(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCurrencyRatesTicker_ConvertToken(t *testing.T) {
|
||||||
|
ticker := &CurrencyRatesTicker{
|
||||||
|
Rates: map[string]float32{
|
||||||
|
"usd": 2129.987654321,
|
||||||
|
"eur": 1332.12345678,
|
||||||
|
},
|
||||||
|
TokenRates: map[string]float32{
|
||||||
|
"0x82df128257a7d7556262e1ab7f1f639d9775b85e": 0.4092341123,
|
||||||
|
"0x6b175474e89094c44da98b954eedeac495271d0f": 12.32323232323232,
|
||||||
|
"0xdac17f958d2ee523a2206206994597c13d831ec7": 1332421341235.51234,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
baseValue float64
|
||||||
|
toCurrency string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value float64
|
||||||
|
token string
|
||||||
|
toCurrency string
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "usd 0x82df128257a7d7556262e1ab7f1f639d9775b85e",
|
||||||
|
value: 10,
|
||||||
|
token: "0x82df128257a7d7556262e1ab7f1f639d9775b85e",
|
||||||
|
toCurrency: "usd",
|
||||||
|
want: 8716.635514874506,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "eur 0xdac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
value: 23.123,
|
||||||
|
token: "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
toCurrency: "eur",
|
||||||
|
want: 4.104216071804417e+16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "eur 0xdac17f958d2ee523a2206206994597c13d831ec8",
|
||||||
|
value: 23.123,
|
||||||
|
token: "0xdac17f958d2ee523a2206206994597c13d831ec8",
|
||||||
|
toCurrency: "eur",
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "eur 0xdac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
value: 23.123,
|
||||||
|
token: "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
toCurrency: "czk",
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := ticker.ConvertToken(tt.value, tt.token, tt.toCurrency); got != tt.want {
|
||||||
|
t.Errorf("CurrencyRatesTicker.ConvertToken() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ type Coingecko struct {
|
|||||||
timeFormat string
|
timeFormat string
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
db *db.RocksDB
|
db *db.RocksDB
|
||||||
|
updatingCurrent bool
|
||||||
updatingTokens bool
|
updatingTokens bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,13 +43,17 @@ type coinsListItem struct {
|
|||||||
// coinList https://api.coingecko.com/api/v3/coins/list
|
// coinList https://api.coingecko.com/api/v3/coins/list
|
||||||
type coinList []coinsListItem
|
type coinList []coinsListItem
|
||||||
|
|
||||||
type marketPoint [2]float32
|
type marketPoint [2]float64
|
||||||
type marketChartPrices struct {
|
type marketChartPrices struct {
|
||||||
Prices []marketPoint `json:"prices"`
|
Prices []marketPoint `json:"prices"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCoinGeckoDownloader creates a coingecko structure that implements the RatesDownloaderInterface
|
// NewCoinGeckoDownloader creates a coingecko structure that implements the RatesDownloaderInterface
|
||||||
func NewCoinGeckoDownloader(db *db.RocksDB, url string, coin string, platformIdentifier string, platformVsCurrency string, timeFormat string, throttlingDelayMs int) RatesDownloaderInterface {
|
func NewCoinGeckoDownloader(db *db.RocksDB, url string, coin string, platformIdentifier string, platformVsCurrency string, timeFormat string, throttleDown bool) RatesDownloaderInterface {
|
||||||
|
var throttlingDelayMs int
|
||||||
|
if throttleDown {
|
||||||
|
throttlingDelayMs = 100
|
||||||
|
}
|
||||||
httpTimeoutSeconds := 15 * time.Second
|
httpTimeoutSeconds := 15 * time.Second
|
||||||
return &Coingecko{
|
return &Coingecko{
|
||||||
url: url,
|
url: url,
|
||||||
@ -95,13 +100,13 @@ func (cg *Coingecko) makeReq(url string) ([]byte, error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
if err.Error() != "error code: 1015" {
|
if err.Error() != "error code: 1015" && !strings.Contains(strings.ToLower(err.Error()), "exceeded the rate limit") {
|
||||||
glog.Errorf("Coingecko makeReq %v error %v", url, err)
|
glog.Errorf("Coingecko makeReq %v error %v", url, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// if there is a throttling error, wait 70 seconds and retry
|
// if there is a throttling error, wait 60 seconds and retry
|
||||||
glog.Errorf("Coingecko makeReq %v error %v, will retry in 70 seconds", url, err)
|
glog.Errorf("Coingecko makeReq %v error %v, will retry in 60 seconds", url, err)
|
||||||
time.Sleep(70 * time.Second)
|
time.Sleep(60 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +224,9 @@ func (cg *Coingecko) platformIds() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cg *Coingecko) CurrentTickers() (*db.CurrencyRatesTicker, error) {
|
func (cg *Coingecko) CurrentTickers() (*db.CurrencyRatesTicker, error) {
|
||||||
|
cg.updatingCurrent = true
|
||||||
|
defer func() { cg.updatingCurrent = false }()
|
||||||
|
|
||||||
var newTickers = db.CurrencyRatesTicker{}
|
var newTickers = db.CurrencyRatesTicker{}
|
||||||
|
|
||||||
if vsCurrencies == nil {
|
if vsCurrencies == nil {
|
||||||
@ -290,13 +298,12 @@ func (cg *Coingecko) getHistoricalTicker(tickersToUpdate map[uint]*db.CurrencyRa
|
|||||||
warningLogged := false
|
warningLogged := false
|
||||||
for _, p := range mc.Prices {
|
for _, p := range mc.Prices {
|
||||||
var timestamp uint
|
var timestamp uint
|
||||||
if p[0] > 100000000000 {
|
timestamp = uint(p[0])
|
||||||
|
if timestamp > 100000000000 {
|
||||||
// convert timestamp from milliseconds to seconds
|
// convert timestamp from milliseconds to seconds
|
||||||
timestamp = uint(p[0] / 1000)
|
timestamp /= 1000
|
||||||
} else {
|
|
||||||
timestamp = uint(p[0])
|
|
||||||
}
|
}
|
||||||
rate := p[1]
|
rate := float32(p[1])
|
||||||
if timestamp%(24*3600) == 0 && timestamp != 0 && rate != 0 { // process only tickers for the whole day with non 0 value
|
if timestamp%(24*3600) == 0 && timestamp != 0 && rate != 0 { // process only tickers for the whole day with non 0 value
|
||||||
var found bool
|
var found bool
|
||||||
var ticker *db.CurrencyRatesTicker
|
var ticker *db.CurrencyRatesTicker
|
||||||
@ -350,6 +357,15 @@ func (cg *Coingecko) storeTickers(tickersToUpdate map[uint]*db.CurrencyRatesTick
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cg *Coingecko) throttleHistoricalDownload() {
|
||||||
|
// long delay next request to avoid throttling if downloading current tickers at the same time
|
||||||
|
delay := 1
|
||||||
|
if cg.updatingCurrent {
|
||||||
|
delay = 600
|
||||||
|
}
|
||||||
|
time.Sleep(cg.throttlingDelay * time.Duration(delay))
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateHistoricalTickers gets historical tickers for the main crypto currency
|
// UpdateHistoricalTickers gets historical tickers for the main crypto currency
|
||||||
func (cg *Coingecko) UpdateHistoricalTickers() error {
|
func (cg *Coingecko) UpdateHistoricalTickers() error {
|
||||||
tickersToUpdate := make(map[uint]*db.CurrencyRatesTicker)
|
tickersToUpdate := make(map[uint]*db.CurrencyRatesTicker)
|
||||||
@ -371,7 +387,7 @@ func (cg *Coingecko) UpdateHistoricalTickers() error {
|
|||||||
glog.Errorf("getHistoricalTicker %s-%s %v", cg.coin, currency, err)
|
glog.Errorf("getHistoricalTicker %s-%s %v", cg.coin, currency, err)
|
||||||
}
|
}
|
||||||
if req {
|
if req {
|
||||||
time.Sleep(cg.throttlingDelay)
|
cg.throttleHistoricalDownload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,8 +429,7 @@ func (cg *Coingecko) UpdateHistoricalTokenTickers() error {
|
|||||||
glog.Infof("Coingecko updated %d of %d token tickers", count, len(platformIds))
|
glog.Infof("Coingecko updated %d of %d token tickers", count, len(platformIds))
|
||||||
}
|
}
|
||||||
if req {
|
if req {
|
||||||
// long delay next request to avoid throttling
|
cg.throttleHistoricalDownload()
|
||||||
time.Sleep(cg.throttlingDelay * 20)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,12 +56,12 @@ func NewFiatRatesDownloader(db *db.RocksDB, apiType string, params string, callb
|
|||||||
rd.db = db
|
rd.db = db
|
||||||
rd.callbackOnNewTicker = callback
|
rd.callbackOnNewTicker = callback
|
||||||
if apiType == "coingecko" {
|
if apiType == "coingecko" {
|
||||||
throttlingDelayMs := 50
|
throttle := true
|
||||||
if callback == nil {
|
if callback == nil {
|
||||||
// a small hack - in tests the callback is not used, therefore there is no delay slowing the test
|
// a small hack - in tests the callback is not used, therefore there is no delay slowing the test
|
||||||
throttlingDelayMs = 0
|
throttle = false
|
||||||
}
|
}
|
||||||
rd.downloader = NewCoinGeckoDownloader(db, rdParams.URL, rdParams.Coin, rdParams.PlatformIdentifier, rdParams.PlatformVsCurrency, rd.timeFormat, throttlingDelayMs)
|
rd.downloader = NewCoinGeckoDownloader(db, rdParams.URL, rdParams.Coin, rdParams.PlatformIdentifier, rdParams.PlatformVsCurrency, rd.timeFormat, throttle)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("NewFiatRatesDownloader: incorrect API type %q", apiType)
|
return nil, fmt.Errorf("NewFiatRatesDownloader: incorrect API type %q", apiType)
|
||||||
}
|
}
|
||||||
@ -74,11 +74,14 @@ func (rd *RatesDownloader) Run() error {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
tickers, err := rd.downloader.CurrentTickers()
|
tickers, err := rd.downloader.CurrentTickers()
|
||||||
if err != nil && tickers != nil {
|
if err != nil || tickers == nil {
|
||||||
glog.Error("FiatRatesDownloader: CurrentTickers error ", err)
|
glog.Error("FiatRatesDownloader: CurrentTickers error ", err)
|
||||||
} else {
|
} else {
|
||||||
rd.db.FiatRatesSetCurrentTicker(tickers)
|
rd.db.FiatRatesSetCurrentTicker(tickers)
|
||||||
glog.Info("FiatRatesDownloader: CurrentTickers updated")
|
glog.Info("FiatRatesDownloader: CurrentTickers updated")
|
||||||
|
if rd.callbackOnNewTicker != nil {
|
||||||
|
rd.callbackOnNewTicker(tickers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if time.Now().UTC().YearDay() != lastHistoricalTickers.YearDay() || time.Now().UTC().Year() != lastHistoricalTickers.Year() {
|
if time.Now().UTC().YearDay() != lastHistoricalTickers.YearDay() || time.Now().UTC().Year() != lastHistoricalTickers.Year() {
|
||||||
err = rd.downloader.UpdateHistoricalTickers()
|
err = rd.downloader.UpdateHistoricalTickers()
|
||||||
@ -86,20 +89,24 @@ func (rd *RatesDownloader) Run() error {
|
|||||||
glog.Error("FiatRatesDownloader: UpdateHistoricalTickers error ", err)
|
glog.Error("FiatRatesDownloader: UpdateHistoricalTickers error ", err)
|
||||||
} else {
|
} else {
|
||||||
lastHistoricalTickers = time.Now().UTC()
|
lastHistoricalTickers = time.Now().UTC()
|
||||||
glog.Info("FiatRatesDownloader: UpdateHistoricalTickers finished")
|
ticker, err := rd.db.FiatRatesFindLastTicker("", "")
|
||||||
}
|
if err != nil || ticker == nil {
|
||||||
// UpdateHistoricalTokenTickers in a goroutine, it can take quite some time as there may be many tokens
|
glog.Error("FiatRatesDownloader: FiatRatesFindLastTicker error ", err)
|
||||||
go func() {
|
|
||||||
err := rd.downloader.UpdateHistoricalTokenTickers()
|
|
||||||
if err != nil {
|
|
||||||
glog.Error("FiatRatesDownloader: UpdateHistoricalTokenTickers error ", err)
|
|
||||||
} else {
|
} else {
|
||||||
lastHistoricalTickers = time.Now().UTC()
|
glog.Infof("FiatRatesDownloader: UpdateHistoricalTickers finished, last ticker from %v", ticker.Timestamp)
|
||||||
glog.Info("FiatRatesDownloader: UpdateHistoricalTokenTickers finished")
|
|
||||||
}
|
}
|
||||||
}()
|
// UpdateHistoricalTokenTickers in a goroutine, it can take quite some time as there may be many tokens
|
||||||
|
go func() {
|
||||||
|
err := rd.downloader.UpdateHistoricalTokenTickers()
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("FiatRatesDownloader: UpdateHistoricalTokenTickers error ", err)
|
||||||
|
} else {
|
||||||
|
glog.Info("FiatRatesDownloader: UpdateHistoricalTokenTickers finished")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// next run on the
|
// wait for the next run with a slight random value to avoid too many request at the same time
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
next := now + rd.periodSeconds
|
next := now + rd.periodSeconds
|
||||||
next -= next % rd.periodSeconds
|
next -= next % rd.periodSeconds
|
||||||
|
|||||||
@ -1205,7 +1205,8 @@ func (s *PublicServer) apiAvailableVsCurrencies(r *http.Request, apiVersion int)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, api.NewAPIError("Parameter \"timestamp\" is not a valid Unix timestamp.", true)
|
return nil, api.NewAPIError("Parameter \"timestamp\" is not a valid Unix timestamp.", true)
|
||||||
}
|
}
|
||||||
result, err := s.api.GetAvailableVsCurrencies(timestamp)
|
token := strings.ToLower(r.URL.Query().Get("token"))
|
||||||
|
result, err := s.api.GetAvailableVsCurrencies(timestamp, token)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1219,11 +1220,12 @@ func (s *PublicServer) apiTickers(r *http.Request, apiVersion int) (interface{},
|
|||||||
if currency != "" {
|
if currency != "" {
|
||||||
currencies = []string{currency}
|
currencies = []string{currency}
|
||||||
}
|
}
|
||||||
|
token := strings.ToLower(r.URL.Query().Get("token"))
|
||||||
|
|
||||||
if block := r.URL.Query().Get("block"); block != "" {
|
if block := r.URL.Query().Get("block"); block != "" {
|
||||||
// Get tickers for specified block height or block hash
|
// Get tickers for specified block height or block hash
|
||||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tickers-block"}).Inc()
|
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tickers-block"}).Inc()
|
||||||
result, err = s.api.GetFiatRatesForBlockID(block, currencies)
|
result, err = s.api.GetFiatRatesForBlockID(block, currencies, token)
|
||||||
} else if timestampString := r.URL.Query().Get("timestamp"); timestampString != "" {
|
} else if timestampString := r.URL.Query().Get("timestamp"); timestampString != "" {
|
||||||
// Get tickers for specified timestamp
|
// Get tickers for specified timestamp
|
||||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tickers-date"}).Inc()
|
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tickers-date"}).Inc()
|
||||||
@ -1233,7 +1235,7 @@ func (s *PublicServer) apiTickers(r *http.Request, apiVersion int) (interface{},
|
|||||||
return nil, api.NewAPIError("Parameter 'timestamp' is not a valid Unix timestamp.", true)
|
return nil, api.NewAPIError("Parameter 'timestamp' is not a valid Unix timestamp.", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
resultTickers, err := s.api.GetFiatRatesForTimestamps([]int64{timestamp}, currencies)
|
resultTickers, err := s.api.GetFiatRatesForTimestamps([]int64{timestamp}, currencies, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1241,7 +1243,7 @@ func (s *PublicServer) apiTickers(r *http.Request, apiVersion int) (interface{},
|
|||||||
} else {
|
} else {
|
||||||
// No parameters - get the latest available ticker
|
// No parameters - get the latest available ticker
|
||||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tickers-last"}).Inc()
|
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tickers-last"}).Inc()
|
||||||
result, err = s.api.GetCurrentFiatRates(currencies)
|
result, err = s.api.GetCurrentFiatRates(currencies, token)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -1259,6 +1261,7 @@ func (s *PublicServer) apiMultiTickers(r *http.Request, apiVersion int) (interfa
|
|||||||
if currency != "" {
|
if currency != "" {
|
||||||
currencies = []string{currency}
|
currencies = []string{currency}
|
||||||
}
|
}
|
||||||
|
token := strings.ToLower(r.URL.Query().Get("token"))
|
||||||
if timestampString := r.URL.Query().Get("timestamp"); timestampString != "" {
|
if timestampString := r.URL.Query().Get("timestamp"); timestampString != "" {
|
||||||
// Get tickers for specified timestamp
|
// Get tickers for specified timestamp
|
||||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-multi-tickers-date"}).Inc()
|
s.metrics.ExplorerViews.With(common.Labels{"action": "api-multi-tickers-date"}).Inc()
|
||||||
@ -1270,7 +1273,7 @@ func (s *PublicServer) apiMultiTickers(r *http.Request, apiVersion int) (interfa
|
|||||||
return nil, api.NewAPIError("Parameter 'timestamp' does not contain a valid Unix timestamp.", true)
|
return nil, api.NewAPIError("Parameter 'timestamp' does not contain a valid Unix timestamp.", true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultTickers, err := s.api.GetFiatRatesForTimestamps(t, currencies)
|
resultTickers, err := s.api.GetFiatRatesForTimestamps(t, currencies, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,42 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) {
|
|||||||
`{"txid":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","vin":[{"n":0,"addresses":["0x20cD153de35D469BA46127A0C8F18626b59a256A"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x4af4114F73d1c1C903aC9E0361b379D1291808A2"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"2081000000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0xd0","gasPrice":"0x9502f9000","gas":"0x130d5","to":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","value":"0x0","input":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","hash":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","blockNumber":"0x41eee8","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","transactionIndex":"0x0"},"internalData":{"type":0,"transfers":[{"type":1,"from":"9f4981531fda132e83c44680787dfa7ee31e4f8d","to":"4af4114f73d1c1c903ac9e0361b379d1291808a2","value":1000000},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000001},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","value":1000002}],"Error":""},"receipt":{"gasUsed":"0xcb39","status":"0x1","logs":[{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":12,"value":"10000000000000000000000"}],"ethereumSpecific":{"status":1,"nonce":208,"gasLimit":78037,"gasUsed":52025,"gasPrice":"40000000000","data":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","parsedData":{"methodId":"0xa9059cbb","name":"Transfer","function":"transfer(address, uint256)","params":[{"type":"address","values":["0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"]},{"type":"uint256","values":["10000000000000000000000"]}]}},"addressAliases":{"0x20cD153de35D469BA46127A0C8F18626b59a256A":{"Type":"ENS","Alias":"address20.eth"},"0x4af4114F73d1c1C903aC9E0361b379D1291808A2":{"Type":"Contract","Alias":"Contract 74"}}}`,
|
`{"txid":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","vin":[{"n":0,"addresses":["0x20cD153de35D469BA46127A0C8F18626b59a256A"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x4af4114F73d1c1C903aC9E0361b379D1291808A2"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"2081000000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0xd0","gasPrice":"0x9502f9000","gas":"0x130d5","to":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","value":"0x0","input":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","hash":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","blockNumber":"0x41eee8","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","transactionIndex":"0x0"},"internalData":{"type":0,"transfers":[{"type":1,"from":"9f4981531fda132e83c44680787dfa7ee31e4f8d","to":"4af4114f73d1c1c903ac9e0361b379d1291808a2","value":1000000},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000001},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","value":1000002}],"Error":""},"receipt":{"gasUsed":"0xcb39","status":"0x1","logs":[{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":12,"value":"10000000000000000000000"}],"ethereumSpecific":{"status":1,"nonce":208,"gasLimit":78037,"gasUsed":52025,"gasPrice":"40000000000","data":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","parsedData":{"methodId":"0xa9059cbb","name":"Transfer","function":"transfer(address, uint256)","params":[{"type":"address","values":["0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"]},{"type":"uint256","values":["10000000000000000000000"]}]}},"addressAliases":{"0x20cD153de35D469BA46127A0C8F18626b59a256A":{"Type":"ENS","Alias":"address20.eth"},"0x4af4114F73d1c1C903aC9E0361b379D1291808A2":{"Type":"Contract","Alias":"Contract 74"}}}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "apiFiatRates get rate by timestamp",
|
||||||
|
r: newGetRequest(ts.URL + "/api/v2/tickers?currency=usd×tamp=1574340000"),
|
||||||
|
status: http.StatusOK,
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
body: []string{
|
||||||
|
`{"ts":1574344800,"rates":{"usd":7814.5}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "apiFiatRates get token rate by timestamp",
|
||||||
|
r: newGetRequest(ts.URL + "/api/v2/tickers?currency=usd×tamp=1574340000&token=0xA4DD6Bc15Be95Af55f0447555c8b6aA3088562f3"),
|
||||||
|
status: http.StatusOK,
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
body: []string{
|
||||||
|
`{"ts":1574344800,"rates":{"usd":6251.6}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "apiFiatRates get token rate by timestamp for all currencies",
|
||||||
|
r: newGetRequest(ts.URL + "/api/v2/tickers?timestamp=1574340000&token=0xA4DD6Bc15Be95Af55f0447555c8b6aA3088562f3"),
|
||||||
|
status: http.StatusBadRequest,
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
body: []string{
|
||||||
|
`{"error":"Rates for token only for a single currency"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "apiFiatRates get token rate for unknown token by timestamp",
|
||||||
|
r: newGetRequest(ts.URL + "/api/v2/tickers?currency=usd×tamp=1574340000&token=0xFFFFFFFFFFe95Af55f0447555c8b6aA3088562f3"),
|
||||||
|
status: http.StatusOK,
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
body: []string{
|
||||||
|
`{"ts":1574340000,"rates":{"usd":-1}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
performHttpTests(tests, t, ts)
|
performHttpTests(tests, t, ts)
|
||||||
@ -103,6 +139,64 @@ func initEthereumTypeDB(d *db.RocksDB) error {
|
|||||||
return d.WriteBatch(wb)
|
return d.WriteBatch(wb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initTestFiatRatesEthereumType initializes test data for /api/v2/tickers endpoint
|
||||||
|
func initTestFiatRatesEthereumType(d *db.RocksDB) error {
|
||||||
|
if err := insertFiatRate("20180320020000", map[string]float32{
|
||||||
|
"usd": 2000.0,
|
||||||
|
"eur": 1300.0,
|
||||||
|
}, map[string]float32{
|
||||||
|
"0xdac17f958d2ee523a2206206994597c13d831ec7": 2000.1,
|
||||||
|
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 123.0,
|
||||||
|
}, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := insertFiatRate("20180320030000", map[string]float32{
|
||||||
|
"usd": 2001.0,
|
||||||
|
"eur": 1301.0,
|
||||||
|
}, map[string]float32{
|
||||||
|
"0xdac17f958d2ee523a2206206994597c13d831ec7": 2001.1,
|
||||||
|
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 199.0,
|
||||||
|
}, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := insertFiatRate("20180320040000", map[string]float32{
|
||||||
|
"usd": 2002.0,
|
||||||
|
"eur": 1302.0,
|
||||||
|
}, map[string]float32{
|
||||||
|
"0xdac17f958d2ee523a2206206994597c13d831ec7": 2002.1,
|
||||||
|
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 99.0,
|
||||||
|
}, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := insertFiatRate("20180321055521", map[string]float32{
|
||||||
|
"usd": 2003.0,
|
||||||
|
"eur": 1303.0,
|
||||||
|
}, map[string]float32{
|
||||||
|
"0xdac17f958d2ee523a2206206994597c13d831ec7": 2003.1,
|
||||||
|
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 101.0,
|
||||||
|
}, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := insertFiatRate("20191121140000", map[string]float32{
|
||||||
|
"usd": 7814.5,
|
||||||
|
"eur": 7100.0,
|
||||||
|
}, map[string]float32{
|
||||||
|
"0xdac17f958d2ee523a2206206994597c13d831ec7": 7814.1,
|
||||||
|
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 499.0,
|
||||||
|
"0xa4dd6bc15be95af55f0447555c8b6aa3088562f3": 0.8,
|
||||||
|
}, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return insertFiatRate("20191121143015", map[string]float32{
|
||||||
|
"usd": 7914.5,
|
||||||
|
"eur": 7134.1,
|
||||||
|
}, map[string]float32{
|
||||||
|
"0xdac17f958d2ee523a2206206994597c13d831ec7": 7914.1,
|
||||||
|
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 599.0,
|
||||||
|
"0xa4dd6bc15be95af55f0447555c8b6aa3088562f3": 1.2,
|
||||||
|
}, d)
|
||||||
|
}
|
||||||
|
|
||||||
func Test_PublicServer_EthereumType(t *testing.T) {
|
func Test_PublicServer_EthereumType(t *testing.T) {
|
||||||
parser := eth.NewEthereumParser(1, true)
|
parser := eth.NewEthereumParser(1, true)
|
||||||
chain, err := dbtestdata.NewFakeBlockChainEthereumType(parser)
|
chain, err := dbtestdata.NewFakeBlockChainEthereumType(parser)
|
||||||
|
|||||||
@ -75,14 +75,18 @@ func setupRocksDB(parser bchain.BlockChainParser, chain bchain.BlockChain, t *te
|
|||||||
if err := d.ConnectBlock(block2); err != nil {
|
if err := d.ConnectBlock(block2); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := initTestFiatRates(d); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
is.FinishedSync(block2.Height)
|
is.FinishedSync(block2.Height)
|
||||||
if parser.GetChainType() == bchain.ChainEthereumType {
|
if parser.GetChainType() == bchain.ChainEthereumType {
|
||||||
|
if err := initTestFiatRatesEthereumType(d); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if err := initEthereumTypeDB(d); err != nil {
|
if err := initEthereumTypeDB(d); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if err := initTestFiatRates(d); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return d, is, tmp
|
return d, is, tmp
|
||||||
}
|
}
|
||||||
@ -162,14 +166,15 @@ func newPostRequest(u string, body string) *http.Request {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertFiatRate(date string, rates map[string]float32, d *db.RocksDB) error {
|
func insertFiatRate(date string, rates map[string]float32, tokenRates map[string]float32, d *db.RocksDB) error {
|
||||||
convertedDate, err := db.FiatRatesConvertDate(date)
|
convertedDate, err := db.FiatRatesConvertDate(date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ticker := &db.CurrencyRatesTicker{
|
ticker := &db.CurrencyRatesTicker{
|
||||||
Timestamp: *convertedDate,
|
Timestamp: *convertedDate,
|
||||||
Rates: rates,
|
Rates: rates,
|
||||||
|
TokenRates: tokenRates,
|
||||||
}
|
}
|
||||||
wb := gorocksdb.NewWriteBatch()
|
wb := gorocksdb.NewWriteBatch()
|
||||||
defer wb.Destroy()
|
defer wb.Destroy()
|
||||||
@ -184,37 +189,37 @@ func initTestFiatRates(d *db.RocksDB) error {
|
|||||||
if err := insertFiatRate("20180320020000", map[string]float32{
|
if err := insertFiatRate("20180320020000", map[string]float32{
|
||||||
"usd": 2000.0,
|
"usd": 2000.0,
|
||||||
"eur": 1300.0,
|
"eur": 1300.0,
|
||||||
}, d); err != nil {
|
}, nil, d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := insertFiatRate("20180320030000", map[string]float32{
|
if err := insertFiatRate("20180320030000", map[string]float32{
|
||||||
"usd": 2001.0,
|
"usd": 2001.0,
|
||||||
"eur": 1301.0,
|
"eur": 1301.0,
|
||||||
}, d); err != nil {
|
}, nil, d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := insertFiatRate("20180320040000", map[string]float32{
|
if err := insertFiatRate("20180320040000", map[string]float32{
|
||||||
"usd": 2002.0,
|
"usd": 2002.0,
|
||||||
"eur": 1302.0,
|
"eur": 1302.0,
|
||||||
}, d); err != nil {
|
}, nil, d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := insertFiatRate("20180321055521", map[string]float32{
|
if err := insertFiatRate("20180321055521", map[string]float32{
|
||||||
"usd": 2003.0,
|
"usd": 2003.0,
|
||||||
"eur": 1303.0,
|
"eur": 1303.0,
|
||||||
}, d); err != nil {
|
}, nil, d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := insertFiatRate("20191121140000", map[string]float32{
|
if err := insertFiatRate("20191121140000", map[string]float32{
|
||||||
"usd": 7814.5,
|
"usd": 7814.5,
|
||||||
"eur": 7100.0,
|
"eur": 7100.0,
|
||||||
}, d); err != nil {
|
}, nil, d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return insertFiatRate("20191121143015", map[string]float32{
|
return insertFiatRate("20191121143015", map[string]float32{
|
||||||
"usd": 7914.5,
|
"usd": 7914.5,
|
||||||
"eur": 7134.1,
|
"eur": 7134.1,
|
||||||
}, d)
|
}, nil, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpTests struct {
|
type httpTests struct {
|
||||||
@ -1332,7 +1337,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
|||||||
"currencies": []string{"does-not-exist"},
|
"currencies": []string{"does-not-exist"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: `{"id":"21","data":{"ts":1574346615,"rates":{"does-not-exist":-1}}}`,
|
want: `{"id":"21","data":{"error":{"message":"No tickers found!"}}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "websocket getFiatRatesForTimestamps missing date",
|
name: "websocket getFiatRatesForTimestamps missing date",
|
||||||
|
|||||||
@ -56,6 +56,11 @@ type websocketChannel struct {
|
|||||||
addrDescs []string // subscribed address descriptors as strings
|
addrDescs []string // subscribed address descriptors as strings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fiatRatesSubscription struct {
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Tokens []string `json:"tokens"`
|
||||||
|
}
|
||||||
|
|
||||||
// WebsocketServer is a handle to websocket server
|
// WebsocketServer is a handle to websocket server
|
||||||
type WebsocketServer struct {
|
type WebsocketServer struct {
|
||||||
socket *websocket.Conn
|
socket *websocket.Conn
|
||||||
@ -77,6 +82,7 @@ type WebsocketServer struct {
|
|||||||
addressSubscriptions map[string]map[*websocketChannel]string
|
addressSubscriptions map[string]map[*websocketChannel]string
|
||||||
addressSubscriptionsLock sync.Mutex
|
addressSubscriptionsLock sync.Mutex
|
||||||
fiatRatesSubscriptions map[string]map[*websocketChannel]string
|
fiatRatesSubscriptions map[string]map[*websocketChannel]string
|
||||||
|
fiatRatesTokenSubscriptions map[*websocketChannel][]string
|
||||||
fiatRatesSubscriptionsLock sync.Mutex
|
fiatRatesSubscriptionsLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +116,7 @@ func NewWebsocketServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.
|
|||||||
newTransactionSubscriptions: make(map[*websocketChannel]string),
|
newTransactionSubscriptions: make(map[*websocketChannel]string),
|
||||||
addressSubscriptions: make(map[string]map[*websocketChannel]string),
|
addressSubscriptions: make(map[string]map[*websocketChannel]string),
|
||||||
fiatRatesSubscriptions: make(map[string]map[*websocketChannel]string),
|
fiatRatesSubscriptions: make(map[string]map[*websocketChannel]string),
|
||||||
|
fiatRatesTokenSubscriptions: make(map[*websocketChannel][]string),
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@ -378,14 +385,16 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
|
|||||||
return s.unsubscribeAddresses(c)
|
return s.unsubscribeAddresses(c)
|
||||||
},
|
},
|
||||||
"subscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
"subscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
||||||
r := struct {
|
var r fiatRatesSubscription
|
||||||
Currency string `json:"currency"`
|
|
||||||
}{}
|
|
||||||
err = json.Unmarshal(req.Params, &r)
|
err = json.Unmarshal(req.Params, &r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return s.subscribeFiatRates(c, strings.ToLower(r.Currency), req)
|
r.Currency = strings.ToLower(r.Currency)
|
||||||
|
for i := range r.Tokens {
|
||||||
|
r.Tokens[i] = strings.ToLower(r.Tokens[i])
|
||||||
|
}
|
||||||
|
return s.subscribeFiatRates(c, &r, req)
|
||||||
},
|
},
|
||||||
"unsubscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
"unsubscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
||||||
return s.unsubscribeFiatRates(c)
|
return s.unsubscribeFiatRates(c)
|
||||||
@ -397,10 +406,11 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
|
|||||||
"getCurrentFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
"getCurrentFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
||||||
r := struct {
|
r := struct {
|
||||||
Currencies []string `json:"currencies"`
|
Currencies []string `json:"currencies"`
|
||||||
|
Token string `json:"token"`
|
||||||
}{}
|
}{}
|
||||||
err = json.Unmarshal(req.Params, &r)
|
err = json.Unmarshal(req.Params, &r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rv, err = s.getCurrentFiatRates(r.Currencies)
|
rv, err = s.getCurrentFiatRates(r.Currencies, r.Token)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
@ -408,20 +418,22 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
|
|||||||
r := struct {
|
r := struct {
|
||||||
Timestamps []int64 `json:"timestamps"`
|
Timestamps []int64 `json:"timestamps"`
|
||||||
Currencies []string `json:"currencies"`
|
Currencies []string `json:"currencies"`
|
||||||
|
Token string `json:"token"`
|
||||||
}{}
|
}{}
|
||||||
err = json.Unmarshal(req.Params, &r)
|
err = json.Unmarshal(req.Params, &r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rv, err = s.getFiatRatesForTimestamps(r.Timestamps, r.Currencies)
|
rv, err = s.getFiatRatesForTimestamps(r.Timestamps, r.Currencies, r.Token)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
"getFiatRatesTickersList": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
"getFiatRatesTickersList": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
||||||
r := struct {
|
r := struct {
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Token string `json:"token"`
|
||||||
}{}
|
}{}
|
||||||
err = json.Unmarshal(req.Params, &r)
|
err = json.Unmarshal(req.Params, &r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rv, err = s.getAvailableVsCurrencies(r.Timestamp)
|
rv, err = s.getAvailableVsCurrencies(r.Timestamp, r.Token)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
@ -799,14 +811,16 @@ func (s *WebsocketServer) doUnsubscribeFiatRates(c *websocketChannel) {
|
|||||||
delete(s.fiatRatesSubscriptions, fr)
|
delete(s.fiatRatesSubscriptions, fr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
delete(s.fiatRatesTokenSubscriptions, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// subscribeFiatRates subscribes all FiatRates subscriptions by this channel
|
// subscribeFiatRates subscribes all FiatRates subscriptions by this channel
|
||||||
func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, currency string, req *websocketReq) (res interface{}, err error) {
|
func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, d *fiatRatesSubscription, req *websocketReq) (res interface{}, err error) {
|
||||||
s.fiatRatesSubscriptionsLock.Lock()
|
s.fiatRatesSubscriptionsLock.Lock()
|
||||||
defer s.fiatRatesSubscriptionsLock.Unlock()
|
defer s.fiatRatesSubscriptionsLock.Unlock()
|
||||||
// unsubscribe all previous subscriptions
|
// unsubscribe all previous subscriptions
|
||||||
s.doUnsubscribeFiatRates(c)
|
s.doUnsubscribeFiatRates(c)
|
||||||
|
currency := d.Currency
|
||||||
if currency == "" {
|
if currency == "" {
|
||||||
currency = allFiatRates
|
currency = allFiatRates
|
||||||
}
|
}
|
||||||
@ -816,6 +830,9 @@ func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, currency strin
|
|||||||
s.fiatRatesSubscriptions[currency] = as
|
s.fiatRatesSubscriptions[currency] = as
|
||||||
}
|
}
|
||||||
as[c] = req.ID
|
as[c] = req.ID
|
||||||
|
if len(d.Tokens) != 0 {
|
||||||
|
s.fiatRatesTokenSubscriptions[c] = d.Tokens
|
||||||
|
}
|
||||||
s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeFiatRates"})).Set(float64(len(s.fiatRatesSubscriptions)))
|
s.metrics.WebsocketSubscribes.With((common.Labels{"method": "subscribeFiatRates"})).Set(float64(len(s.fiatRatesSubscriptions)))
|
||||||
return &subscriptionResponse{true}, nil
|
return &subscriptionResponse{true}, nil
|
||||||
}
|
}
|
||||||
@ -960,7 +977,7 @@ func (s *WebsocketServer) OnNewTx(tx *bchain.MempoolTx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]float32) {
|
func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]float32, ticker *db.CurrencyRatesTicker) {
|
||||||
as, ok := s.fiatRatesSubscriptions[currency]
|
as, ok := s.fiatRatesSubscriptions[currency]
|
||||||
if ok && len(as) > 0 {
|
if ok && len(as) > 0 {
|
||||||
data := struct {
|
data := struct {
|
||||||
@ -969,10 +986,34 @@ func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]floa
|
|||||||
Rates: rates,
|
Rates: rates,
|
||||||
}
|
}
|
||||||
for c, id := range as {
|
for c, id := range as {
|
||||||
c.DataOut(&websocketRes{
|
var tokens []string
|
||||||
ID: id,
|
if ticker != nil {
|
||||||
Data: &data,
|
tokens = s.fiatRatesTokenSubscriptions[c]
|
||||||
})
|
}
|
||||||
|
if len(tokens) > 0 {
|
||||||
|
dataWithTokens := struct {
|
||||||
|
Rates interface{} `json:"rates"`
|
||||||
|
TokenRates map[string]float32 `json:"tokenRates,omitempty"`
|
||||||
|
}{
|
||||||
|
Rates: rates,
|
||||||
|
TokenRates: map[string]float32{},
|
||||||
|
}
|
||||||
|
for _, token := range tokens {
|
||||||
|
rate := ticker.TokenRateInCurrency(token, currency)
|
||||||
|
if rate > 0 {
|
||||||
|
dataWithTokens.TokenRates[token] = rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.DataOut(&websocketRes{
|
||||||
|
ID: id,
|
||||||
|
Data: &dataWithTokens,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.DataOut(&websocketRes{
|
||||||
|
ID: id,
|
||||||
|
Data: &data,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
glog.Info("broadcasting new rates for currency ", currency, " to ", len(as), " channels")
|
glog.Info("broadcasting new rates for currency ", currency, " to ", len(as), " channels")
|
||||||
}
|
}
|
||||||
@ -983,22 +1024,22 @@ func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) {
|
|||||||
s.fiatRatesSubscriptionsLock.Lock()
|
s.fiatRatesSubscriptionsLock.Lock()
|
||||||
defer s.fiatRatesSubscriptionsLock.Unlock()
|
defer s.fiatRatesSubscriptionsLock.Unlock()
|
||||||
for currency, rate := range ticker.Rates {
|
for currency, rate := range ticker.Rates {
|
||||||
s.broadcastTicker(currency, map[string]float32{currency: rate})
|
s.broadcastTicker(currency, map[string]float32{currency: rate}, ticker)
|
||||||
}
|
}
|
||||||
s.broadcastTicker(allFiatRates, ticker.Rates)
|
s.broadcastTicker(allFiatRates, ticker.Rates, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WebsocketServer) getCurrentFiatRates(currencies []string) (interface{}, error) {
|
func (s *WebsocketServer) getCurrentFiatRates(currencies []string, token string) (interface{}, error) {
|
||||||
ret, err := s.api.GetCurrentFiatRates(currencies)
|
ret, err := s.api.GetCurrentFiatRates(currencies, strings.ToLower(token))
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string) (interface{}, error) {
|
func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string, token string) (interface{}, error) {
|
||||||
ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies)
|
ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies, strings.ToLower(token))
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WebsocketServer) getAvailableVsCurrencies(timestamp int64) (interface{}, error) {
|
func (s *WebsocketServer) getAvailableVsCurrencies(timestamp int64, token string) (interface{}, error) {
|
||||||
ret, err := s.api.GetAvailableVsCurrencies(timestamp)
|
ret, err := s.api.GetAvailableVsCurrencies(timestamp, strings.ToLower(token))
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,9 @@
|
|||||||
<style>
|
<style>
|
||||||
.row {
|
.row {
|
||||||
margin-top: 1%;
|
margin-top: 1%;
|
||||||
|
}
|
||||||
|
::placeholder {
|
||||||
|
color: #ccc !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>Blockbook Websocket Test Page</title>
|
<title>Blockbook Websocket Test Page</title>
|
||||||
@ -94,6 +97,12 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function paramAsArray(name) {
|
||||||
|
const p = document.getElementById(name).value;
|
||||||
|
if(p) {
|
||||||
|
return p.split(",").map(s => s.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getInfo() {
|
function getInfo() {
|
||||||
const method = 'getInfo';
|
const method = 'getInfo';
|
||||||
@ -166,7 +175,7 @@
|
|||||||
const descriptor = document.getElementById('getBalanceHistoryDescriptor').value.trim();
|
const descriptor = document.getElementById('getBalanceHistoryDescriptor').value.trim();
|
||||||
const from = parseInt(document.getElementById("getBalanceHistoryFrom").value.trim());
|
const from = parseInt(document.getElementById("getBalanceHistoryFrom").value.trim());
|
||||||
const to = parseInt(document.getElementById("getBalanceHistoryTo").value.trim());
|
const to = parseInt(document.getElementById("getBalanceHistoryTo").value.trim());
|
||||||
const currencies = document.getElementById('getBalanceHistoryFiat').value.split(",");
|
const currencies = paramAsArray('getBalanceHistoryFiat');
|
||||||
const groupBy = parseInt(document.getElementById("getBalanceHistoryGroupBy").value);
|
const groupBy = parseInt(document.getElementById("getBalanceHistoryGroupBy").value);
|
||||||
const method = 'getBalanceHistory';
|
const method = 'getBalanceHistory';
|
||||||
const params = {
|
const params = {
|
||||||
@ -207,7 +216,7 @@
|
|||||||
|
|
||||||
function estimateFee() {
|
function estimateFee() {
|
||||||
try {
|
try {
|
||||||
var blocks = document.getElementById('estimateFeeBlocks').value.split(",");
|
var blocks = paramAsArray('estimateFeeBlocks');
|
||||||
var specific = document.getElementById('estimateFeeSpecific').value.trim();
|
var specific = document.getElementById('estimateFeeSpecific').value.trim();
|
||||||
if (specific) {
|
if (specific) {
|
||||||
// example for bitcoin type: {"conservative": false,"txsize":1234}
|
// example for bitcoin type: {"conservative": false,"txsize":1234}
|
||||||
@ -299,8 +308,7 @@
|
|||||||
|
|
||||||
function subscribeAddresses() {
|
function subscribeAddresses() {
|
||||||
const method = 'subscribeAddresses';
|
const method = 'subscribeAddresses';
|
||||||
var addresses = document.getElementById('subscribeAddressesName').value.split(",");
|
var addresses = paramAsArray('subscribeAddressesName');
|
||||||
addresses = addresses.map(s => s.trim());
|
|
||||||
const params = {
|
const params = {
|
||||||
addresses
|
addresses
|
||||||
};
|
};
|
||||||
@ -329,12 +337,14 @@
|
|||||||
|
|
||||||
function getFiatRatesForTimestamps() {
|
function getFiatRatesForTimestamps() {
|
||||||
const method = 'getFiatRatesForTimestamps';
|
const method = 'getFiatRatesForTimestamps';
|
||||||
var timestamps = document.getElementById('getFiatRatesForTimestampsList').value.split(",");
|
var timestamps = paramAsArray('getFiatRatesForTimestampsList');
|
||||||
var currencies = document.getElementById('getFiatRatesForTimestampsCurrency').value.split(",");
|
var currencies = paramAsArray('getFiatRatesForTimestampsCurrency');
|
||||||
|
var token = document.getElementById('getFiatRatesForTimestampsToken').value;
|
||||||
timestamps = timestamps.map(Number);
|
timestamps = timestamps.map(Number);
|
||||||
const params = {
|
const params = {
|
||||||
timestamps,
|
timestamps,
|
||||||
'currencies': currencies
|
'currencies': currencies,
|
||||||
|
token,
|
||||||
};
|
};
|
||||||
send(method, params, function (result) {
|
send(method, params, function (result) {
|
||||||
document.getElementById('getFiatRatesForTimestampsResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
document.getElementById('getFiatRatesForTimestampsResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
||||||
@ -343,9 +353,11 @@
|
|||||||
|
|
||||||
function getCurrentFiatRates() {
|
function getCurrentFiatRates() {
|
||||||
const method = 'getCurrentFiatRates';
|
const method = 'getCurrentFiatRates';
|
||||||
var currencies = document.getElementById('getCurrentFiatRatesCurrency').value.split(",");
|
var currencies = paramAsArray('getCurrentFiatRatesCurrency');
|
||||||
|
var token = document.getElementById('getCurrentFiatRatesToken').value;
|
||||||
const params = {
|
const params = {
|
||||||
"currencies": currencies
|
"currencies": currencies,
|
||||||
|
token,
|
||||||
};
|
};
|
||||||
send(method, params, function (result) {
|
send(method, params, function (result) {
|
||||||
document.getElementById('getCurrentFiatRatesResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
document.getElementById('getCurrentFiatRatesResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
||||||
@ -355,9 +367,11 @@
|
|||||||
function getFiatRatesTickersList() {
|
function getFiatRatesTickersList() {
|
||||||
const method = 'getFiatRatesTickersList';
|
const method = 'getFiatRatesTickersList';
|
||||||
var timestamp = document.getElementById('getFiatRatesTickersListDate').value;
|
var timestamp = document.getElementById('getFiatRatesTickersListDate').value;
|
||||||
|
var token = document.getElementById('getFiatRatesTickersToken').value;
|
||||||
timestamp = parseInt(timestamp);
|
timestamp = parseInt(timestamp);
|
||||||
const params = {
|
const params = {
|
||||||
timestamp,
|
timestamp,
|
||||||
|
token,
|
||||||
};
|
};
|
||||||
send(method, params, function (result) {
|
send(method, params, function (result) {
|
||||||
document.getElementById('getFiatRatesTickersListResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
document.getElementById('getFiatRatesTickersListResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
||||||
@ -367,8 +381,10 @@
|
|||||||
function subscribeNewFiatRatesTicker() {
|
function subscribeNewFiatRatesTicker() {
|
||||||
const method = 'subscribeFiatRates';
|
const method = 'subscribeFiatRates';
|
||||||
var currency = document.getElementById('subscribeFiatRatesCurrency').value;
|
var currency = document.getElementById('subscribeFiatRatesCurrency').value;
|
||||||
|
var tokens = paramAsArray('subscribeFiatRatesTokens');
|
||||||
const params = {
|
const params = {
|
||||||
"currency": currency
|
"currency": currency,
|
||||||
|
tokens,
|
||||||
};
|
};
|
||||||
if (subscribeNewFiatRatesTickerId) {
|
if (subscribeNewFiatRatesTickerId) {
|
||||||
delete subscriptions[subscribeNewFiatRatesTickerId];
|
delete subscriptions[subscribeNewFiatRatesTickerId];
|
||||||
@ -473,7 +489,7 @@
|
|||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<div class="row" style="margin: 0;">
|
<div class="row" style="margin: 0;">
|
||||||
<input type="text" placeholder="descriptor" class="form-control" id="getAccountUtxoDescriptor" value="0xba98d6a5ac827632e3457de7512d211e4ff7e8bd">
|
<input type="text" placeholder="descriptor" class="form-control" id="getAccountUtxoDescriptor" value="0xba98d6a5ac827632e3457de7512d211e4ff7e8bd">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col form-inline"></div>
|
<div class="col form-inline"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -509,7 +525,7 @@
|
|||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<div class="row" style="margin: 0;">
|
<div class="row" style="margin: 0;">
|
||||||
<input type="text" placeholder="txid" class="form-control" id="getTransactionTxid" value="0xb266c89f9bfefa4aa2fca4e65b7d6c918d5407f464be781c2803f3546d34a574">
|
<input type="text" placeholder="txid" class="form-control" id="getTransactionTxid" value="0xb266c89f9bfefa4aa2fca4e65b7d6c918d5407f464be781c2803f3546d34a574">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col form-inline"></div>
|
<div class="col form-inline"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -524,7 +540,7 @@
|
|||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<div class="row" style="margin: 0;">
|
<div class="row" style="margin: 0;">
|
||||||
<input type="text" placeholder="txid" class="form-control" id="getTransactionSpecificTxid" value="0xb266c89f9bfefa4aa2fca4e65b7d6c918d5407f464be781c2803f3546d34a574">
|
<input type="text" placeholder="txid" class="form-control" id="getTransactionSpecificTxid" value="0xb266c89f9bfefa4aa2fca4e65b7d6c918d5407f464be781c2803f3546d34a574">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col form-inline"></div>
|
<div class="col form-inline"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -573,6 +589,9 @@
|
|||||||
<div class="col-7">
|
<div class="col-7">
|
||||||
<input type="text" class="form-control" id="getFiatRatesForTimestampsList" value="1575288000,1575550800">
|
<input type="text" class="form-control" id="getFiatRatesForTimestampsList" value="1575288000,1575550800">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-5 offset-3">
|
||||||
|
<input type="text" class="form-control" id="getFiatRatesForTimestampsToken" value="" placeholder="Token address">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col" id="getFiatRatesForTimestampsResult"></div>
|
<div class="col" id="getFiatRatesForTimestampsResult"></div>
|
||||||
@ -584,17 +603,23 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<input type="text" class="form-control" id="getCurrentFiatRatesCurrency" placeholder="usd">
|
<input type="text" class="form-control" id="getCurrentFiatRatesCurrency" placeholder="usd">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<input type="text" class="form-control" id="getCurrentFiatRatesToken" value="" placeholder="Token address">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col" id="getCurrentFiatRatesResult"></div>
|
<div class="col" id="getCurrentFiatRatesResult"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<input class="btn btn-secondary" type="button" value="get fiat rates tickers" onclick="getFiatRatesTickersList()">
|
<input class="btn btn-secondary" type="button" value="get available tickers" onclick="getFiatRatesTickersList()">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-4">
|
||||||
<input type="text" class="form-control" id="getFiatRatesTickersListDate" value="1576591569" placeholder="Unix timestamp">
|
<input type="text" class="form-control" id="getFiatRatesTickersListDate" value="1576591569" placeholder="Unix timestamp">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<input type="text" class="form-control" id="getFiatRatesTickersToken" value="" placeholder="Token address">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col" id="getFiatRatesTickersListResult"></div>
|
<div class="col" id="getFiatRatesTickersListResult"></div>
|
||||||
@ -645,15 +670,18 @@
|
|||||||
<div class="col" id="subscribeAddressesResult"></div>
|
<div class="col" id="subscribeAddressesResult"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3">
|
<div class="col-2">
|
||||||
<input class="btn btn-secondary" type="button" value="subscribe new fiat rates" onclick="subscribeNewFiatRatesTicker()">
|
<input class="btn btn-secondary" type="button" value="subscribe fiat rates" onclick="subscribeNewFiatRatesTicker()">
|
||||||
</div>
|
|
||||||
<div class="col-1">
|
|
||||||
<span id="subscribeNewFiatRatesTickerId"></span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<input type="text" class="form-control" id="subscribeFiatRatesCurrency" value="usd">
|
<input type="text" class="form-control" id="subscribeFiatRatesCurrency" value="usd">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<input type="text" class="form-control" id="subscribeFiatRatesTokens" value="" placeholder="0xdAC17F958D2ee523a2206206994597C13D831ec7,0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2">
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<span id="subscribeNewFiatRatesTickerId"></span>
|
||||||
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<input class="btn btn-secondary" id="unsubscribeNewFiatRatesTickerButton" style="display: none;" type="button" value="unsubscribe" onclick="unsubscribeNewFiatRatesTicker()">
|
<input class="btn btn-secondary" id="unsubscribeNewFiatRatesTickerButton" style="display: none;" type="button" value="unsubscribe" onclick="unsubscribeNewFiatRatesTicker()">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user