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 {
|
||||
bh := &histories[i]
|
||||
t := time.Unix(int64(bh.Time), 0)
|
||||
// TODO
|
||||
ticker, err := w.db.FiatRatesFindTicker(&t, "", "")
|
||||
if err != nil {
|
||||
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
|
||||
func (w *Worker) getFiatRatesResult(currencies []string, ticker *db.CurrencyRatesTicker) (*FiatTicker, error) {
|
||||
currencies = removeEmpty(currencies)
|
||||
func (w *Worker) getFiatRatesResult(currencies []string, ticker *db.CurrencyRatesTicker, token string) (*FiatTicker, error) {
|
||||
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 {
|
||||
// Return all available ticker rates
|
||||
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
|
||||
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
|
||||
bi, err := w.getBlockInfoFromBlockID(blockID)
|
||||
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
|
||||
tm := time.Unix(dbi.Time, 0) // convert it to Time object
|
||||
// TODO
|
||||
ticker, err = w.db.FiatRatesFindTicker(&tm, "", "")
|
||||
vsCurrency := ""
|
||||
currencies = removeEmpty(currencies)
|
||||
if len(currencies) == 1 {
|
||||
vsCurrency = currencies[0]
|
||||
}
|
||||
ticker, err = w.db.FiatRatesFindTicker(&tm, 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 available for %s", tm), true)
|
||||
}
|
||||
result, err := w.getFiatRatesResult(currencies, ticker)
|
||||
result, err := w.getFiatRatesResult(currencies, ticker, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1646,22 +1661,29 @@ func (w *Worker) GetFiatRatesForBlockID(blockID string, currencies []string) (*F
|
||||
}
|
||||
|
||||
// GetCurrentFiatRates returns last available fiat rates
|
||||
func (w *Worker) GetCurrentFiatRates(currencies []string) (*FiatTicker, error) {
|
||||
// TODO
|
||||
ticker, err := w.db.FiatRatesFindLastTicker("", "")
|
||||
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)
|
||||
func (w *Worker) GetCurrentFiatRates(currencies []string, token string) (*FiatTicker, error) {
|
||||
vsCurrency := ""
|
||||
currencies = removeEmpty(currencies)
|
||||
if len(currencies) == 1 {
|
||||
vsCurrency = currencies[0]
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
func makeErrorRates(currencies []string) 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
|
||||
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 {
|
||||
return nil, NewAPIError("No timestamps provided", true)
|
||||
}
|
||||
vsCurrency := ""
|
||||
currencies = removeEmpty(currencies)
|
||||
if len(currencies) == 1 {
|
||||
vsCurrency = currencies[0]
|
||||
}
|
||||
|
||||
ret := &FiatTickers{}
|
||||
for _, timestamp := range timestamps {
|
||||
date := time.Unix(timestamp, 0)
|
||||
date = date.UTC()
|
||||
// TODO
|
||||
ticker, err := w.db.FiatRatesFindTicker(&date, "", "")
|
||||
ticker, err := w.db.FiatRatesFindTicker(&date, vsCurrency, token)
|
||||
if err != nil {
|
||||
glog.Errorf("Error finding ticker for date %v. Error: %v", date, err)
|
||||
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)})
|
||||
continue
|
||||
}
|
||||
result, err := w.getFiatRatesResult(currencies, ticker)
|
||||
result, err := w.getFiatRatesResult(currencies, ticker, token)
|
||||
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)})
|
||||
continue
|
||||
}
|
||||
@ -1703,12 +1733,11 @@ func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []stri
|
||||
}
|
||||
|
||||
// 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 = date.UTC()
|
||||
|
||||
// TODO
|
||||
ticker, err := w.db.FiatRatesFindTicker(&date, "", "")
|
||||
ticker, err := w.db.FiatRatesFindTicker(&date, "", strings.ToLower(token))
|
||||
if err != nil {
|
||||
return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false)
|
||||
} else if ticker == nil {
|
||||
|
||||
@ -525,6 +525,7 @@ func onNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
glog.Error("onNewFiatRatesTicker recovered from panic: ", r)
|
||||
debug.PrintStack()
|
||||
}
|
||||
}()
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return []byte(t.UTC().Format(FiatRatesTimeFormat))
|
||||
}
|
||||
@ -117,6 +160,26 @@ func (d *RocksDB) FiatRatesStoreTicker(wb *gorocksdb.WriteBatch, ticker *Currenc
|
||||
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) {
|
||||
timeObj, err := time.Parse(FiatRatesTimeFormat, string(it.Key().Data()))
|
||||
if err != nil {
|
||||
@ -126,21 +189,8 @@ func getTickerFromIterator(it *gorocksdb.Iterator, vsCurrency string, token stri
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if vsCurrency != "" {
|
||||
if ticker.Rates == 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
|
||||
}
|
||||
if !isSuitableTicker(ticker, vsCurrency, token) {
|
||||
return nil, nil
|
||||
}
|
||||
ticker.Timestamp = timeObj.UTC()
|
||||
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
|
||||
func (d *RocksDB) FiatRatesFindTicker(tickerTime *time.Time, vsCurrency string, token string) (*CurrencyRatesTicker, error) {
|
||||
tickersMux.Lock()
|
||||
if currentTicker != nil && lastTickerInDB != nil {
|
||||
if tickerTime.After(lastTickerInDB.Timestamp) {
|
||||
if currentTicker != nil {
|
||||
if !tickerTime.Before(currentTicker.Timestamp) || (lastTickerInDB != nil && tickerTime.After(lastTickerInDB.Timestamp)) {
|
||||
f := true
|
||||
if token != "" && currentTicker.TokenRates != nil {
|
||||
_, f = currentTicker.TokenRates[token]
|
||||
@ -224,14 +274,17 @@ func (d *RocksDB) FiatRatesFindLastTicker(vsCurrency string, token string) (*Cur
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// FiatRatesGetCurrentTicker return current ticker
|
||||
func (d *RocksDB) FiatRatesGetCurrentTicker(tickerTime *time.Time, token string) (*CurrencyRatesTicker, error) {
|
||||
// FiatRatesGetCurrentTicker returns current ticker
|
||||
func (d *RocksDB) FiatRatesGetCurrentTicker(vsCurrency string, token string) (*CurrencyRatesTicker, error) {
|
||||
tickersMux.Lock()
|
||||
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) {
|
||||
tickersMux.Lock()
|
||||
defer tickersMux.Unlock()
|
||||
|
||||
@ -157,6 +157,24 @@ func TestRocksTickers(t *testing.T) {
|
||||
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) {
|
||||
@ -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
|
||||
httpClient *http.Client
|
||||
db *db.RocksDB
|
||||
updatingCurrent bool
|
||||
updatingTokens bool
|
||||
}
|
||||
|
||||
@ -42,13 +43,17 @@ type coinsListItem struct {
|
||||
// coinList https://api.coingecko.com/api/v3/coins/list
|
||||
type coinList []coinsListItem
|
||||
|
||||
type marketPoint [2]float32
|
||||
type marketPoint [2]float64
|
||||
type marketChartPrices struct {
|
||||
Prices []marketPoint `json:"prices"`
|
||||
}
|
||||
|
||||
// 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
|
||||
return &Coingecko{
|
||||
url: url,
|
||||
@ -95,13 +100,13 @@ func (cg *Coingecko) makeReq(url string) ([]byte, error) {
|
||||
if err == nil {
|
||||
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)
|
||||
return nil, err
|
||||
}
|
||||
// if there is a throttling error, wait 70 seconds and retry
|
||||
glog.Errorf("Coingecko makeReq %v error %v, will retry in 70 seconds", url, err)
|
||||
time.Sleep(70 * time.Second)
|
||||
// if there is a throttling error, wait 60 seconds and retry
|
||||
glog.Errorf("Coingecko makeReq %v error %v, will retry in 60 seconds", url, err)
|
||||
time.Sleep(60 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,6 +224,9 @@ func (cg *Coingecko) platformIds() error {
|
||||
}
|
||||
|
||||
func (cg *Coingecko) CurrentTickers() (*db.CurrencyRatesTicker, error) {
|
||||
cg.updatingCurrent = true
|
||||
defer func() { cg.updatingCurrent = false }()
|
||||
|
||||
var newTickers = db.CurrencyRatesTicker{}
|
||||
|
||||
if vsCurrencies == nil {
|
||||
@ -290,13 +298,12 @@ func (cg *Coingecko) getHistoricalTicker(tickersToUpdate map[uint]*db.CurrencyRa
|
||||
warningLogged := false
|
||||
for _, p := range mc.Prices {
|
||||
var timestamp uint
|
||||
if p[0] > 100000000000 {
|
||||
timestamp = uint(p[0])
|
||||
if timestamp > 100000000000 {
|
||||
// convert timestamp from milliseconds to seconds
|
||||
timestamp = uint(p[0] / 1000)
|
||||
} else {
|
||||
timestamp = uint(p[0])
|
||||
timestamp /= 1000
|
||||
}
|
||||
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
|
||||
var found bool
|
||||
var ticker *db.CurrencyRatesTicker
|
||||
@ -350,6 +357,15 @@ func (cg *Coingecko) storeTickers(tickersToUpdate map[uint]*db.CurrencyRatesTick
|
||||
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
|
||||
func (cg *Coingecko) UpdateHistoricalTickers() error {
|
||||
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)
|
||||
}
|
||||
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))
|
||||
}
|
||||
if req {
|
||||
// long delay next request to avoid throttling
|
||||
time.Sleep(cg.throttlingDelay * 20)
|
||||
cg.throttleHistoricalDownload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,12 +56,12 @@ func NewFiatRatesDownloader(db *db.RocksDB, apiType string, params string, callb
|
||||
rd.db = db
|
||||
rd.callbackOnNewTicker = callback
|
||||
if apiType == "coingecko" {
|
||||
throttlingDelayMs := 50
|
||||
throttle := true
|
||||
if callback == nil {
|
||||
// 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 {
|
||||
return nil, fmt.Errorf("NewFiatRatesDownloader: incorrect API type %q", apiType)
|
||||
}
|
||||
@ -74,11 +74,14 @@ func (rd *RatesDownloader) Run() error {
|
||||
|
||||
for {
|
||||
tickers, err := rd.downloader.CurrentTickers()
|
||||
if err != nil && tickers != nil {
|
||||
if err != nil || tickers == nil {
|
||||
glog.Error("FiatRatesDownloader: CurrentTickers error ", err)
|
||||
} else {
|
||||
rd.db.FiatRatesSetCurrentTicker(tickers)
|
||||
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() {
|
||||
err = rd.downloader.UpdateHistoricalTickers()
|
||||
@ -86,20 +89,24 @@ func (rd *RatesDownloader) Run() error {
|
||||
glog.Error("FiatRatesDownloader: UpdateHistoricalTickers error ", err)
|
||||
} else {
|
||||
lastHistoricalTickers = time.Now().UTC()
|
||||
glog.Info("FiatRatesDownloader: UpdateHistoricalTickers 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)
|
||||
ticker, err := rd.db.FiatRatesFindLastTicker("", "")
|
||||
if err != nil || ticker == nil {
|
||||
glog.Error("FiatRatesDownloader: FiatRatesFindLastTicker error ", err)
|
||||
} else {
|
||||
lastHistoricalTickers = time.Now().UTC()
|
||||
glog.Info("FiatRatesDownloader: UpdateHistoricalTokenTickers finished")
|
||||
glog.Infof("FiatRatesDownloader: UpdateHistoricalTickers finished, last ticker from %v", ticker.Timestamp)
|
||||
}
|
||||
}()
|
||||
// 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()
|
||||
next := now + rd.periodSeconds
|
||||
next -= next % rd.periodSeconds
|
||||
|
||||
@ -1205,7 +1205,8 @@ func (s *PublicServer) apiAvailableVsCurrencies(r *http.Request, apiVersion int)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@ -1219,11 +1220,12 @@ func (s *PublicServer) apiTickers(r *http.Request, apiVersion int) (interface{},
|
||||
if currency != "" {
|
||||
currencies = []string{currency}
|
||||
}
|
||||
token := strings.ToLower(r.URL.Query().Get("token"))
|
||||
|
||||
if block := r.URL.Query().Get("block"); block != "" {
|
||||
// Get tickers for specified block height or block hash
|
||||
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 != "" {
|
||||
// Get tickers for specified timestamp
|
||||
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)
|
||||
}
|
||||
|
||||
resultTickers, err := s.api.GetFiatRatesForTimestamps([]int64{timestamp}, currencies)
|
||||
resultTickers, err := s.api.GetFiatRatesForTimestamps([]int64{timestamp}, currencies, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1241,7 +1243,7 @@ func (s *PublicServer) apiTickers(r *http.Request, apiVersion int) (interface{},
|
||||
} else {
|
||||
// No parameters - get the latest available ticker
|
||||
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 {
|
||||
return nil, err
|
||||
@ -1259,6 +1261,7 @@ func (s *PublicServer) apiMultiTickers(r *http.Request, apiVersion int) (interfa
|
||||
if currency != "" {
|
||||
currencies = []string{currency}
|
||||
}
|
||||
token := strings.ToLower(r.URL.Query().Get("token"))
|
||||
if timestampString := r.URL.Query().Get("timestamp"); timestampString != "" {
|
||||
// Get tickers for specified timestamp
|
||||
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)
|
||||
}
|
||||
}
|
||||
resultTickers, err := s.api.GetFiatRatesForTimestamps(t, currencies)
|
||||
resultTickers, err := s.api.GetFiatRatesForTimestamps(t, currencies, token)
|
||||
if err != nil {
|
||||
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"}}}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
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)
|
||||
@ -103,6 +139,64 @@ func initEthereumTypeDB(d *db.RocksDB) error {
|
||||
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) {
|
||||
parser := eth.NewEthereumParser(1, true)
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := initTestFiatRates(d); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
is.FinishedSync(block2.Height)
|
||||
if parser.GetChainType() == bchain.ChainEthereumType {
|
||||
if err := initTestFiatRatesEthereumType(d); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := initEthereumTypeDB(d); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if err := initTestFiatRates(d); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return d, is, tmp
|
||||
}
|
||||
@ -162,14 +166,15 @@ func newPostRequest(u string, body string) *http.Request {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ticker := &db.CurrencyRatesTicker{
|
||||
Timestamp: *convertedDate,
|
||||
Rates: rates,
|
||||
Timestamp: *convertedDate,
|
||||
Rates: rates,
|
||||
TokenRates: tokenRates,
|
||||
}
|
||||
wb := gorocksdb.NewWriteBatch()
|
||||
defer wb.Destroy()
|
||||
@ -184,37 +189,37 @@ func initTestFiatRates(d *db.RocksDB) error {
|
||||
if err := insertFiatRate("20180320020000", map[string]float32{
|
||||
"usd": 2000.0,
|
||||
"eur": 1300.0,
|
||||
}, d); err != nil {
|
||||
}, nil, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertFiatRate("20180320030000", map[string]float32{
|
||||
"usd": 2001.0,
|
||||
"eur": 1301.0,
|
||||
}, d); err != nil {
|
||||
}, nil, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertFiatRate("20180320040000", map[string]float32{
|
||||
"usd": 2002.0,
|
||||
"eur": 1302.0,
|
||||
}, d); err != nil {
|
||||
}, nil, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertFiatRate("20180321055521", map[string]float32{
|
||||
"usd": 2003.0,
|
||||
"eur": 1303.0,
|
||||
}, d); err != nil {
|
||||
}, nil, d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertFiatRate("20191121140000", map[string]float32{
|
||||
"usd": 7814.5,
|
||||
"eur": 7100.0,
|
||||
}, d); err != nil {
|
||||
}, nil, d); err != nil {
|
||||
return err
|
||||
}
|
||||
return insertFiatRate("20191121143015", map[string]float32{
|
||||
"usd": 7914.5,
|
||||
"eur": 7134.1,
|
||||
}, d)
|
||||
}, nil, d)
|
||||
}
|
||||
|
||||
type httpTests struct {
|
||||
@ -1332,7 +1337,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
||||
"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",
|
||||
|
||||
@ -56,6 +56,11 @@ type websocketChannel struct {
|
||||
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
|
||||
type WebsocketServer struct {
|
||||
socket *websocket.Conn
|
||||
@ -77,6 +82,7 @@ type WebsocketServer struct {
|
||||
addressSubscriptions map[string]map[*websocketChannel]string
|
||||
addressSubscriptionsLock sync.Mutex
|
||||
fiatRatesSubscriptions map[string]map[*websocketChannel]string
|
||||
fiatRatesTokenSubscriptions map[*websocketChannel][]string
|
||||
fiatRatesSubscriptionsLock sync.Mutex
|
||||
}
|
||||
|
||||
@ -110,6 +116,7 @@ func NewWebsocketServer(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.
|
||||
newTransactionSubscriptions: make(map[*websocketChannel]string),
|
||||
addressSubscriptions: make(map[string]map[*websocketChannel]string),
|
||||
fiatRatesSubscriptions: make(map[string]map[*websocketChannel]string),
|
||||
fiatRatesTokenSubscriptions: make(map[*websocketChannel][]string),
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@ -378,14 +385,16 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
|
||||
return s.unsubscribeAddresses(c)
|
||||
},
|
||||
"subscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
||||
r := struct {
|
||||
Currency string `json:"currency"`
|
||||
}{}
|
||||
var r fiatRatesSubscription
|
||||
err = json.Unmarshal(req.Params, &r)
|
||||
if err != nil {
|
||||
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) {
|
||||
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) {
|
||||
r := struct {
|
||||
Currencies []string `json:"currencies"`
|
||||
Token string `json:"token"`
|
||||
}{}
|
||||
err = json.Unmarshal(req.Params, &r)
|
||||
if err == nil {
|
||||
rv, err = s.getCurrentFiatRates(r.Currencies)
|
||||
rv, err = s.getCurrentFiatRates(r.Currencies, r.Token)
|
||||
}
|
||||
return
|
||||
},
|
||||
@ -408,20 +418,22 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
|
||||
r := struct {
|
||||
Timestamps []int64 `json:"timestamps"`
|
||||
Currencies []string `json:"currencies"`
|
||||
Token string `json:"token"`
|
||||
}{}
|
||||
err = json.Unmarshal(req.Params, &r)
|
||||
if err == nil {
|
||||
rv, err = s.getFiatRatesForTimestamps(r.Timestamps, r.Currencies)
|
||||
rv, err = s.getFiatRatesForTimestamps(r.Timestamps, r.Currencies, r.Token)
|
||||
}
|
||||
return
|
||||
},
|
||||
"getFiatRatesTickersList": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
|
||||
r := struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Token string `json:"token"`
|
||||
}{}
|
||||
err = json.Unmarshal(req.Params, &r)
|
||||
if err == nil {
|
||||
rv, err = s.getAvailableVsCurrencies(r.Timestamp)
|
||||
rv, err = s.getAvailableVsCurrencies(r.Timestamp, r.Token)
|
||||
}
|
||||
return
|
||||
},
|
||||
@ -799,14 +811,16 @@ func (s *WebsocketServer) doUnsubscribeFiatRates(c *websocketChannel) {
|
||||
delete(s.fiatRatesSubscriptions, fr)
|
||||
}
|
||||
}
|
||||
delete(s.fiatRatesTokenSubscriptions, c)
|
||||
}
|
||||
|
||||
// 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()
|
||||
defer s.fiatRatesSubscriptionsLock.Unlock()
|
||||
// unsubscribe all previous subscriptions
|
||||
s.doUnsubscribeFiatRates(c)
|
||||
currency := d.Currency
|
||||
if currency == "" {
|
||||
currency = allFiatRates
|
||||
}
|
||||
@ -816,6 +830,9 @@ func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, currency strin
|
||||
s.fiatRatesSubscriptions[currency] = as
|
||||
}
|
||||
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)))
|
||||
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]
|
||||
if ok && len(as) > 0 {
|
||||
data := struct {
|
||||
@ -969,10 +986,34 @@ func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]floa
|
||||
Rates: rates,
|
||||
}
|
||||
for c, id := range as {
|
||||
c.DataOut(&websocketRes{
|
||||
ID: id,
|
||||
Data: &data,
|
||||
})
|
||||
var tokens []string
|
||||
if ticker != nil {
|
||||
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")
|
||||
}
|
||||
@ -983,22 +1024,22 @@ func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *db.CurrencyRatesTicker) {
|
||||
s.fiatRatesSubscriptionsLock.Lock()
|
||||
defer s.fiatRatesSubscriptionsLock.Unlock()
|
||||
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) {
|
||||
ret, err := s.api.GetCurrentFiatRates(currencies)
|
||||
func (s *WebsocketServer) getCurrentFiatRates(currencies []string, token string) (interface{}, error) {
|
||||
ret, err := s.api.GetCurrentFiatRates(currencies, strings.ToLower(token))
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string) (interface{}, error) {
|
||||
ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies)
|
||||
func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string, token string) (interface{}, error) {
|
||||
ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies, strings.ToLower(token))
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (s *WebsocketServer) getAvailableVsCurrencies(timestamp int64) (interface{}, error) {
|
||||
ret, err := s.api.GetAvailableVsCurrencies(timestamp)
|
||||
func (s *WebsocketServer) getAvailableVsCurrencies(timestamp int64, token string) (interface{}, error) {
|
||||
ret, err := s.api.GetAvailableVsCurrencies(timestamp, strings.ToLower(token))
|
||||
return ret, err
|
||||
}
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
<style>
|
||||
.row {
|
||||
margin-top: 1%;
|
||||
}
|
||||
::placeholder {
|
||||
color: #ccc !important;
|
||||
}
|
||||
</style>
|
||||
<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() {
|
||||
const method = 'getInfo';
|
||||
@ -166,7 +175,7 @@
|
||||
const descriptor = document.getElementById('getBalanceHistoryDescriptor').value.trim();
|
||||
const from = parseInt(document.getElementById("getBalanceHistoryFrom").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 method = 'getBalanceHistory';
|
||||
const params = {
|
||||
@ -207,7 +216,7 @@
|
||||
|
||||
function estimateFee() {
|
||||
try {
|
||||
var blocks = document.getElementById('estimateFeeBlocks').value.split(",");
|
||||
var blocks = paramAsArray('estimateFeeBlocks');
|
||||
var specific = document.getElementById('estimateFeeSpecific').value.trim();
|
||||
if (specific) {
|
||||
// example for bitcoin type: {"conservative": false,"txsize":1234}
|
||||
@ -299,8 +308,7 @@
|
||||
|
||||
function subscribeAddresses() {
|
||||
const method = 'subscribeAddresses';
|
||||
var addresses = document.getElementById('subscribeAddressesName').value.split(",");
|
||||
addresses = addresses.map(s => s.trim());
|
||||
var addresses = paramAsArray('subscribeAddressesName');
|
||||
const params = {
|
||||
addresses
|
||||
};
|
||||
@ -329,12 +337,14 @@
|
||||
|
||||
function getFiatRatesForTimestamps() {
|
||||
const method = 'getFiatRatesForTimestamps';
|
||||
var timestamps = document.getElementById('getFiatRatesForTimestampsList').value.split(",");
|
||||
var currencies = document.getElementById('getFiatRatesForTimestampsCurrency').value.split(",");
|
||||
var timestamps = paramAsArray('getFiatRatesForTimestampsList');
|
||||
var currencies = paramAsArray('getFiatRatesForTimestampsCurrency');
|
||||
var token = document.getElementById('getFiatRatesForTimestampsToken').value;
|
||||
timestamps = timestamps.map(Number);
|
||||
const params = {
|
||||
timestamps,
|
||||
'currencies': currencies
|
||||
'currencies': currencies,
|
||||
token,
|
||||
};
|
||||
send(method, params, function (result) {
|
||||
document.getElementById('getFiatRatesForTimestampsResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
||||
@ -343,9 +353,11 @@
|
||||
|
||||
function getCurrentFiatRates() {
|
||||
const method = 'getCurrentFiatRates';
|
||||
var currencies = document.getElementById('getCurrentFiatRatesCurrency').value.split(",");
|
||||
var currencies = paramAsArray('getCurrentFiatRatesCurrency');
|
||||
var token = document.getElementById('getCurrentFiatRatesToken').value;
|
||||
const params = {
|
||||
"currencies": currencies
|
||||
"currencies": currencies,
|
||||
token,
|
||||
};
|
||||
send(method, params, function (result) {
|
||||
document.getElementById('getCurrentFiatRatesResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
||||
@ -355,9 +367,11 @@
|
||||
function getFiatRatesTickersList() {
|
||||
const method = 'getFiatRatesTickersList';
|
||||
var timestamp = document.getElementById('getFiatRatesTickersListDate').value;
|
||||
var token = document.getElementById('getFiatRatesTickersToken').value;
|
||||
timestamp = parseInt(timestamp);
|
||||
const params = {
|
||||
timestamp,
|
||||
token,
|
||||
};
|
||||
send(method, params, function (result) {
|
||||
document.getElementById('getFiatRatesTickersListResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
||||
@ -367,8 +381,10 @@
|
||||
function subscribeNewFiatRatesTicker() {
|
||||
const method = 'subscribeFiatRates';
|
||||
var currency = document.getElementById('subscribeFiatRatesCurrency').value;
|
||||
var tokens = paramAsArray('subscribeFiatRatesTokens');
|
||||
const params = {
|
||||
"currency": currency
|
||||
"currency": currency,
|
||||
tokens,
|
||||
};
|
||||
if (subscribeNewFiatRatesTickerId) {
|
||||
delete subscriptions[subscribeNewFiatRatesTickerId];
|
||||
@ -473,7 +489,7 @@
|
||||
<div class="col-8">
|
||||
<div class="row" style="margin: 0;">
|
||||
<input type="text" placeholder="descriptor" class="form-control" id="getAccountUtxoDescriptor" value="0xba98d6a5ac827632e3457de7512d211e4ff7e8bd">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col form-inline"></div>
|
||||
</div>
|
||||
@ -509,7 +525,7 @@
|
||||
<div class="col-8">
|
||||
<div class="row" style="margin: 0;">
|
||||
<input type="text" placeholder="txid" class="form-control" id="getTransactionTxid" value="0xb266c89f9bfefa4aa2fca4e65b7d6c918d5407f464be781c2803f3546d34a574">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col form-inline"></div>
|
||||
</div>
|
||||
@ -524,7 +540,7 @@
|
||||
<div class="col-8">
|
||||
<div class="row" style="margin: 0;">
|
||||
<input type="text" placeholder="txid" class="form-control" id="getTransactionSpecificTxid" value="0xb266c89f9bfefa4aa2fca4e65b7d6c918d5407f464be781c2803f3546d34a574">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col form-inline"></div>
|
||||
</div>
|
||||
@ -573,6 +589,9 @@
|
||||
<div class="col-7">
|
||||
<input type="text" class="form-control" id="getFiatRatesForTimestampsList" value="1575288000,1575550800">
|
||||
</div>
|
||||
<div class="col-5 offset-3">
|
||||
<input type="text" class="form-control" id="getFiatRatesForTimestampsToken" value="" placeholder="Token address">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col" id="getFiatRatesForTimestampsResult"></div>
|
||||
@ -584,17 +603,23 @@
|
||||
<div class="col-1">
|
||||
<input type="text" class="form-control" id="getCurrentFiatRatesCurrency" placeholder="usd">
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<input type="text" class="form-control" id="getCurrentFiatRatesToken" value="" placeholder="Token address">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col" id="getCurrentFiatRatesResult"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<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 class="col-8">
|
||||
<div class="col-4">
|
||||
<input type="text" class="form-control" id="getFiatRatesTickersListDate" value="1576591569" placeholder="Unix timestamp">
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<input type="text" class="form-control" id="getFiatRatesTickersToken" value="" placeholder="Token address">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col" id="getFiatRatesTickersListResult"></div>
|
||||
@ -645,15 +670,18 @@
|
||||
<div class="col" id="subscribeAddressesResult"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<input class="btn btn-secondary" type="button" value="subscribe new fiat rates" onclick="subscribeNewFiatRatesTicker()">
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<span id="subscribeNewFiatRatesTickerId"></span>
|
||||
<div class="col-2">
|
||||
<input class="btn btn-secondary" type="button" value="subscribe fiat rates" onclick="subscribeNewFiatRatesTicker()">
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="text" class="form-control" id="subscribeFiatRatesCurrency" value="usd">
|
||||
</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">
|
||||
<input class="btn btn-secondary" id="unsubscribeNewFiatRatesTickerButton" style="display: none;" type="button" value="unsubscribe" onclick="unsubscribeNewFiatRatesTicker()">
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user