github.com/diadata-org/diadata@v1.4.593/pkg/http/restServer/diaApi/diaApi.go (about)

     1  package diaApi
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math"
     7  	"net/http"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/diadata-org/diadata/pkg/dia"
    14  	"github.com/diadata-org/diadata/pkg/dia/helpers/gqlclient"
    15  	"github.com/diadata-org/diadata/pkg/http/restApi"
    16  	models "github.com/diadata-org/diadata/pkg/model"
    17  	"github.com/diadata-org/diadata/pkg/utils"
    18  	"github.com/ethereum/go-ethereum/common"
    19  	"github.com/gin-gonic/gin"
    20  	"github.com/go-redis/redis"
    21  	log "github.com/sirupsen/logrus"
    22  )
    23  
    24  var (
    25  	DECIMALS_CACHE                = make(map[dia.Asset]uint8)
    26  	ASSET_CACHE                   = make(map[string]dia.Asset)
    27  	QUOTATION_CACHE               = make(map[string]*models.AssetQuotation)
    28  	BLOCKCHAINS                   = make(map[string]dia.BlockChain)
    29  	ASSETQUOTATION_LOOKBACK_HOURS = 24 * 7
    30  )
    31  
    32  type Env struct {
    33  	DataStore models.Datastore
    34  	RelDB     models.RelDB
    35  	signer    *utils.AssetQuotationSigner
    36  }
    37  
    38  func init() {
    39  	relDB, err := models.NewRelDataStore()
    40  	if err != nil {
    41  		log.Errorln("Error connecting to asset DB: ", err)
    42  		return
    43  	}
    44  	chains, err := relDB.GetAllBlockchains(false)
    45  	if err != nil {
    46  		log.Fatal("get all chains: ", err)
    47  	}
    48  	for _, chain := range chains {
    49  		BLOCKCHAINS[chain.Name] = chain
    50  	}
    51  }
    52  
    53  func NewEnv(ds models.Datastore, rdb models.RelDB, signer *utils.AssetQuotationSigner) *Env {
    54  	return &Env{DataStore: ds, RelDB: rdb, signer: signer}
    55  }
    56  
    57  // GetAssetQuotation returns quotation of asset with highest market cap among
    58  // all assets with symbol ticker @symbol.
    59  func (env *Env) GetAssetQuotation(c *gin.Context) {
    60  	if !validateInputParams(c) {
    61  		return
    62  	}
    63  
    64  	blockchain := c.Param("blockchain")
    65  	address := normalizeAddress(c.Param("address"), blockchain)
    66  
    67  	var (
    68  		err               error
    69  		asset             dia.Asset
    70  		quotationExtended models.AssetQuotationFull
    71  	)
    72  
    73  	// Time for quotation is now by default.
    74  	timestampInt, err := strconv.ParseInt(c.DefaultQuery("timestamp", strconv.Itoa(int(time.Now().Unix()))), 10, 64)
    75  	if err != nil {
    76  		restApi.SendError(c, http.StatusNotFound, errors.New("could not parse Unix timestamp"))
    77  		return
    78  	}
    79  	timestamp := time.Unix(timestampInt, 0)
    80  
    81  	// An asset is uniquely defined by blockchain and address.
    82  	asset, err = env.RelDB.GetAsset(address, blockchain)
    83  	if err != nil {
    84  		restApi.SendError(c, http.StatusNotFound, err)
    85  		return
    86  	}
    87  
    88  	// Get quotation for asset.
    89  	quotation, err := env.DataStore.GetAssetQuotation(asset, dia.CRYPTO_ZERO_UNIX_TIME, timestamp)
    90  	if err != nil {
    91  		restApi.SendError(c, http.StatusNotFound, err)
    92  		return
    93  	}
    94  
    95  	quotationYesterday, err := env.DataStore.GetAssetQuotation(asset, dia.CRYPTO_ZERO_UNIX_TIME, timestamp.AddDate(0, 0, -1))
    96  	if err != nil {
    97  		log.Warn("get quotation yesterday: ", err)
    98  	} else {
    99  		quotationExtended.PriceYesterday = quotationYesterday.Price
   100  	}
   101  
   102  	volumeYesterday, err := env.DataStore.Get24HoursAssetVolume(asset)
   103  	if err != nil {
   104  		log.Warn("get volume yesterday: ", err)
   105  	} else {
   106  		quotationExtended.VolumeYesterdayUSD = *volumeYesterday
   107  	}
   108  
   109  	// Appropriate formatting.
   110  	quotationExtended.Symbol = quotation.Asset.Symbol
   111  	quotationExtended.Name = quotation.Asset.Name
   112  	quotationExtended.Address = quotation.Asset.Address
   113  	quotationExtended.Blockchain = quotation.Asset.Blockchain
   114  	quotationExtended.Price = quotation.Price
   115  	quotationExtended.Time = quotation.Time
   116  	quotationExtended.Source = quotation.Source
   117  
   118  	signedData, err := env.signer.Sign(quotation.Asset.Symbol, quotation.Asset.Address, quotation.Asset.Blockchain, quotation.Price, quotationExtended.Time)
   119  	if err != nil {
   120  		log.Warn("error signing data: ", err)
   121  	}
   122  	quotationExtended.Signature = signedData
   123  
   124  	c.JSON(http.StatusOK, quotationExtended)
   125  
   126  }
   127  
   128  // GetQuotation returns quotation of asset with highest market cap among
   129  // all assets with symbol ticker @symbol.
   130  func (env *Env) GetQuotation(c *gin.Context) {
   131  	if !validateInputParams(c) {
   132  		return
   133  	}
   134  
   135  	symbol := c.Param("symbol")
   136  
   137  	timestamp := time.Now()
   138  	var quotationExtended models.AssetQuotationFull
   139  	// Fetch underlying assets for symbol
   140  	assets, err := env.RelDB.GetTopAssetByVolume(symbol)
   141  	if err != nil {
   142  		restApi.SendError(c, http.StatusNotFound, err)
   143  		return
   144  	}
   145  	if len(assets) == 0 {
   146  		restApi.SendError(c, http.StatusNotFound, errors.New("no quotation available"))
   147  		return
   148  	}
   149  	topAsset := assets[0]
   150  	quotation, err := env.DataStore.GetAssetQuotation(topAsset, dia.CRYPTO_ZERO_UNIX_TIME, timestamp)
   151  	if err != nil {
   152  		restApi.SendError(c, http.StatusNotFound, errors.New("no quotation available"))
   153  		return
   154  	}
   155  	quotationYesterday, err := env.DataStore.GetAssetQuotation(topAsset, dia.CRYPTO_ZERO_UNIX_TIME, timestamp.AddDate(0, 0, -1))
   156  	if err != nil {
   157  		log.Warn("get quotation yesterday: ", err)
   158  	} else {
   159  		quotationExtended.PriceYesterday = quotationYesterday.Price
   160  	}
   161  	volumeYesterday, err := env.DataStore.Get24HoursAssetVolume(topAsset)
   162  	if err != nil {
   163  		log.Warn("get volume yesterday: ", err)
   164  	} else {
   165  		quotationExtended.VolumeYesterdayUSD = *volumeYesterday
   166  	}
   167  	quotationExtended.Symbol = quotation.Asset.Symbol
   168  	quotationExtended.Name = quotation.Asset.Name
   169  	quotationExtended.Address = quotation.Asset.Address
   170  	quotationExtended.Blockchain = quotation.Asset.Blockchain
   171  	quotationExtended.Price = quotation.Price
   172  	quotationExtended.Time = quotation.Time
   173  	quotationExtended.Source = quotation.Source
   174  
   175  	c.JSON(http.StatusOK, quotationExtended)
   176  }
   177  
   178  func (env *Env) GetAssetMap(c *gin.Context) {
   179  	if !validateInputParams(c) {
   180  		return
   181  	}
   182  
   183  	blockchain := c.Param("blockchain")
   184  	address := normalizeAddress(c.Param("address"), blockchain)
   185  
   186  	timestamp := time.Now()
   187  	var quotations []models.AssetQuotationFull
   188  	// Fetch underlying assets for symbol
   189  	asset, err := env.RelDB.GetAsset(address, blockchain)
   190  	if err != nil {
   191  		restApi.SendError(c, http.StatusNotFound, err)
   192  		return
   193  	}
   194  
   195  	// get assetid
   196  	assetid, err := env.RelDB.GetAssetID(asset)
   197  	if err != nil {
   198  		restApi.SendError(c, http.StatusNotFound, err)
   199  		return
   200  	}
   201  
   202  	// get groupId
   203  	group_id, err := env.RelDB.GetAssetMap(assetid)
   204  	if err != nil {
   205  		restApi.SendError(c, http.StatusNotFound, err)
   206  		return
   207  	}
   208  
   209  	assets, err := env.RelDB.GetAssetByGroupID(group_id)
   210  	if err != nil || len(assets) == 0 {
   211  		restApi.SendError(c, http.StatusNotFound, errors.New("no quotation available"))
   212  		return
   213  	}
   214  
   215  	for _, topAsset := range assets {
   216  		var quotationExtended models.AssetQuotationFull
   217  
   218  		quotation, err := env.DataStore.GetAssetQuotation(topAsset, dia.CRYPTO_ZERO_UNIX_TIME, timestamp)
   219  		if err != nil {
   220  			log.Warn("get quotation: ", err)
   221  		}
   222  		quotationYesterday, err := env.DataStore.GetAssetQuotation(topAsset, dia.CRYPTO_ZERO_UNIX_TIME, timestamp.AddDate(0, 0, -1))
   223  		if err != nil {
   224  			log.Warn("get quotation yesterday: ", err)
   225  		} else {
   226  			quotationExtended.PriceYesterday = quotationYesterday.Price
   227  		}
   228  		volumeYesterday, err := env.RelDB.GetLastAssetVolume24H(topAsset)
   229  		if err != nil {
   230  			log.Warn("get volume yesterday: ", err)
   231  		} else {
   232  			quotationExtended.VolumeYesterdayUSD = volumeYesterday
   233  		}
   234  		quotationExtended.Symbol = topAsset.Symbol
   235  		quotationExtended.Name = topAsset.Name
   236  		quotationExtended.Address = topAsset.Address
   237  		quotationExtended.Blockchain = topAsset.Blockchain
   238  		quotationExtended.Price = quotation.Price
   239  		quotationExtended.Time = quotation.Time
   240  		quotationExtended.Source = quotation.Source
   241  		quotations = append(quotations, quotationExtended)
   242  	}
   243  
   244  	c.JSON(http.StatusOK, quotations)
   245  }
   246  
   247  // GetSupply returns latest supply of token with @symbol
   248  func (env *Env) GetSupply(c *gin.Context) {
   249  	if !validateInputParams(c) {
   250  		return
   251  	}
   252  
   253  	symbol := c.Param("symbol")
   254  
   255  	s, err := env.DataStore.GetLatestSupply(symbol, &env.RelDB)
   256  	if err != nil {
   257  		if errors.Is(err, redis.Nil) {
   258  			restApi.SendError(c, http.StatusNotFound, err)
   259  		} else {
   260  			restApi.SendError(c, http.StatusInternalServerError, err)
   261  		}
   262  	} else {
   263  		c.JSON(http.StatusOK, s)
   264  	}
   265  }
   266  
   267  // GetSupply returns latest supply of token with @symbol
   268  func (env *Env) GetAssetSupply(c *gin.Context) {
   269  	if !validateInputParams(c) {
   270  		return
   271  	}
   272  
   273  	blockchain := c.Param("blockchain")
   274  	address := normalizeAddress(c.Param("address"), blockchain)
   275  
   276  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(24*time.Hour))
   277  	if err != nil {
   278  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("parse time range"))
   279  		return
   280  	}
   281  
   282  	if ok := utils.ValidTimeRange(starttime, endtime, time.Duration(30*24*time.Hour)); !ok {
   283  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("time-range too big. max duration is %v", 30*24*time.Hour))
   284  		return
   285  	}
   286  
   287  	values, err := env.DataStore.GetSupplyInflux(dia.Asset{Address: address, Blockchain: blockchain}, starttime, endtime)
   288  	if err != nil {
   289  		if errors.Is(err, redis.Nil) {
   290  			restApi.SendError(c, http.StatusNotFound, err)
   291  			return
   292  		} else {
   293  			restApi.SendError(c, http.StatusInternalServerError, err)
   294  			return
   295  		}
   296  	}
   297  
   298  	// Fetch decimals from local cache implementation.
   299  	for i := range values {
   300  		values[i].Asset.Decimals = env.getDecimalsFromCache(DECIMALS_CACHE, values[i].Asset)
   301  	}
   302  
   303  	if len(values) == 1 {
   304  		c.JSON(http.StatusOK, values[0])
   305  	} else {
   306  		c.JSON(http.StatusOK, values)
   307  	}
   308  
   309  }
   310  
   311  // GetSupplies returns a time range of supplies of token with @symbol
   312  func (env *Env) GetSupplies(c *gin.Context) {
   313  	if !validateInputParams(c) {
   314  		return
   315  	}
   316  
   317  	symbol := c.Param("symbol")
   318  	starttimeStr := c.DefaultQuery("starttime", "noRange")
   319  	endtimeStr := c.Query("endtime")
   320  
   321  	var starttime, endtime time.Time
   322  
   323  	if starttimeStr == "noRange" || endtimeStr == "" {
   324  		endtime = time.Now()
   325  		starttime = endtime.AddDate(0, 0, -30)
   326  	} else {
   327  		starttimeInt, err := strconv.ParseInt(starttimeStr, 10, 64)
   328  		if err != nil {
   329  			restApi.SendError(c, http.StatusInternalServerError, err)
   330  			return
   331  		}
   332  		starttime = time.Unix(starttimeInt, 0)
   333  		endtimeInt, err := strconv.ParseInt(endtimeStr, 10, 64)
   334  		if err != nil {
   335  			restApi.SendError(c, http.StatusInternalServerError, err)
   336  			return
   337  		}
   338  		endtime = time.Unix(endtimeInt, 0)
   339  	}
   340  	if ok := utils.ValidTimeRange(starttime, endtime, time.Duration(30*24*time.Hour)); !ok {
   341  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("time-range too big. max duration is %v", 30*24*time.Hour))
   342  		return
   343  	}
   344  
   345  	s, err := env.DataStore.GetSupply(symbol, starttime, endtime, &env.RelDB)
   346  	if len(s) == 0 {
   347  		c.JSON(http.StatusOK, make([]string, 0))
   348  		return
   349  	}
   350  	if err != nil {
   351  		restApi.SendError(c, http.StatusInternalServerError, err)
   352  		return
   353  	}
   354  	c.JSON(http.StatusOK, s)
   355  }
   356  
   357  func (env *Env) GetDiaTotalSupply(c *gin.Context) {
   358  	q, err := env.DataStore.GetDiaTotalSupply()
   359  	if err != nil {
   360  		if errors.Is(err, redis.Nil) {
   361  			restApi.SendError(c, http.StatusNotFound, err)
   362  		} else {
   363  			restApi.SendError(c, http.StatusInternalServerError, err)
   364  		}
   365  	} else {
   366  		c.JSON(http.StatusOK, q)
   367  	}
   368  }
   369  
   370  func (env *Env) GetDiaCirculatingSupply(c *gin.Context) {
   371  	q, err := env.DataStore.GetDiaCirculatingSupply()
   372  	if err != nil {
   373  		if errors.Is(err, redis.Nil) {
   374  			restApi.SendError(c, http.StatusNotFound, err)
   375  		} else {
   376  			restApi.SendError(c, http.StatusInternalServerError, err)
   377  		}
   378  	} else {
   379  		c.JSON(http.StatusOK, q)
   380  	}
   381  }
   382  
   383  // Get24hVolume if no times are set use the last 24h
   384  func (env *Env) Get24hVolume(c *gin.Context) {
   385  	if !validateInputParams(c) {
   386  		return
   387  	}
   388  
   389  	v, err := env.DataStore.Get24HoursExchangeVolume(c.Param("exchange"))
   390  	if err != nil {
   391  		restApi.SendError(c, http.StatusInternalServerError, err)
   392  		return
   393  	}
   394  	c.JSON(http.StatusOK, *v)
   395  }
   396  
   397  // GetExchanges is the delegate method for fetching all exchanges available in Postgres.
   398  func (env *Env) GetExchanges(c *gin.Context) {
   399  
   400  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(24*time.Hour))
   401  	if err != nil {
   402  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("parse time range"))
   403  		return
   404  	}
   405  	if starttime.Before(endtime.AddDate(0, 0, -30)) {
   406  		restApi.SendError(c, http.StatusInternalServerError, errors.New("time range is limited to 30 days"))
   407  		return
   408  	}
   409  
   410  	type exchangeReturn struct {
   411  		Name          string
   412  		Volume24h     float64
   413  		Trades        int64
   414  		Pairs         int
   415  		Type          string
   416  		Blockchain    string
   417  		ScraperActive bool
   418  	}
   419  	var exchangereturns []exchangeReturn
   420  	exchanges, err := env.RelDB.GetAllExchanges()
   421  	if len(exchanges) == 0 || err != nil {
   422  		restApi.SendError(c, http.StatusInternalServerError, nil)
   423  	}
   424  
   425  	dexPoolCountMap, err := env.RelDB.GetAllDEXPoolsCount()
   426  	if err != nil {
   427  		restApi.SendError(c, http.StatusInternalServerError, nil)
   428  	}
   429  
   430  	for _, exchange := range exchanges {
   431  		var numPairs int
   432  
   433  		vol, err := env.DataStore.GetVolumeInflux(dia.Asset{}, exchange.Name, starttime, endtime)
   434  		if err != nil {
   435  			restApi.SendError(c, http.StatusInternalServerError, err)
   436  			return
   437  		}
   438  
   439  		numTrades, err := env.DataStore.GetNumTrades(exchange.Name, "", "", starttime, endtime)
   440  		if err != nil {
   441  			restApi.SendError(c, http.StatusInternalServerError, err)
   442  			return
   443  		}
   444  
   445  		if models.GetExchangeType(exchange) == "DEX" {
   446  			numPairs = dexPoolCountMap[exchange.Name]
   447  		} else {
   448  			numPairs, err = env.RelDB.GetNumPairs(exchange)
   449  			if err != nil {
   450  				restApi.SendError(c, http.StatusInternalServerError, nil)
   451  			}
   452  		}
   453  
   454  		exchangereturn := exchangeReturn{
   455  			Name:          exchange.Name,
   456  			Volume24h:     *vol,
   457  			Trades:        numTrades,
   458  			Pairs:         numPairs,
   459  			Blockchain:    exchange.BlockChain.Name,
   460  			ScraperActive: exchange.ScraperActive,
   461  		}
   462  		exchangereturn.Type = models.GetExchangeType(exchange)
   463  		exchangereturns = append(exchangereturns, exchangereturn)
   464  
   465  	}
   466  
   467  	sort.Slice(exchangereturns, func(i, j int) bool {
   468  		return exchangereturns[i].Volume24h > exchangereturns[j].Volume24h
   469  	})
   470  
   471  	c.JSON(http.StatusOK, exchangereturns)
   472  }
   473  
   474  // GetAssetChartPoints queries for filter points of asset given by address and blockchain.
   475  func (env *Env) GetAssetChartPoints(c *gin.Context) {
   476  	if !validateInputParams(c) {
   477  		return
   478  	}
   479  
   480  	filter := c.Param("filter")
   481  	blockchain := c.Param("blockchain")
   482  	address := normalizeAddress(c.Param("address"), blockchain)
   483  
   484  	exchange := c.Query("exchange")
   485  
   486  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(7*24*time.Hour))
   487  	if err != nil {
   488  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("parse time range"))
   489  		return
   490  	}
   491  
   492  	if ok := utils.ValidTimeRange(starttime, endtime, time.Duration(14*24*time.Hour)); !ok {
   493  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("time-range too big. max duration is %v", 14*24*time.Hour))
   494  		return
   495  	}
   496  
   497  	p, err := env.DataStore.GetFilterPointsAsset(filter, exchange, address, blockchain, starttime, endtime)
   498  	if err != nil {
   499  		restApi.SendError(c, http.StatusInternalServerError, err)
   500  	} else {
   501  		c.JSON(http.StatusOK, p)
   502  	}
   503  }
   504  
   505  // GetChartPoints returns Filter points for given symbol -> Deprecated?
   506  func (env *Env) GetChartPoints(c *gin.Context) {
   507  	if !validateInputParams(c) {
   508  		return
   509  	}
   510  
   511  	filter := c.Param("filter")
   512  	exchange := c.Param("exchange")
   513  	symbol := c.Param("symbol")
   514  	scale := c.Query("scale")
   515  
   516  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(7*24*time.Hour))
   517  	if err != nil {
   518  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("parse time range"))
   519  		return
   520  	}
   521  
   522  	if ok := utils.ValidTimeRange(starttime, endtime, time.Duration(30*24*time.Hour)); !ok {
   523  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("time-range too big. max duration is %v", 30*24*time.Hour))
   524  		return
   525  	}
   526  
   527  	p, err := env.DataStore.GetFilterPoints(filter, exchange, symbol, scale, starttime, endtime)
   528  	if err != nil {
   529  		restApi.SendError(c, http.StatusInternalServerError, err)
   530  	} else {
   531  		c.JSON(http.StatusOK, p)
   532  	}
   533  }
   534  
   535  // GetChartPointsAllExchanges returns filter points across all exchanges.
   536  func (env *Env) GetChartPointsAllExchanges(c *gin.Context) {
   537  	if !validateInputParams(c) {
   538  		return
   539  	}
   540  
   541  	filter := c.Param("filter")
   542  	symbol := c.Param("symbol")
   543  	scale := c.Query("scale")
   544  
   545  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(7*24*time.Hour))
   546  	if err != nil {
   547  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("parse time range"))
   548  		return
   549  	}
   550  
   551  	if ok := utils.ValidTimeRange(starttime, endtime, time.Duration(30*24*time.Hour)); !ok {
   552  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("time-range too big. max duration is %v", 30*24*time.Hour))
   553  		return
   554  	}
   555  
   556  	p, err := env.DataStore.GetFilterPoints(filter, "", symbol, scale, starttime, endtime)
   557  	if err != nil {
   558  		restApi.SendError(c, http.StatusInternalServerError, err)
   559  	} else {
   560  		c.JSON(http.StatusOK, p)
   561  	}
   562  
   563  }
   564  
   565  func (env *Env) GetFilterPerSource(c *gin.Context) {
   566  	if !validateInputParams(c) {
   567  		return
   568  	}
   569  
   570  	type priceOnExchange struct {
   571  		Price     float64   `json:"Price"`
   572  		Exchange  string    `json:"Exchange"`
   573  		Timestamp time.Time `json:"Time"`
   574  	}
   575  
   576  	type localReturn struct {
   577  		Asset  dia.Asset         `json:"Asset"`
   578  		Prices []priceOnExchange `json:"PricePerExchange"`
   579  	}
   580  
   581  	blockchain := c.Param("blockchain")
   582  	address := normalizeAddress(c.Param("address"), blockchain)
   583  	filter := c.Param("filter")
   584  
   585  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(30)*time.Minute)
   586  	if err != nil {
   587  		restApi.SendError(c, http.StatusInternalServerError, nil)
   588  		return
   589  	}
   590  
   591  	assetQuotations, err := env.DataStore.GetFilterAllExchanges(filter, address, blockchain, starttime, endtime)
   592  	if err != nil {
   593  		restApi.SendError(c, http.StatusInternalServerError, nil)
   594  		return
   595  	}
   596  
   597  	var lr localReturn
   598  	lr.Asset = env.getAssetFromCache(ASSET_CACHE, blockchain, address)
   599  
   600  	for _, aq := range assetQuotations {
   601  		var pe priceOnExchange
   602  		pe.Exchange = aq.Source
   603  		pe.Price = aq.Price
   604  		pe.Timestamp = aq.Time
   605  		lr.Prices = append(lr.Prices, pe)
   606  	}
   607  	c.JSON(http.StatusOK, lr)
   608  
   609  }
   610  
   611  // GetAllSymbols returns all Symbols on @exchange.
   612  // If @exchange is not set, it returns all symbols across all exchanges.
   613  // If @top is set to an integer, only the top @top symbols w.r.t. trading volume are returned. This is
   614  // only active if @exchange is not set.
   615  func (env *Env) GetAllSymbols(c *gin.Context) {
   616  	if !validateInputParams(c) {
   617  		return
   618  	}
   619  
   620  	var (
   621  		s            []string
   622  		numSymbols   int64
   623  		sortedAssets []dia.AssetVolume
   624  		err          error
   625  	)
   626  
   627  	substring := c.Param("substring")
   628  	exchange := c.DefaultQuery("exchange", "noRange")
   629  	numSymbolsString := c.Query("top")
   630  
   631  	if numSymbolsString != "" {
   632  		numSymbols, err = strconv.ParseInt(numSymbolsString, 10, 64)
   633  		if err != nil {
   634  			restApi.SendError(c, http.StatusInternalServerError, errors.New("number of symbols must be an integer"))
   635  		}
   636  	}
   637  
   638  	// Filter results by substring. @exchange is disabled.
   639  	if substring != "" {
   640  		s, err = env.RelDB.GetExchangeSymbols("", substring)
   641  		if err != nil {
   642  			restApi.SendError(c, http.StatusInternalServerError, errors.New("cannot find symbols"))
   643  		}
   644  		s = utils.UniqueStrings(s)
   645  
   646  		sort.Strings(s)
   647  		// Sort all symbols by volume, append if they have no volume.
   648  		sortedAssets, err = env.RelDB.GetSortedAssetSymbols(int64(0), int64(0), substring)
   649  		if err != nil {
   650  			log.Error("get assets with volume: ", err)
   651  		}
   652  		var sortedSymbols []string
   653  		for _, assetvol := range sortedAssets {
   654  			sortedSymbols = append(sortedSymbols, assetvol.Asset.Symbol)
   655  		}
   656  		sortedSymbols = utils.UniqueStrings(sortedSymbols)
   657  		allSymbols := utils.UniqueStrings(append(sortedSymbols, s...))
   658  
   659  		c.JSON(http.StatusOK, allSymbols)
   660  		return
   661  	}
   662  
   663  	if exchange == "noRange" {
   664  		if numSymbolsString != "" {
   665  			// -- Get top @numSymbols symbols across all exchanges. --
   666  			sortedAssets, err = env.RelDB.GetAssetsWithVOL(time.Now().AddDate(0, -1, 0), numSymbols, int64(0), false, "")
   667  			if err != nil {
   668  				log.Error("get assets with volume: ", err)
   669  			}
   670  			for _, assetvol := range sortedAssets {
   671  				s = append(s, assetvol.Asset.Symbol)
   672  			}
   673  			c.JSON(http.StatusOK, s)
   674  		} else {
   675  			// -- Get all symbols across all exchanges. --
   676  			s, err = env.RelDB.GetExchangeSymbols("", "")
   677  			if err != nil {
   678  				restApi.SendError(c, http.StatusInternalServerError, errors.New("cannot find symbols"))
   679  			}
   680  			s = utils.UniqueStrings(s)
   681  
   682  			sort.Strings(s)
   683  			// Sort all symbols by volume, append if they have no volume.
   684  			sortedAssets, err = env.RelDB.GetAssetsWithVOL(time.Now().AddDate(0, -1, 0), numSymbols, int64(0), false, "")
   685  			if err != nil {
   686  				log.Error("get assets with volume: ", err)
   687  			}
   688  			var sortedSymbols []string
   689  			for _, assetvol := range sortedAssets {
   690  				sortedSymbols = append(sortedSymbols, assetvol.Asset.Symbol)
   691  			}
   692  			sortedSymbols = utils.UniqueStrings(sortedSymbols)
   693  			allSymbols := utils.UniqueStrings(append(sortedSymbols, s...))
   694  
   695  			c.JSON(http.StatusOK, allSymbols)
   696  		}
   697  	} else {
   698  		// -- Get all symbols on @exchange. --
   699  		symbols, err := env.RelDB.GetExchangeSymbols(exchange, "")
   700  		if err != nil {
   701  			restApi.SendError(c, http.StatusInternalServerError, errors.New("cannot find symbols"))
   702  		}
   703  		c.JSON(http.StatusOK, symbols)
   704  	}
   705  
   706  }
   707  
   708  // -----------------------------------------------------------------------------
   709  // POOLS AND LIQUIDITY
   710  // -----------------------------------------------------------------------------
   711  
   712  func (env *Env) GetPoolsByAsset(c *gin.Context) {
   713  	if !validateInputParams(c) {
   714  		return
   715  	}
   716  	blockchain := c.Param("blockchain")
   717  	address := normalizeAddress(c.Param("address"), blockchain)
   718  	asset := env.getAssetFromCache(ASSET_CACHE, blockchain, address)
   719  
   720  	liquidityThreshold, err := strconv.ParseFloat(c.DefaultQuery("liquidityThreshold", "10"), 64)
   721  	if err != nil {
   722  		restApi.SendError(c, http.StatusInternalServerError, errors.New("cannot parse liquidityThreshold"))
   723  		return
   724  	}
   725  
   726  	liquidityThresholdUSD, err := strconv.ParseFloat(c.DefaultQuery("liquidityThresholdUSD", "10000"), 64)
   727  	if err != nil {
   728  		restApi.SendError(c, http.StatusInternalServerError, errors.New("cannot parse liquidityThresholdUSD"))
   729  		return
   730  	}
   731  
   732  	// Set liquidity threshold measured in native currency to 1 in order to filter out noise.
   733  	pools, err := env.RelDB.GetPoolsByAsset(asset, liquidityThreshold, 0)
   734  	if err != nil {
   735  		restApi.SendError(c, http.StatusInternalServerError, errors.New("cannot find pool"))
   736  		return
   737  	}
   738  
   739  	type poolInfo struct {
   740  		Exchange          string
   741  		Blockchain        string
   742  		Address           string
   743  		Time              time.Time
   744  		TotalLiquidityUSD float64
   745  		Message           string
   746  		Liquidity         []dia.AssetLiquidity
   747  	}
   748  	var result []poolInfo
   749  
   750  	// Get total liquidity for each filtered pool.
   751  	for _, pool := range pools {
   752  		var pi poolInfo
   753  
   754  		totalLiquidity, lowerBound := pool.GetPoolLiquidityUSD()
   755  
   756  		// In case we can determine USD liquidity and it's below the threshold, continue.
   757  		if !lowerBound && totalLiquidity < liquidityThresholdUSD {
   758  			continue
   759  		}
   760  
   761  		pi.Exchange = pool.Exchange.Name
   762  		pi.Blockchain = pool.Blockchain.Name
   763  		pi.Address = pool.Address
   764  		pi.TotalLiquidityUSD = totalLiquidity
   765  		pi.Time = pool.Time
   766  		for i := range pool.Assetvolumes {
   767  			var al dia.AssetLiquidity = dia.AssetLiquidity(pool.Assetvolumes[i])
   768  			pi.Liquidity = append(pi.Liquidity, al)
   769  		}
   770  		if lowerBound {
   771  			pi.Message = "No US-Dollar price information on one or more pool assets available."
   772  		}
   773  		result = append(result, pi)
   774  	}
   775  
   776  	// Sort by total USD liquidity.
   777  	sort.Slice(result, func(m, n int) bool {
   778  		return result[m].TotalLiquidityUSD > result[n].TotalLiquidityUSD
   779  	})
   780  
   781  	c.JSON(http.StatusOK, result)
   782  }
   783  
   784  func (env *Env) GetPoolLiquidityByAddress(c *gin.Context) {
   785  	if !validateInputParams(c) {
   786  		return
   787  	}
   788  	blockchain := c.Param("blockchain")
   789  	address := normalizeAddress(c.Param("address"), blockchain)
   790  
   791  	pool, err := env.RelDB.GetPoolByAddress(blockchain, address)
   792  	if err != nil {
   793  		log.Info("err: ", err)
   794  		restApi.SendError(c, http.StatusInternalServerError, errors.New("cannot find pool"))
   795  		return
   796  	}
   797  
   798  	// Get total liquidity.
   799  	var (
   800  		totalLiquidity float64
   801  		lowerBound     bool
   802  	)
   803  	totalLiquidity, lowerBound = pool.GetPoolLiquidityUSD()
   804  
   805  	type localReturn struct {
   806  		Exchange          string
   807  		Blockchain        string
   808  		Address           string
   809  		Time              time.Time
   810  		TotalLiquidityUSD float64
   811  		Message           string
   812  		Liquidity         []dia.AssetLiquidity
   813  	}
   814  
   815  	var l localReturn
   816  	if lowerBound {
   817  		l.Message = "No US-Dollar price information on one or more pool assets available."
   818  	}
   819  	l.TotalLiquidityUSD = totalLiquidity
   820  	l.Exchange = pool.Exchange.Name
   821  	l.Blockchain = pool.Blockchain.Name
   822  	l.Address = pool.Address
   823  	l.Time = pool.Time
   824  	for i := range pool.Assetvolumes {
   825  		var al dia.AssetLiquidity = dia.AssetLiquidity(pool.Assetvolumes[i])
   826  		l.Liquidity = append(l.Liquidity, al)
   827  	}
   828  
   829  	c.JSON(http.StatusOK, l)
   830  
   831  }
   832  
   833  func (env *Env) GetPoolSlippage(c *gin.Context) {
   834  	if !validateInputParams(c) {
   835  		return
   836  	}
   837  	blockchain := c.Param("blockchain")
   838  	addressPool := normalizeAddress(c.Param("addressPool"), blockchain)
   839  	addressAsset := normalizeAddress(c.Param("addressAsset"), blockchain)
   840  	poolType := c.Param("poolType")
   841  	priceDeviationInt, err := strconv.ParseInt(c.Param("priceDeviation"), 10, 64)
   842  	if err != nil {
   843  		restApi.SendError(c, http.StatusInternalServerError, errors.New("error parsing priceDeviation"))
   844  		return
   845  	}
   846  	if priceDeviationInt < 0 || priceDeviationInt >= 1000 {
   847  		restApi.SendError(c, http.StatusInternalServerError, errors.New("priceDeviation measured in per mille is out of range"))
   848  		return
   849  	}
   850  	priceDeviation := float64(priceDeviationInt) / 1000
   851  
   852  	type localReturn struct {
   853  		VolumeRequired float64
   854  		AssetIn        string
   855  		Exchange       string
   856  		Blockchain     string
   857  		Address        string
   858  		Time           time.Time
   859  		Liquidity      []dia.AssetLiquidity
   860  	}
   861  
   862  	pool, err := env.RelDB.GetPoolByAddress(blockchain, addressPool)
   863  	if err != nil {
   864  		restApi.SendError(c, http.StatusInternalServerError, errors.New("cannot find pool"))
   865  		return
   866  	}
   867  	var l localReturn
   868  	l.Exchange = pool.Exchange.Name
   869  	l.Blockchain = pool.Blockchain.Name
   870  	l.Address = pool.Address
   871  	l.Time = pool.Time
   872  	for i := range pool.Assetvolumes {
   873  		var al dia.AssetLiquidity = dia.AssetLiquidity(pool.Assetvolumes[i])
   874  		l.Liquidity = append(l.Liquidity, al)
   875  	}
   876  
   877  	var (
   878  		assetInIndex int
   879  		foundAsset   bool
   880  	)
   881  	for i := range pool.Assetvolumes {
   882  		if pool.Assetvolumes[i].Asset.Address == addressAsset {
   883  			assetInIndex = i
   884  			foundAsset = true
   885  		}
   886  	}
   887  	if !foundAsset {
   888  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("asset %s not in pool", addressAsset))
   889  		return
   890  	}
   891  	l.AssetIn = pool.Assetvolumes[assetInIndex].Asset.Symbol
   892  
   893  	switch poolType {
   894  	case "UniswapV2":
   895  		l.VolumeRequired = pool.Assetvolumes[assetInIndex].Volume * (1/(1-priceDeviation) - 1)
   896  	}
   897  
   898  	c.JSON(http.StatusOK, l)
   899  }
   900  
   901  func (env *Env) GetPoolPriceImpact(c *gin.Context) {
   902  	if !validateInputParams(c) {
   903  		return
   904  	}
   905  	blockchain := c.Param("blockchain")
   906  	addressPool := normalizeAddress(c.Param("addressPool"), blockchain)
   907  	addressAsset := normalizeAddress(c.Param("addressAsset"), blockchain)
   908  	poolType := c.Param("poolType")
   909  	priceDeviationInt, err := strconv.ParseInt(c.Param("priceDeviation"), 10, 64)
   910  	if err != nil {
   911  		restApi.SendError(c, http.StatusInternalServerError, errors.New("error parsing priceDeviation"))
   912  		return
   913  	}
   914  	if priceDeviationInt < 0 || priceDeviationInt >= 1000 {
   915  		restApi.SendError(c, http.StatusInternalServerError, errors.New("priceDeviation measured in per mille is out of range"))
   916  		return
   917  	}
   918  	priceDeviation := float64(priceDeviationInt) / 1000
   919  
   920  	type localReturn struct {
   921  		VolumeRequired float64
   922  		AssetIn        string
   923  		Exchange       string
   924  		Blockchain     string
   925  		Address        string
   926  		Time           time.Time
   927  		Liquidity      []dia.AssetLiquidity
   928  	}
   929  
   930  	pool, err := env.RelDB.GetPoolByAddress(blockchain, addressPool)
   931  	if err != nil {
   932  		restApi.SendError(c, http.StatusInternalServerError, errors.New("cannot find pool"))
   933  		return
   934  	}
   935  	var l localReturn
   936  	l.Exchange = pool.Exchange.Name
   937  	l.Blockchain = pool.Blockchain.Name
   938  	l.Address = pool.Address
   939  	l.Time = pool.Time
   940  	for i := range pool.Assetvolumes {
   941  		var al dia.AssetLiquidity = dia.AssetLiquidity(pool.Assetvolumes[i])
   942  		l.Liquidity = append(l.Liquidity, al)
   943  	}
   944  
   945  	var (
   946  		assetInIndex int
   947  		foundAsset   bool
   948  	)
   949  	for i := range pool.Assetvolumes {
   950  		if pool.Assetvolumes[i].Asset.Address == addressAsset {
   951  			assetInIndex = i
   952  			foundAsset = true
   953  		}
   954  	}
   955  	if !foundAsset {
   956  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("asset %s not in pool", addressAsset))
   957  		return
   958  	}
   959  	l.AssetIn = pool.Assetvolumes[assetInIndex].Asset.Symbol
   960  
   961  	switch poolType {
   962  	case "UniswapV2":
   963  		l.VolumeRequired = pool.Assetvolumes[assetInIndex].Volume * (1/math.Sqrt(1-priceDeviation) - 1)
   964  	}
   965  
   966  	c.JSON(http.StatusOK, l)
   967  }
   968  
   969  func (env *Env) GetPriceImpactSimulation(c *gin.Context) {
   970  	if !validateInputParams(c) {
   971  		return
   972  	}
   973  
   974  	poolType := c.Param("poolType")
   975  	priceDeviationInt, err := strconv.ParseInt(c.Param("priceDeviation"), 10, 64)
   976  	if err != nil {
   977  		restApi.SendError(c, http.StatusInternalServerError, errors.New("error parsing priceDeviation"))
   978  		return
   979  	}
   980  	if priceDeviationInt < 0 || priceDeviationInt >= 1000 {
   981  		restApi.SendError(c, http.StatusInternalServerError, errors.New("priceDeviation measured in per mille is out of range"))
   982  		return
   983  	}
   984  	priceDeviation := float64(priceDeviationInt) / 1000
   985  	liquidityA, err := strconv.ParseFloat(c.Param("liquidityA"), 64)
   986  	if err != nil {
   987  		restApi.SendError(c, http.StatusInternalServerError, errors.New("error parsing liquidityA"))
   988  		return
   989  	}
   990  	if liquidityA <= 0 {
   991  		restApi.SendError(c, http.StatusInternalServerError, errors.New("liquidity must be a non-negative number"))
   992  		return
   993  	}
   994  	liquidityB, err := strconv.ParseFloat(c.Param("liquidityB"), 64)
   995  	if err != nil {
   996  		restApi.SendError(c, http.StatusInternalServerError, errors.New("error parsing liquidityB"))
   997  		return
   998  	}
   999  	if liquidityB <= 0 {
  1000  		restApi.SendError(c, http.StatusInternalServerError, errors.New("liquidity must be a non-negative number"))
  1001  		return
  1002  	}
  1003  
  1004  	type dummyLiquidity struct {
  1005  		Asset     string
  1006  		Liquidity float64
  1007  	}
  1008  
  1009  	type localReturn struct {
  1010  		PriceDeviation  float64
  1011  		PriceAssetA     float64
  1012  		PriceAssetB     float64
  1013  		VolumesRequired []struct {
  1014  			AssetIn                string
  1015  			VolumeRequired         float64
  1016  			InitialPriceAssetIn    float64
  1017  			ResultingPriceAssetIn  float64
  1018  			ResultingPriceAssetOut float64
  1019  		}
  1020  		Liquidity []dummyLiquidity
  1021  	}
  1022  
  1023  	l := []dummyLiquidity{
  1024  		{Asset: "A", Liquidity: liquidityA},
  1025  		{Asset: "B", Liquidity: liquidityB},
  1026  	}
  1027  	var lr localReturn
  1028  	lr.PriceDeviation = priceDeviation
  1029  	lr.PriceAssetA = liquidityB / liquidityA
  1030  	lr.PriceAssetB = liquidityA / liquidityB
  1031  
  1032  	switch poolType {
  1033  	case "UniswapV2":
  1034  		volRequiredA := liquidityA * (1/math.Sqrt(1-priceDeviation) - 1)
  1035  		volRequiredB := liquidityB * (1/math.Sqrt(1-priceDeviation) - 1)
  1036  		lr.VolumesRequired = append(lr.VolumesRequired, struct {
  1037  			AssetIn                string
  1038  			VolumeRequired         float64
  1039  			InitialPriceAssetIn    float64
  1040  			ResultingPriceAssetIn  float64
  1041  			ResultingPriceAssetOut float64
  1042  		}{
  1043  			"A",
  1044  			volRequiredA,
  1045  			liquidityB / liquidityA,
  1046  			liquidityA * liquidityB / math.Pow(volRequiredA+liquidityA, 2),
  1047  			math.Pow(volRequiredA+liquidityA, 2) / liquidityA / liquidityB,
  1048  		})
  1049  		lr.Liquidity = l
  1050  		lr.VolumesRequired = append(lr.VolumesRequired, struct {
  1051  			AssetIn                string
  1052  			VolumeRequired         float64
  1053  			InitialPriceAssetIn    float64
  1054  			ResultingPriceAssetIn  float64
  1055  			ResultingPriceAssetOut float64
  1056  		}{
  1057  			"B",
  1058  			volRequiredB,
  1059  			liquidityA / liquidityB,
  1060  			liquidityB * liquidityA / math.Pow(volRequiredB+liquidityB, 2),
  1061  			math.Pow(volRequiredB+liquidityB, 2) / liquidityA / liquidityB,
  1062  		})
  1063  
  1064  	}
  1065  
  1066  	c.JSON(http.StatusOK, lr)
  1067  }
  1068  
  1069  // -----------------------------------------------------------------------------
  1070  // EXCHANGE PAIRS
  1071  // -----------------------------------------------------------------------------
  1072  
  1073  func (env *Env) GetExchangePairs(c *gin.Context) {
  1074  	if !validateInputParams(c) {
  1075  		return
  1076  	}
  1077  	exchange, err := env.RelDB.GetExchange(c.Param("exchange"))
  1078  	if err != nil {
  1079  		restApi.SendError(c, http.StatusInternalServerError, err)
  1080  		return
  1081  	}
  1082  	var (
  1083  		filterVerified bool
  1084  		verified       bool
  1085  	)
  1086  	verifiedString := c.Query("verified")
  1087  	if verifiedString != "" {
  1088  		verified, err = strconv.ParseBool(verifiedString)
  1089  		if err != nil {
  1090  			restApi.SendError(c, http.StatusInternalServerError, err)
  1091  			return
  1092  		}
  1093  		filterVerified = true
  1094  	}
  1095  
  1096  	pairs, err := env.RelDB.GetPairsForExchange(exchange, filterVerified, verified)
  1097  	if err != nil {
  1098  		restApi.SendError(c, http.StatusInternalServerError, err)
  1099  		return
  1100  	}
  1101  
  1102  	sort.Slice(pairs, func(m, n int) bool {
  1103  		return pairs[m].Symbol < pairs[n].Symbol
  1104  	})
  1105  	c.JSON(http.StatusOK, pairs)
  1106  
  1107  }
  1108  
  1109  func (env *Env) GetAssetPairs(c *gin.Context) {
  1110  	if !validateInputParams(c) {
  1111  		return
  1112  	}
  1113  	blockchain := c.Param("blockchain")
  1114  	address := normalizeAddress(c.Param("address"), blockchain)
  1115  	var (
  1116  		filterVerified bool
  1117  		verified       bool
  1118  		err            error
  1119  	)
  1120  	verifiedString := c.Query("verified")
  1121  	if verifiedString != "" {
  1122  		verified, err = strconv.ParseBool(verifiedString)
  1123  		if err != nil {
  1124  			restApi.SendError(c, http.StatusInternalServerError, err)
  1125  			return
  1126  		}
  1127  		filterVerified = true
  1128  	}
  1129  
  1130  	pairs, err := env.RelDB.GetPairsForAsset(dia.Asset{Address: address, Blockchain: blockchain}, filterVerified, verified)
  1131  	if err != nil {
  1132  		restApi.SendError(c, http.StatusInternalServerError, err)
  1133  		return
  1134  	}
  1135  
  1136  	sort.Slice(pairs, func(m, n int) bool { return pairs[m].Exchange < pairs[n].Exchange })
  1137  	c.JSON(http.StatusOK, pairs)
  1138  
  1139  }
  1140  
  1141  func (env *Env) SearchAsset(c *gin.Context) {
  1142  	if !validateInputParams(c) {
  1143  		return
  1144  	}
  1145  
  1146  	querystring := c.Param("query")
  1147  	var (
  1148  		assets = []dia.Asset{}
  1149  		err    error
  1150  	)
  1151  
  1152  	switch {
  1153  	case len(querystring) > 4 && strings.Contains(querystring[0:2], "0x"):
  1154  		assets, err = env.RelDB.GetAssetsByAddress(querystring)
  1155  		if err != nil {
  1156  			// restApi.SendError(c, http.StatusInternalServerError, errors.New("eror getting asset"))
  1157  			log.Errorln("error getting GetAssetsByAddress", err)
  1158  		}
  1159  
  1160  	case len(querystring) > 4 && !strings.Contains(querystring[0:2], "0x"):
  1161  		assets, err = env.RelDB.GetAssetsBySymbolName(querystring, querystring)
  1162  		if err != nil {
  1163  			// restApi.SendError(c, http.StatusInternalServerError, errors.New("eror getting asset"))
  1164  			log.Errorln("error getting GetAssetsBySymbolName", err)
  1165  
  1166  		}
  1167  
  1168  	case len(querystring) <= 4:
  1169  		assets, err = env.RelDB.GetAssetsBySymbolName(querystring, querystring)
  1170  		if err != nil {
  1171  			// restApi.SendError(c, http.StatusInternalServerError, errors.New("eror getting asset"))
  1172  			log.Errorln("error getting GetAssetsBySymbolName", err)
  1173  
  1174  		}
  1175  	}
  1176  	c.JSON(http.StatusOK, assets)
  1177  }
  1178  
  1179  func (env *Env) GetTopAssets(c *gin.Context) {
  1180  	if !validateInputParams(c) {
  1181  		return
  1182  	}
  1183  
  1184  	numAssetsString := c.Param("numAssets")
  1185  	pageString := c.DefaultQuery("Page", "1")
  1186  	onlycexString := c.DefaultQuery("Cex", "false")
  1187  	blockchain := c.DefaultQuery("Network", "")
  1188  
  1189  	var (
  1190  		numAssets    int64
  1191  		sortedAssets []dia.AssetVolume
  1192  		err          error
  1193  		pageNumber   int64
  1194  		offset       int64
  1195  	)
  1196  
  1197  	pageNumber, err = strconv.ParseInt(pageString, 10, 64)
  1198  	if err != nil {
  1199  		restApi.SendError(c, http.StatusInternalServerError, errors.New("page of assets must be an integer"))
  1200  	}
  1201  
  1202  	onlycex, err := strconv.ParseBool(onlycexString)
  1203  	if err != nil {
  1204  		log.Fatal(err)
  1205  	}
  1206  
  1207  	numAssets, err = strconv.ParseInt(numAssetsString, 10, 64)
  1208  	if err != nil {
  1209  		restApi.SendError(c, http.StatusInternalServerError, errors.New("number of assets must be an integer"))
  1210  	}
  1211  
  1212  	offset = (pageNumber - 1) * numAssets
  1213  
  1214  	sortedAssets, err = env.RelDB.GetAssetsWithVOL(time.Now().AddDate(0, 0, -7), numAssets, offset, onlycex, blockchain)
  1215  	if err != nil {
  1216  		log.Error("get assets with volume: ", err)
  1217  
  1218  	}
  1219  	var assets = []dia.TopAsset{}
  1220  
  1221  	for _, v := range sortedAssets {
  1222  		var sources = make(map[string][]string)
  1223  
  1224  		aqf := dia.TopAsset{}
  1225  		aqf.Asset = v.Asset
  1226  		quotation, err := env.DataStore.GetAssetQuotationLatest(aqf.Asset, dia.CRYPTO_ZERO_UNIX_TIME)
  1227  		if err != nil {
  1228  			log.Warn("quotation: ", err)
  1229  		} else {
  1230  			aqf.Price = quotation.Price
  1231  
  1232  		}
  1233  		aqf.Volume = v.Volume
  1234  
  1235  		sources["CEX"], err = env.RelDB.GetAssetSource(v.Asset, true)
  1236  		if err != nil {
  1237  			log.Warn("get GetAssetSource: ", err)
  1238  		}
  1239  		sources["DEX"], err = env.RelDB.GetAssetSource(v.Asset, false)
  1240  		if err != nil {
  1241  			log.Warn("get GetAssetSource: ", err)
  1242  		}
  1243  		aqf.Source = sources
  1244  
  1245  		quotationYesterday, err := env.DataStore.GetAssetQuotation(aqf.Asset, dia.CRYPTO_ZERO_UNIX_TIME, time.Now().AddDate(0, 0, -1))
  1246  		if err != nil {
  1247  			log.Warn("get quotation yesterday: ", err)
  1248  		} else {
  1249  			aqf.PriceYesterday = quotationYesterday.Price
  1250  		}
  1251  
  1252  		assets = append(assets, aqf)
  1253  
  1254  	}
  1255  	c.JSON(http.StatusOK, assets)
  1256  }
  1257  
  1258  // GetQuotedAssets is the delegate method to fetch all assets that have an asset quotation
  1259  // dating back at most 7 days.
  1260  func (env *Env) GetQuotedAssets(c *gin.Context) {
  1261  	if !validateInputParams(c) {
  1262  		return
  1263  	}
  1264  
  1265  	endtime := time.Now()
  1266  	starttime := endtime.AddDate(0, 0, -7)
  1267  	assetvolumes, err := env.RelDB.GetAssetsWithVolByBlockchain(starttime, endtime, c.Query("blockchain"))
  1268  	if err != nil {
  1269  		log.Error("get assets with volume: ", err)
  1270  	}
  1271  
  1272  	c.JSON(http.StatusOK, assetvolumes)
  1273  }
  1274  
  1275  // -----------------------------------------------------------------------------
  1276  // FIAT CURRENCIES
  1277  // -----------------------------------------------------------------------------
  1278  
  1279  // GetFiatQuotations returns several quotations vs USD as published by the ECB
  1280  func (env *Env) GetFiatQuotations(c *gin.Context) {
  1281  	if !validateInputParams(c) {
  1282  		return
  1283  	}
  1284  	q, err := env.DataStore.GetCurrencyChange()
  1285  	if err != nil {
  1286  		if errors.Is(err, redis.Nil) {
  1287  			restApi.SendError(c, http.StatusNotFound, err)
  1288  		} else {
  1289  			restApi.SendError(c, http.StatusInternalServerError, err)
  1290  		}
  1291  	} else {
  1292  		c.JSON(http.StatusOK, q)
  1293  	}
  1294  }
  1295  
  1296  func (env *Env) GetTwelvedataFiatQuotations(c *gin.Context) {
  1297  	if !validateInputParams(c) {
  1298  		return
  1299  	}
  1300  
  1301  	// Parse symbol.
  1302  	assets := strings.Split(c.Param("symbol"), "-")
  1303  	if len(assets) != 2 {
  1304  		restApi.SendError(c, http.StatusNotFound, errors.New("wrong format for forex pair"))
  1305  		return
  1306  	}
  1307  	symbol := assets[0] + "/" + assets[1]
  1308  
  1309  	// Time for quotation is time.Now() by default.
  1310  	timestampInt, err := strconv.ParseInt(c.DefaultQuery("timestamp", strconv.Itoa(int(time.Now().Unix()))), 10, 64)
  1311  	if err != nil {
  1312  		restApi.SendError(c, http.StatusNotFound, errors.New("could not parse Unix timestamp"))
  1313  		return
  1314  	}
  1315  	timestamp := time.Unix(timestampInt, 0)
  1316  
  1317  	var (
  1318  		q       models.ForeignQuotation
  1319  		errRev  error
  1320  		reverse bool
  1321  	)
  1322  
  1323  	q, err = env.DataStore.GetForeignQuotationInflux(symbol, "TwelveData", timestamp)
  1324  	if err != nil || q.Price == 0 {
  1325  		reverse = true
  1326  		symbol = assets[1] + "/" + assets[0]
  1327  		log.Info("try reverse order: ", symbol)
  1328  		q, errRev = env.DataStore.GetForeignQuotationInflux(symbol, "TwelveData", timestamp)
  1329  		if errRev != nil || q.Price == 0 {
  1330  			if q.Price == 0 {
  1331  				errRev = errors.New("not found")
  1332  			}
  1333  			if errors.Is(errRev, redis.Nil) {
  1334  				restApi.SendError(c, http.StatusNotFound, errRev)
  1335  				return
  1336  			} else {
  1337  				log.Info(c)
  1338  				restApi.SendError(c, http.StatusInternalServerError, errRev)
  1339  				return
  1340  			}
  1341  		}
  1342  	}
  1343  
  1344  	response := struct {
  1345  		Ticker    string
  1346  		Price     float64
  1347  		Timestamp time.Time
  1348  	}{
  1349  		Ticker:    c.Param("symbol"),
  1350  		Price:     q.Price,
  1351  		Timestamp: q.Time,
  1352  	}
  1353  	if err == nil && !reverse {
  1354  		c.JSON(http.StatusOK, response)
  1355  		return
  1356  	}
  1357  	if errRev == nil && q.Price != 0 {
  1358  		response.Price = 1 / q.Price
  1359  		c.JSON(http.StatusOK, response)
  1360  	}
  1361  }
  1362  
  1363  // -----------------------------------------------------------------------------
  1364  // STOCKS
  1365  // -----------------------------------------------------------------------------
  1366  
  1367  func (env *Env) GetTwelvedataStockQuotations(c *gin.Context) {
  1368  	if !validateInputParams(c) {
  1369  		return
  1370  	}
  1371  
  1372  	// Time for quotation is time.Now() by default.
  1373  	timestampInt, err := strconv.ParseInt(c.DefaultQuery("timestamp", strconv.Itoa(int(time.Now().Unix()))), 10, 64)
  1374  	if err != nil {
  1375  		restApi.SendError(c, http.StatusNotFound, errors.New("could not parse Unix timestamp"))
  1376  		return
  1377  	}
  1378  	timestamp := time.Unix(timestampInt, 0)
  1379  
  1380  	q, err := env.DataStore.GetForeignQuotationInflux(c.Param("symbol"), "TwelveData", timestamp)
  1381  	if err != nil {
  1382  		if errors.Is(err, redis.Nil) {
  1383  			restApi.SendError(c, http.StatusNotFound, err)
  1384  		} else {
  1385  			restApi.SendError(c, http.StatusInternalServerError, err)
  1386  		}
  1387  	} else {
  1388  		// Format response.
  1389  		response := struct {
  1390  			Ticker    string
  1391  			Price     float64
  1392  			Timestamp time.Time
  1393  		}{
  1394  			Ticker:    c.Param("symbol"),
  1395  			Price:     q.Price,
  1396  			Timestamp: q.Time,
  1397  		}
  1398  		c.JSON(http.StatusOK, response)
  1399  	}
  1400  }
  1401  
  1402  func (env *Env) GetStockSymbols(c *gin.Context) {
  1403  	if !validateInputParams(c) {
  1404  		return
  1405  	}
  1406  	type sourcedStock struct {
  1407  		Stock  models.Stock
  1408  		Source string
  1409  	}
  1410  	var srcStocks []sourcedStock
  1411  	stocks, err := env.DataStore.GetStockSymbols()
  1412  	log.Info("stocks: ", stocks)
  1413  
  1414  	if err != nil {
  1415  		if errors.Is(err, redis.Nil) {
  1416  			restApi.SendError(c, http.StatusNotFound, err)
  1417  		} else {
  1418  			restApi.SendError(c, http.StatusInternalServerError, err)
  1419  		}
  1420  	} else {
  1421  		for stock, source := range stocks {
  1422  			srcStocks = append(srcStocks, sourcedStock{
  1423  				Stock:  stock,
  1424  				Source: source,
  1425  			})
  1426  		}
  1427  		c.JSON(http.StatusOK, srcStocks)
  1428  	}
  1429  }
  1430  
  1431  // GetStockQuotation is the delegate method to fetch the value(s) of
  1432  // quotations of asset with @symbol from @source.
  1433  // Last value is retrieved. Otional query parameters allow to obtain data in a time range.
  1434  func (env *Env) GetStockQuotation(c *gin.Context) {
  1435  	if !validateInputParams(c) {
  1436  		return
  1437  	}
  1438  	source := c.Param("source")
  1439  	symbol := c.Param("symbol")
  1440  	date := c.Param("time")
  1441  	// Add optional query parameters for requesting a range of values
  1442  	dateInit := c.DefaultQuery("dateInit", "noRange")
  1443  	dateFinal := c.Query("dateFinal")
  1444  
  1445  	if dateInit == "noRange" {
  1446  		// Return most recent data point
  1447  		var endTime time.Time
  1448  		var err error
  1449  		if date == "" {
  1450  			endTime = time.Now()
  1451  		} else {
  1452  			// Convert unix time int/string to time
  1453  			endTime, err = utils.StrToUnixtime(date)
  1454  			if err != nil {
  1455  				restApi.SendError(c, http.StatusNotFound, err)
  1456  			}
  1457  		}
  1458  		startTime := endTime.AddDate(0, 0, -1)
  1459  
  1460  		q, err := env.DataStore.GetStockQuotation(source, symbol, startTime, endTime)
  1461  		if err != nil {
  1462  			if errors.Is(err, redis.Nil) {
  1463  				restApi.SendError(c, http.StatusNotFound, err)
  1464  			} else {
  1465  				restApi.SendError(c, http.StatusInternalServerError, err)
  1466  			}
  1467  		} else {
  1468  			c.JSON(http.StatusOK, q[0])
  1469  		}
  1470  	} else {
  1471  		starttime, err := utils.StrToUnixtime(dateInit)
  1472  		if err != nil {
  1473  			restApi.SendError(c, http.StatusNotFound, err)
  1474  		}
  1475  		endtime, err := utils.StrToUnixtime(dateFinal)
  1476  		if err != nil {
  1477  			restApi.SendError(c, http.StatusNotFound, err)
  1478  		}
  1479  
  1480  		q, err := env.DataStore.GetStockQuotation(source, symbol, starttime, endtime)
  1481  		if err != nil {
  1482  			if errors.Is(err, redis.Nil) {
  1483  				restApi.SendError(c, http.StatusNotFound, err)
  1484  			} else {
  1485  				restApi.SendError(c, http.StatusInternalServerError, err)
  1486  			}
  1487  		} else {
  1488  			c.JSON(http.StatusOK, q)
  1489  		}
  1490  	}
  1491  }
  1492  
  1493  func (env *Env) GetTwelvedataCommodityQuotation(c *gin.Context) {
  1494  	if !validateInputParams(c) {
  1495  		return
  1496  	}
  1497  
  1498  	timestampInt, err := strconv.ParseInt(c.DefaultQuery("timestamp", strconv.Itoa(int(time.Now().Unix()))), 10, 64)
  1499  	if err != nil {
  1500  		restApi.SendError(c, http.StatusNotFound, errors.New("could not parse Unix timestamp"))
  1501  		return
  1502  	}
  1503  	timestamp := time.Unix(timestampInt, 0)
  1504  	if len(strings.Split(c.Param("symbol"), "-")) != 2 {
  1505  		restApi.SendError(c, http.StatusNotFound, errors.New("symbol format not known"))
  1506  		return
  1507  	}
  1508  	symbol := strings.Split(c.Param("symbol"), "-")[0] + "/" + strings.Split(c.Param("symbol"), "-")[1]
  1509  
  1510  	q, err := env.DataStore.GetForeignQuotationInflux(symbol, "TwelveData", timestamp)
  1511  	if err != nil {
  1512  		if errors.Is(err, redis.Nil) {
  1513  			restApi.SendError(c, http.StatusNotFound, err)
  1514  		} else {
  1515  			restApi.SendError(c, http.StatusInternalServerError, err)
  1516  		}
  1517  	} else {
  1518  		// Format response.
  1519  		response := struct {
  1520  			Ticker    string
  1521  			Name      string
  1522  			Price     float64
  1523  			Timestamp time.Time
  1524  		}{
  1525  			Ticker:    c.Param("symbol"),
  1526  			Name:      q.Name,
  1527  			Price:     q.Price,
  1528  			Timestamp: q.Time,
  1529  		}
  1530  		c.JSON(http.StatusOK, response)
  1531  	}
  1532  }
  1533  
  1534  func (env *Env) GetTwelvedataETFQuotation(c *gin.Context) {
  1535  	if !validateInputParams(c) {
  1536  		return
  1537  	}
  1538  
  1539  	timestampInt, err := strconv.ParseInt(c.DefaultQuery("timestamp", strconv.Itoa(int(time.Now().Unix()))), 10, 64)
  1540  	if err != nil {
  1541  		restApi.SendError(c, http.StatusNotFound, errors.New("could not parse Unix timestamp"))
  1542  		return
  1543  	}
  1544  	timestamp := time.Unix(timestampInt, 0)
  1545  
  1546  	q, err := env.DataStore.GetForeignQuotationInflux(c.Param("symbol"), "TwelveData", timestamp)
  1547  	if err != nil {
  1548  		if errors.Is(err, redis.Nil) {
  1549  			restApi.SendError(c, http.StatusNotFound, err)
  1550  		} else {
  1551  			restApi.SendError(c, http.StatusInternalServerError, err)
  1552  		}
  1553  	} else {
  1554  		// Format response.
  1555  		response := struct {
  1556  			Ticker    string
  1557  			Name      string
  1558  			Price     float64
  1559  			Timestamp time.Time
  1560  		}{
  1561  			Ticker:    c.Param("symbol"),
  1562  			Name:      q.Name,
  1563  			Price:     q.Price,
  1564  			Timestamp: q.Time,
  1565  		}
  1566  		c.JSON(http.StatusOK, response)
  1567  	}
  1568  }
  1569  
  1570  // -----------------------------------------------------------------------------
  1571  // FOREIGN QUOTATIONS
  1572  // -----------------------------------------------------------------------------
  1573  
  1574  // GetForeignQuotation returns several quotations vs USD as published by the ECB
  1575  func (env *Env) GetForeignQuotation(c *gin.Context) {
  1576  	if !validateInputParams(c) {
  1577  		return
  1578  	}
  1579  	var (
  1580  		q models.ForeignQuotation
  1581  	)
  1582  
  1583  	source := c.Param("source")
  1584  	symbol := c.Param("symbol")
  1585  	date := c.DefaultQuery("time", "noRange")
  1586  	var timestamp time.Time
  1587  
  1588  	if date == "noRange" {
  1589  		timestamp = time.Now()
  1590  	} else {
  1591  		t, err := strconv.Atoi(date)
  1592  		if err != nil {
  1593  			log.Error(err)
  1594  		}
  1595  		timestamp = time.Unix(int64(t), 0)
  1596  	}
  1597  
  1598  	var err error
  1599  	q, err = env.DataStore.GetForeignQuotationInflux(symbol, source, timestamp)
  1600  	if err != nil || q.Time.Before(time.Unix(1689847252, 0)) {
  1601  		// Attempt to fetch quotation for reversed order of symbol string.
  1602  		assetsSymbols := strings.Split(symbol, "-")
  1603  		if source == "YahooFinance" && len(assetsSymbols) == 2 {
  1604  			symbolInflux := assetsSymbols[1] + "-" + assetsSymbols[0]
  1605  			q, err = env.DataStore.GetForeignQuotationInflux(symbolInflux, source, timestamp)
  1606  			if err != nil {
  1607  				restApi.SendError(c, http.StatusInternalServerError, err)
  1608  				return
  1609  			}
  1610  			if q.Price != 0 {
  1611  				q.Price = 1 / q.Price
  1612  			}
  1613  			if q.PriceYesterday != 0 {
  1614  				q.PriceYesterday = 1 / q.PriceYesterday
  1615  			}
  1616  			q.Symbol = symbol
  1617  			q.Name = symbol
  1618  			c.JSON(http.StatusOK, q)
  1619  		} else {
  1620  			restApi.SendError(c, http.StatusInternalServerError, err)
  1621  			return
  1622  		}
  1623  	} else {
  1624  		c.JSON(http.StatusOK, q)
  1625  	}
  1626  }
  1627  
  1628  // GetForeignSymbols returns all symbols available for quotation from @source.
  1629  func (env *Env) GetForeignSymbols(c *gin.Context) {
  1630  	if !validateInputParams(c) {
  1631  		return
  1632  	}
  1633  
  1634  	source := c.Param("source")
  1635  
  1636  	q, err := env.DataStore.GetForeignSymbolsInflux(source)
  1637  	if err != nil {
  1638  		if errors.Is(err, redis.Nil) {
  1639  			restApi.SendError(c, http.StatusNotFound, err)
  1640  		} else {
  1641  			restApi.SendError(c, http.StatusInternalServerError, err)
  1642  		}
  1643  	} else {
  1644  		c.JSON(http.StatusOK, q)
  1645  	}
  1646  
  1647  }
  1648  
  1649  // -----------------------------------------------------------------------------
  1650  // CUSTOMIZED PRODUCTS
  1651  // -----------------------------------------------------------------------------
  1652  
  1653  func (env *Env) GetVwapFirefly(c *gin.Context) {
  1654  	if !validateInputParams(c) {
  1655  		return
  1656  	}
  1657  
  1658  	foreignname := c.Param("ticker")
  1659  	starttimeStr := c.Query("starttime")
  1660  	endtimeStr := c.Query("endtime")
  1661  
  1662  	var starttime, endtime time.Time
  1663  	if starttimeStr == "" || endtimeStr == "" {
  1664  		starttime = time.Now().Add(time.Duration(-4 * time.Hour))
  1665  		endtime = time.Now()
  1666  	} else {
  1667  		starttimeInt, err := strconv.ParseInt(starttimeStr, 10, 64)
  1668  		if err != nil {
  1669  			restApi.SendError(c, http.StatusInternalServerError, err)
  1670  			return
  1671  		}
  1672  		starttime = time.Unix(starttimeInt, 0)
  1673  		endtimeInt, err := strconv.ParseInt(endtimeStr, 10, 64)
  1674  		if err != nil {
  1675  			restApi.SendError(c, http.StatusInternalServerError, err)
  1676  			return
  1677  		}
  1678  		endtime = time.Unix(endtimeInt, 0)
  1679  		if ok := utils.ValidTimeRange(starttime, endtime, time.Duration(30*24*time.Hour)); !ok {
  1680  			restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("time-range too big. max duration is %v", 30*24*time.Hour))
  1681  			return
  1682  		}
  1683  	}
  1684  
  1685  	type vwapObj struct {
  1686  		Ticker    string
  1687  		Value     float64
  1688  		Timestamp time.Time
  1689  	}
  1690  	values, timestamps, err := env.DataStore.GetVWAPFirefly(foreignname, starttime, endtime)
  1691  	if err != nil {
  1692  		restApi.SendError(c, http.StatusInternalServerError, err)
  1693  		return
  1694  	}
  1695  	if starttimeStr == "" || endtimeStr == "" {
  1696  		response := vwapObj{
  1697  			Ticker:    foreignname,
  1698  			Value:     values[0],
  1699  			Timestamp: timestamps[0],
  1700  		}
  1701  		c.JSON(http.StatusOK, response)
  1702  	} else {
  1703  		var response []vwapObj
  1704  		for i := 0; i < len(values); i++ {
  1705  			tmp := vwapObj{
  1706  				Ticker:    foreignname,
  1707  				Value:     values[i],
  1708  				Timestamp: timestamps[i],
  1709  			}
  1710  			response = append(response, tmp)
  1711  		}
  1712  		c.JSON(http.StatusOK, response)
  1713  	}
  1714  }
  1715  
  1716  func (env *Env) GetLastTradeTime(c *gin.Context) {
  1717  	if !validateInputParams(c) {
  1718  		return
  1719  	}
  1720  
  1721  	exchange := c.Param("exchange")
  1722  	blockchain := c.Param("blockchain")
  1723  	address := normalizeAddress(c.Param("address"), blockchain)
  1724  
  1725  	t, err := env.DataStore.GetLastTradeTimeForExchange(dia.Asset{Address: address, Blockchain: blockchain}, exchange)
  1726  	if err != nil {
  1727  		if errors.Is(err, redis.Nil) {
  1728  			restApi.SendError(c, http.StatusNotFound, err)
  1729  		} else {
  1730  			restApi.SendError(c, http.StatusInternalServerError, err)
  1731  		}
  1732  	} else {
  1733  		c.JSON(http.StatusOK, *t)
  1734  	}
  1735  }
  1736  
  1737  // GetLastTrades returns last N trades of an asset. Defaults to N=1000.
  1738  func (env *Env) GetLastTradesAsset(c *gin.Context) {
  1739  	if !validateInputParams(c) {
  1740  		return
  1741  	}
  1742  
  1743  	blockchain := c.Param("blockchain")
  1744  	address := normalizeAddress(c.Param("address"), blockchain)
  1745  
  1746  	numTradesString := c.DefaultQuery("numTrades", "1000")
  1747  	exchange := c.Query("exchange")
  1748  
  1749  	var numTrades int64
  1750  	var err error
  1751  	numTrades, err = strconv.ParseInt(numTradesString, 10, 64)
  1752  	if err != nil {
  1753  		numTrades = 1000
  1754  	}
  1755  	if numTrades > 5000 {
  1756  		numTrades = 5000
  1757  	}
  1758  
  1759  	asset, err := env.RelDB.GetAsset(address, blockchain)
  1760  	if err != nil {
  1761  		restApi.SendError(c, http.StatusNotFound, err)
  1762  		return
  1763  	}
  1764  
  1765  	q, err := env.DataStore.GetLastTrades(asset, exchange, time.Now(), int(numTrades), true)
  1766  	if err != nil {
  1767  		if errors.Is(err, redis.Nil) {
  1768  			restApi.SendError(c, http.StatusNotFound, err)
  1769  		} else {
  1770  			restApi.SendError(c, http.StatusInternalServerError, err)
  1771  		}
  1772  	} else {
  1773  		c.JSON(http.StatusOK, q)
  1774  	}
  1775  }
  1776  
  1777  // GetMissingExchangeSymbol returns all unverified symbol
  1778  func (env *Env) GetMissingExchangeSymbol(c *gin.Context) {
  1779  	if !validateInputParams(c) {
  1780  		return
  1781  	}
  1782  
  1783  	exchange := c.Param("exchange")
  1784  
  1785  	//symbols, err := api.GetUnverifiedExchangeSymbols(exchange)
  1786  	symbols, err := env.RelDB.GetUnverifiedExchangeSymbols(exchange)
  1787  	if err != nil {
  1788  		restApi.SendError(c, http.StatusInternalServerError, err)
  1789  	} else {
  1790  		c.JSON(http.StatusOK, symbols)
  1791  	}
  1792  }
  1793  
  1794  func (env *Env) GetAsset(c *gin.Context) {
  1795  	if !validateInputParams(c) {
  1796  		return
  1797  	}
  1798  
  1799  	symbol := c.Param("symbol")
  1800  
  1801  	symbols, err := env.RelDB.GetAssets(symbol)
  1802  	if err != nil {
  1803  		restApi.SendError(c, http.StatusInternalServerError, err)
  1804  	} else {
  1805  		c.JSON(http.StatusOK, symbols)
  1806  	}
  1807  }
  1808  
  1809  func (env *Env) GetAssetExchanges(c *gin.Context) {
  1810  	if !validateInputParams(c) {
  1811  		return
  1812  	}
  1813  
  1814  	symbol := c.Param("symbol")
  1815  
  1816  	symbols, err := env.RelDB.GetAssetExchange(symbol)
  1817  	if err != nil {
  1818  		restApi.SendError(c, http.StatusInternalServerError, err)
  1819  	} else {
  1820  		c.JSON(http.StatusOK, symbols)
  1821  	}
  1822  }
  1823  
  1824  func (env *Env) GetAllBlockchains(c *gin.Context) {
  1825  	blockchains, err := env.RelDB.GetAllAssetsBlockchains()
  1826  	if err != nil {
  1827  		restApi.SendError(c, http.StatusInternalServerError, err)
  1828  	} else {
  1829  		c.JSON(http.StatusOK, blockchains)
  1830  	}
  1831  }
  1832  
  1833  func (env *Env) GetFeedStats(c *gin.Context) {
  1834  	if !validateInputParams(c) {
  1835  		return
  1836  	}
  1837  
  1838  	// Return type for the trades distribution statistics.
  1839  	type localDistType struct {
  1840  		NumTradesTotal   int64   `json:"NumTradesTotal"`
  1841  		NumBins          int     `json:"NumBins"`
  1842  		NumLowBins       int     `json:"NumberLowBins"`
  1843  		Threshold        int     `json:"Threshold"`
  1844  		SizeBinSeconds   int64   `json:"SizeBinSeconds"`
  1845  		AvgNumPerBin     float64 `json:"AverageNumberPerBin"`
  1846  		StdDeviation     float64 `json:"StandardDeviation"`
  1847  		TimeRangeSeconds int64   `json:"TimeRangeSeconds"`
  1848  	}
  1849  
  1850  	// Return type for pair volumes per exchange.
  1851  	type exchangeVolumes struct {
  1852  		Exchange    string
  1853  		PairVolumes []dia.PairVolume
  1854  	}
  1855  
  1856  	// Overall return type.
  1857  	type localReturn struct {
  1858  		Timestamp          time.Time
  1859  		TotalVolume        float64
  1860  		Price              float64
  1861  		TradesDistribution localDistType
  1862  		ExchangeVolumes    []exchangeVolumes
  1863  	}
  1864  
  1865  	// ---- Parse / check input ----
  1866  
  1867  	blockchain := c.Param("blockchain")
  1868  	address := normalizeAddress(c.Param("address"), blockchain)
  1869  
  1870  	// Make starttime and endtime from Unix time input strings.
  1871  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(24*time.Hour))
  1872  	if err != nil {
  1873  		log.Error("make timerange: ", err)
  1874  	}
  1875  
  1876  	// Check whether time range is feasible.
  1877  	if starttime.After(endtime) {
  1878  		restApi.SendError(c, http.StatusNotAcceptable, fmt.Errorf("endtime must be later than starttime"))
  1879  		return
  1880  	}
  1881  	if ok := utils.ValidTimeRange(starttime, endtime, time.Duration(24*time.Hour)); !ok {
  1882  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("time-range too big. max duration is %v", 24*time.Hour))
  1883  		return
  1884  	}
  1885  
  1886  	tradesBinThreshold, err := strconv.Atoi(c.DefaultQuery("tradesThreshold", "2"))
  1887  	if err != nil {
  1888  		log.Warn("parse trades bin threshold: ", err)
  1889  		tradesBinThreshold = 2
  1890  	}
  1891  
  1892  	sizeBinSeconds, err := strconv.Atoi(c.DefaultQuery("sizeBinSeconds", "120"))
  1893  	if err != nil {
  1894  		log.Warn("parse sizeBinSeconds: ", err)
  1895  		sizeBinSeconds = 120
  1896  	}
  1897  
  1898  	volumeThreshold, err := strconv.ParseFloat(c.DefaultQuery("volumeThreshold", "0"), 64)
  1899  	if err != nil {
  1900  		log.Warn("parse volumeThreshold: ", err)
  1901  	}
  1902  
  1903  	if sizeBinSeconds < 20 || sizeBinSeconds > 21600 {
  1904  		restApi.SendError(
  1905  			c,
  1906  			http.StatusInternalServerError,
  1907  			fmt.Errorf("sizeBinSeconds out of range. Must be between %v and %v", 20*time.Second, 6*time.Hour),
  1908  		)
  1909  		return
  1910  	}
  1911  
  1912  	// ---- Get data for input ----
  1913  
  1914  	asset := env.getAssetFromCache(ASSET_CACHE, blockchain, address)
  1915  	if err != nil {
  1916  		restApi.SendError(c, http.StatusInternalServerError, nil)
  1917  	}
  1918  
  1919  	volumeMap, err := env.DataStore.GetExchangePairVolumes(asset, starttime, endtime, volumeThreshold)
  1920  	if err != nil {
  1921  		restApi.SendError(c, http.StatusInternalServerError, nil)
  1922  		return
  1923  	}
  1924  
  1925  	numTradesSeries, err := env.DataStore.GetNumTradesSeries(asset, "", starttime, endtime, strconv.Itoa(sizeBinSeconds)+"s")
  1926  	if err != nil {
  1927  		log.Error("get number of trades' series: ", err)
  1928  	}
  1929  
  1930  	// ---- Fill return types with fetched data -----
  1931  
  1932  	var (
  1933  		result localReturn
  1934  		// tradesDistribution localDistType
  1935  		ev []exchangeVolumes
  1936  	)
  1937  
  1938  	for key, value := range volumeMap {
  1939  
  1940  		var e exchangeVolumes
  1941  		e.Exchange = key
  1942  		e.PairVolumes = value
  1943  
  1944  		// Collect total volume and full asset information.
  1945  		for i, v := range value {
  1946  			result.TotalVolume += v.Volume
  1947  			e.PairVolumes[i].Pair.QuoteToken = env.getAssetFromCache(ASSET_CACHE, blockchain, address)
  1948  			e.PairVolumes[i].Pair.BaseToken = env.getAssetFromCache(ASSET_CACHE, v.Pair.BaseToken.Blockchain, v.Pair.BaseToken.Address)
  1949  		}
  1950  
  1951  		// Sort pairs per exchange by volume.
  1952  		aux := value
  1953  		sort.Slice(aux, func(k, l int) bool {
  1954  			return aux[k].Volume > aux[l].Volume
  1955  		})
  1956  		ev = append(ev, e)
  1957  
  1958  		// Sort exchanges by volume.
  1959  		sort.Slice(ev, func(k, l int) bool {
  1960  			var ExchangeSums []float64
  1961  			for _, exchange := range ev {
  1962  				var S float64
  1963  				for _, vol := range exchange.PairVolumes {
  1964  					S += vol.Volume
  1965  				}
  1966  				ExchangeSums = append(ExchangeSums, S)
  1967  			}
  1968  			return ExchangeSums[k] > ExchangeSums[l]
  1969  		})
  1970  
  1971  	}
  1972  
  1973  	result.ExchangeVolumes = ev
  1974  	result.Timestamp = endtime
  1975  	result.Price, err = env.DataStore.GetAssetPriceUSD(asset, endtime.Add(-time.Duration(ASSETQUOTATION_LOOKBACK_HOURS)*time.Hour), endtime)
  1976  	if err != nil {
  1977  		log.Warn("get price for asset: ", err)
  1978  	}
  1979  
  1980  	// Trades Distribution values.
  1981  	result.TradesDistribution.Threshold = tradesBinThreshold
  1982  	result.TradesDistribution.NumBins = len(numTradesSeries)
  1983  	result.TradesDistribution.SizeBinSeconds = int64(sizeBinSeconds)
  1984  	var numTradesSeriesFloat []float64
  1985  	for _, num := range numTradesSeries {
  1986  		numTradesSeriesFloat = append(numTradesSeriesFloat, float64(num))
  1987  		result.TradesDistribution.NumTradesTotal += num
  1988  		if num < int64(tradesBinThreshold) {
  1989  			result.TradesDistribution.NumLowBins++
  1990  		}
  1991  	}
  1992  	if len(volumeMap) == 0 {
  1993  		result.TradesDistribution.NumBins = int(endtime.Sub(starttime).Seconds()) / sizeBinSeconds
  1994  		result.TradesDistribution.NumLowBins = result.TradesDistribution.NumBins
  1995  	}
  1996  	if len(numTradesSeries) > 0 {
  1997  		result.TradesDistribution.AvgNumPerBin = float64(result.TradesDistribution.NumTradesTotal) / float64(len(numTradesSeries))
  1998  	}
  1999  	result.TradesDistribution.StdDeviation = utils.StandardDeviation(numTradesSeriesFloat)
  2000  	result.TradesDistribution.TimeRangeSeconds = int64(endtime.Sub(starttime).Seconds())
  2001  
  2002  	c.JSON(http.StatusOK, result)
  2003  
  2004  }
  2005  
  2006  // GetAssetUpdates returns the number of updates an oracle with the given parameters
  2007  // would have done in the given time-range.
  2008  func (env *Env) GetAssetUpdates(c *gin.Context) {
  2009  	if !validateInputParams(c) {
  2010  		return
  2011  	}
  2012  
  2013  	type localDeviationType struct {
  2014  		Time      time.Time `json:"Time"`
  2015  		Deviation float64   `json:"Deviation"`
  2016  	}
  2017  	type localResultType struct {
  2018  		UpdateCount   int                  `json:"UpdateCount"`
  2019  		UpdatesPer24h float64              `json:"UpdatesPer24h"`
  2020  		Asset         dia.Asset            `json:"Asset"`
  2021  		Deviations    []localDeviationType `json:"Deviations"`
  2022  	}
  2023  
  2024  	blockchain := c.Param("blockchain")
  2025  	address := normalizeAddress(c.Param("address"), blockchain)
  2026  
  2027  	// Deviation in per mille.
  2028  	deviation, err := strconv.Atoi(c.Param("deviation"))
  2029  	if err != nil {
  2030  		restApi.SendError(c, http.StatusInternalServerError, err)
  2031  		return
  2032  	}
  2033  	FrequencySeconds, err := strconv.Atoi(c.Param("frequencySeconds"))
  2034  	if err != nil {
  2035  		restApi.SendError(c, http.StatusInternalServerError, err)
  2036  		return
  2037  	}
  2038  
  2039  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(24*60)*time.Minute)
  2040  	if err != nil {
  2041  		restApi.SendError(c, http.StatusInternalServerError, err)
  2042  		return
  2043  	}
  2044  	if endtime.Sub(starttime) > time.Duration(7*24*60)*time.Minute {
  2045  		restApi.SendError(c, http.StatusRequestedRangeNotSatisfiable, errors.New("requested time-range too large"))
  2046  		return
  2047  	}
  2048  	if ok := utils.ValidTimeRange(starttime, endtime, 30*24*time.Hour); !ok {
  2049  		restApi.SendError(c, http.StatusInternalServerError, fmt.Errorf("time-range too big. max duration is %v", 30*24*time.Hour))
  2050  		return
  2051  	}
  2052  
  2053  	asset, errGetAsset := env.RelDB.GetAsset(address, blockchain)
  2054  	if errGetAsset != nil {
  2055  		restApi.SendError(c, http.StatusInternalServerError, errGetAsset)
  2056  		return
  2057  	}
  2058  
  2059  	quotations, errGetAssetQuotations := env.DataStore.GetAssetQuotations(asset, starttime, endtime)
  2060  	if errGetAssetQuotations != nil {
  2061  		restApi.SendError(c, http.StatusInternalServerError, errGetAssetQuotations)
  2062  		return
  2063  	}
  2064  
  2065  	var lrt localResultType
  2066  
  2067  	lastQuotation := quotations[len(quotations)-1]
  2068  	lastValue := lastQuotation.Price
  2069  	for i := len(quotations) - 2; i >= 0; i-- {
  2070  		var diff float64
  2071  		// Oracle did not check for new quotation yet.
  2072  		if quotations[i].Time.Sub(lastQuotation.Time) < time.Duration(FrequencySeconds)*time.Second {
  2073  			continue
  2074  		}
  2075  		if lastValue != 0 {
  2076  			diff = math.Abs((quotations[i].Price - lastValue) / lastValue)
  2077  		} else {
  2078  			// Artificially make diff large enough for update (instead of infty).
  2079  			diff = float64(deviation)/1000 + 1
  2080  		}
  2081  		// If deviation is large enough, update values.
  2082  		if diff > float64(deviation)/1000 {
  2083  			lastQuotation = quotations[i]
  2084  			lastValue = lastQuotation.Price
  2085  
  2086  			var ldt localDeviationType
  2087  			ldt.Deviation = diff
  2088  			ldt.Time = lastQuotation.Time
  2089  			lrt.Deviations = append(lrt.Deviations, ldt)
  2090  			lrt.UpdateCount++
  2091  
  2092  		}
  2093  	}
  2094  
  2095  	lrt.Asset = asset
  2096  	lrt.UpdatesPer24h = float64(lrt.UpdateCount) * float64(time.Duration(24*time.Hour).Hours()/endtime.Sub(starttime).Hours())
  2097  	c.JSON(http.StatusOK, lrt)
  2098  }
  2099  
  2100  // GetAssetInfo returns quotation of asset with highest market cap among
  2101  // all assets with symbol ticker @symbol. Additionally information on exchanges and volumes.
  2102  func (env *Env) GetAssetInfo(c *gin.Context) {
  2103  	if !validateInputParams(c) {
  2104  		return
  2105  	}
  2106  	type localExchangeInfo struct {
  2107  		Name      string
  2108  		Volume24h float64
  2109  		NumPairs  int
  2110  		NumTrades int64
  2111  	}
  2112  
  2113  	type localAssetInfoReturn struct {
  2114  		Symbol             string
  2115  		Name               string
  2116  		Address            string
  2117  		Blockchain         string
  2118  		Price              float64
  2119  		PriceYesterday     float64
  2120  		VolumeYesterdayUSD float64
  2121  		Time               time.Time
  2122  		Source             string
  2123  		ExchangeInfo       []localExchangeInfo
  2124  	}
  2125  
  2126  	blockchain := c.Param("blockchain")
  2127  	address := normalizeAddress(c.Param("address"), blockchain)
  2128  
  2129  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(24*60)*time.Minute)
  2130  	if err != nil {
  2131  		restApi.SendError(c, http.StatusInternalServerError, err)
  2132  		return
  2133  	}
  2134  
  2135  	var quotationExtended localAssetInfoReturn
  2136  
  2137  	asset, err := env.RelDB.GetAsset(address, blockchain)
  2138  	if err != nil {
  2139  		restApi.SendError(c, http.StatusNotFound, err)
  2140  		return
  2141  	}
  2142  
  2143  	quotation, err := env.DataStore.GetAssetQuotation(asset, endtime.Add(-time.Duration(ASSETQUOTATION_LOOKBACK_HOURS)*time.Hour), endtime)
  2144  	if err != nil {
  2145  		restApi.SendError(c, http.StatusNotFound, errors.New("no quotation available"))
  2146  		return
  2147  	}
  2148  	quotationYesterday, err := env.DataStore.GetAssetQuotation(asset, endtime.Add(-time.Duration(ASSETQUOTATION_LOOKBACK_HOURS)*time.Hour), starttime)
  2149  	if err != nil {
  2150  		log.Warn("get quotation yesterday: ", err)
  2151  	} else {
  2152  		quotationExtended.PriceYesterday = quotationYesterday.Price
  2153  	}
  2154  	volumeYesterday, err := env.DataStore.GetVolumeInflux(asset, "", starttime, endtime)
  2155  	if err != nil {
  2156  		log.Warn("get volume yesterday: ", err)
  2157  	} else {
  2158  		quotationExtended.VolumeYesterdayUSD = *volumeYesterday
  2159  	}
  2160  	quotationExtended.Symbol = quotation.Asset.Symbol
  2161  	quotationExtended.Name = quotation.Asset.Name
  2162  	quotationExtended.Address = quotation.Asset.Address
  2163  	quotationExtended.Blockchain = quotation.Asset.Blockchain
  2164  	quotationExtended.Price = quotation.Price
  2165  	quotationExtended.Time = quotation.Time
  2166  	quotationExtended.Source = quotation.Source
  2167  
  2168  	// Get Exchange stats
  2169  	exchangemap, _, err := env.DataStore.GetActiveExchangesAndPairs(asset.Address, asset.Blockchain, int64(0), starttime, endtime)
  2170  	if err != nil {
  2171  		restApi.SendError(c, http.StatusNotFound, err)
  2172  		return
  2173  	}
  2174  	var eix []localExchangeInfo
  2175  	for exchange, pairs := range exchangemap {
  2176  		var ei localExchangeInfo
  2177  		ei.Name = exchange
  2178  		ei.NumPairs = len(pairs)
  2179  		ei.NumTrades, err = env.DataStore.GetNumTrades(exchange, asset.Address, asset.Blockchain, starttime, endtime)
  2180  		if err != nil {
  2181  			log.Errorf("get number of trades for %s: %v", exchange, err)
  2182  		}
  2183  		vol, err := env.DataStore.GetVolumeInflux(asset, exchange, starttime, endtime)
  2184  		if err != nil {
  2185  			log.Errorf("get 24h volume for %s: %v", exchange, err)
  2186  		} else {
  2187  			ei.Volume24h = *vol
  2188  		}
  2189  		eix = append(eix, ei)
  2190  	}
  2191  
  2192  	sort.Slice(eix, func(i, j int) bool {
  2193  		return eix[i].Volume24h > eix[j].Volume24h
  2194  	})
  2195  	quotationExtended.ExchangeInfo = eix
  2196  
  2197  	c.JSON(http.StatusOK, quotationExtended)
  2198  }
  2199  
  2200  // GetPairsInFeed returns quotation of asset with highest market cap among
  2201  // all assets with symbol ticker @symbol. Additionally information on exchanges and volumes.
  2202  func (env *Env) GetPairsInFeed(c *gin.Context) {
  2203  	if !validateInputParams(c) {
  2204  		return
  2205  	}
  2206  
  2207  	type localPairInfo struct {
  2208  		ForeignName string
  2209  		Exchange    string
  2210  		NumTrades   int64
  2211  		Quotetoken  dia.Asset
  2212  		Basetoken   dia.Asset
  2213  	}
  2214  
  2215  	type localAssetInfoReturn struct {
  2216  		Symbol             string
  2217  		Name               string
  2218  		Address            string
  2219  		Blockchain         string
  2220  		Price              float64
  2221  		PriceYesterday     float64
  2222  		VolumeYesterdayUSD float64
  2223  		Time               time.Time
  2224  		Source             string
  2225  		PairInfo           []localPairInfo
  2226  	}
  2227  	var quotationExtended localAssetInfoReturn
  2228  
  2229  	blockchain := c.Param("blockchain")
  2230  	address := normalizeAddress(c.Param("address"), blockchain)
  2231  	numTradesThreshold, err := strconv.ParseInt(c.Param("numTradesThreshold"), 10, 64)
  2232  	if err != nil {
  2233  		restApi.SendError(c, http.StatusInternalServerError, err)
  2234  		return
  2235  	}
  2236  
  2237  	starttime, endtime, err := utils.MakeTimerange(c.Query("starttime"), c.Query("endtime"), time.Duration(24*60)*time.Minute)
  2238  	if err != nil {
  2239  		restApi.SendError(c, http.StatusInternalServerError, err)
  2240  		return
  2241  	}
  2242  
  2243  	asset := env.getAssetFromCache(ASSET_CACHE, blockchain, address)
  2244  
  2245  	quotation, err := env.DataStore.GetAssetQuotation(asset, dia.CRYPTO_ZERO_UNIX_TIME, endtime)
  2246  	if err != nil {
  2247  		restApi.SendError(c, http.StatusNotFound, errors.New("no quotation available"))
  2248  		return
  2249  	}
  2250  	quotationYesterday, err := env.DataStore.GetAssetQuotation(asset, dia.CRYPTO_ZERO_UNIX_TIME, starttime)
  2251  	if err != nil {
  2252  		log.Warn("get quotation yesterday: ", err)
  2253  	} else {
  2254  		quotationExtended.PriceYesterday = quotationYesterday.Price
  2255  	}
  2256  	volumeYesterday, err := env.DataStore.Get24HoursAssetVolume(asset)
  2257  	if err != nil {
  2258  		log.Warn("get volume yesterday: ", err)
  2259  	} else {
  2260  		quotationExtended.VolumeYesterdayUSD = *volumeYesterday
  2261  	}
  2262  	quotationExtended.Symbol = quotation.Asset.Symbol
  2263  	quotationExtended.Name = quotation.Asset.Name
  2264  	quotationExtended.Address = quotation.Asset.Address
  2265  	quotationExtended.Blockchain = quotation.Asset.Blockchain
  2266  	quotationExtended.Price = quotation.Price
  2267  	quotationExtended.Time = quotation.Time
  2268  	quotationExtended.Source = quotation.Source
  2269  
  2270  	// Get Exchange stats
  2271  	exchangemap, pairCountMap, err := env.DataStore.GetActiveExchangesAndPairs(asset.Address, asset.Blockchain, numTradesThreshold, starttime, endtime)
  2272  	if err != nil {
  2273  		restApi.SendError(c, http.StatusNotFound, err)
  2274  		return
  2275  	}
  2276  
  2277  	var eix []localPairInfo
  2278  	for exchange, pairs := range exchangemap {
  2279  		var ei localPairInfo
  2280  		ei.Exchange = exchange
  2281  
  2282  		for _, pair := range pairs {
  2283  			ei.NumTrades = pairCountMap[pair.PairExchangeIdentifier(exchange)]
  2284  			ei.Quotetoken = asset
  2285  			ei.Basetoken = env.getAssetFromCache(ASSET_CACHE, pair.BaseToken.Blockchain, pair.BaseToken.Address)
  2286  			ei.ForeignName = ei.Quotetoken.Symbol + "-" + ei.Basetoken.Symbol
  2287  			eix = append(eix, ei)
  2288  		}
  2289  
  2290  	}
  2291  
  2292  	sort.Slice(eix, func(i, j int) bool {
  2293  		return eix[i].NumTrades > eix[j].NumTrades
  2294  	})
  2295  	quotationExtended.PairInfo = eix
  2296  
  2297  	c.JSON(http.StatusOK, quotationExtended)
  2298  }
  2299  
  2300  func (env *Env) GetAvailableAssets(c *gin.Context) {
  2301  	if !validateInputParams(c) {
  2302  		return
  2303  	}
  2304  
  2305  	assetClass := c.Param("assetClass")
  2306  
  2307  	if assetClass == "CryptoToken" {
  2308  		assets, err := env.RelDB.GetAllExchangeAssets(true)
  2309  		if err != nil {
  2310  			restApi.SendError(c, http.StatusInternalServerError, err)
  2311  			return
  2312  		}
  2313  		c.JSON(http.StatusOK, assets)
  2314  	} else {
  2315  		restApi.SendError(c, http.StatusInternalServerError, errors.New("unknown asset class"))
  2316  		return
  2317  	}
  2318  }
  2319  
  2320  func validateInputParams(c *gin.Context) bool {
  2321  
  2322  	// Validate input parameters.
  2323  	for _, input := range c.Params {
  2324  		if containsSpecialChars(input.Value) {
  2325  			restApi.SendError(c, http.StatusInternalServerError, errors.New("invalid input params"))
  2326  			return false
  2327  		}
  2328  	}
  2329  
  2330  	// Validate query parameters.
  2331  	for _, value := range c.Request.URL.Query() {
  2332  		for _, input := range value {
  2333  			if containsSpecialChars(input) {
  2334  				restApi.SendError(c, http.StatusInternalServerError, errors.New("invalid input params"))
  2335  				return false
  2336  			}
  2337  		}
  2338  	}
  2339  
  2340  	return true
  2341  }
  2342  
  2343  func containsSpecialChars(s string) bool {
  2344  	return strings.ContainsAny(s, "!@#$%^&*()'\"|{}[];><?/`~,")
  2345  }
  2346  
  2347  // Returns the EIP55 compliant address in case @blockchain has an Ethereum ChainID.
  2348  func makeAddressEIP55Compliant(address string, blockchain string) string {
  2349  	if strings.Contains(BLOCKCHAINS[blockchain].ChainID, "Ethereum") {
  2350  		return common.HexToAddress(address).Hex()
  2351  	}
  2352  	return address
  2353  }
  2354  
  2355  // Normalize address depending on the blockchain.
  2356  func normalizeAddress(address string, blockchain string) string {
  2357  	if strings.Contains(BLOCKCHAINS[blockchain].ChainID, "Ethereum") {
  2358  		return makeAddressEIP55Compliant(address, blockchain)
  2359  	}
  2360  	if BLOCKCHAINS[blockchain].Name == dia.OSMOSIS {
  2361  		if strings.Contains(address, "ibc-") && len(strings.Split(address, "-")[1]) > 1 {
  2362  			return "ibc/" + strings.Split(address, "-")[1]
  2363  		}
  2364  	}
  2365  	return address
  2366  }
  2367  
  2368  // getDecimalsFromCache returns the decimals of @asset, either from the map @localCache or from
  2369  // Postgres, in which latter case it also adds the decimals to the local cache.
  2370  // Remember that maps are always passed by reference.
  2371  func (env *Env) getDecimalsFromCache(localCache map[dia.Asset]uint8, asset dia.Asset) uint8 {
  2372  	if decimals, ok := localCache[asset]; ok {
  2373  		return decimals
  2374  	}
  2375  	fullAsset, err := env.RelDB.GetAsset(asset.Address, asset.Blockchain)
  2376  	if err != nil {
  2377  		log.Warnf("could not find asset with address %s on blockchain %s in postgres: ", asset.Address, asset.Blockchain)
  2378  	}
  2379  	localCache[asset] = fullAsset.Decimals
  2380  	return fullAsset.Decimals
  2381  }
  2382  
  2383  // getAssetFromCache returns the full asset given by blockchain and address, either from the map @localCache
  2384  // or from Postgres, in which latter case it also adds the asset to the local cache.
  2385  // Remember that maps are always passed by reference.
  2386  func (env *Env) getAssetFromCache(localCache map[string]dia.Asset, blockchain string, address string) dia.Asset {
  2387  	if asset, ok := localCache[assetIdentifier(blockchain, address)]; ok {
  2388  		return asset
  2389  	}
  2390  	fullAsset, err := env.RelDB.GetAsset(address, blockchain)
  2391  	if err != nil {
  2392  		log.Warnf("could not find asset with address %s on blockchain %s in postgres: ", address, blockchain)
  2393  	}
  2394  	localCache[assetIdentifier(blockchain, address)] = fullAsset
  2395  	return fullAsset
  2396  }
  2397  
  2398  func assetIdentifier(blockchain string, address string) string {
  2399  	return blockchain + "-" + address
  2400  }
  2401  
  2402  func (env *Env) SearchAssetList(c *gin.Context) {
  2403  	if !validateInputParams(c) {
  2404  		return
  2405  	}
  2406  
  2407  	querystring := c.Param("query")
  2408  	var (
  2409  		assets = []dia.AssetList{}
  2410  		err    error
  2411  	)
  2412  
  2413  	assets, err = env.RelDB.SearchAssetList(querystring)
  2414  	if err != nil {
  2415  		// restApi.SendError(c, http.StatusInternalServerError, errors.New("eror getting asset"))
  2416  		log.Errorln("error getting SearchAssetList", err)
  2417  	}
  2418  
  2419  	c.JSON(http.StatusOK, assets)
  2420  }
  2421  
  2422  func (env *Env) GetAssetList(c *gin.Context) {
  2423  	if !validateInputParams(c) {
  2424  		return
  2425  	}
  2426  	listname := c.Param("listname")
  2427  
  2428  	assets, err := env.RelDB.GetAssetList(listname)
  2429  	if err != nil {
  2430  		// restApi.SendError(c, http.StatusInternalServerError, errors.New("eror getting asset"))
  2431  		log.Errorln("error getting GetAssetList", err)
  2432  	}
  2433  	if len(assets) <= 0 {
  2434  		restApi.SendError(c, http.StatusNotFound, errors.New("asset missing"))
  2435  		return
  2436  	}
  2437  
  2438  	c.JSON(http.StatusOK, assets)
  2439  }
  2440  
  2441  func (env *Env) GetAssetListBySymbol(c *gin.Context) {
  2442  	if !validateInputParams(c) {
  2443  		return
  2444  	}
  2445  	listname := c.Param("listname")
  2446  
  2447  	querystring := c.Param("symbol")
  2448  	querystring = strings.ToUpper(querystring)
  2449  	var (
  2450  		assets = []dia.AssetList{}
  2451  		err    error
  2452  	)
  2453  
  2454  	assets, err = env.RelDB.GetAssetListBySymbol(querystring, listname)
  2455  	if err != nil {
  2456  		// restApi.SendError(c, http.StatusInternalServerError, errors.New("eror getting asset"))
  2457  		log.Errorln("error getting SearchAssetList", err)
  2458  	}
  2459  	if len(assets) <= 0 {
  2460  		restApi.SendError(c, http.StatusNotFound, errors.New("asset missing"))
  2461  		return
  2462  	}
  2463  
  2464  	selectedAsset := assets[0]
  2465  
  2466  	splitted := strings.Split(selectedAsset.AssetName, "-")
  2467  
  2468  	price, _, time, source, err := gqlclient.GetGraphqlAssetQuotationFromDia(splitted[0], splitted[1], 60, selectedAsset)
  2469  	if err != nil {
  2470  		// restApi.SendError(c, http.StatusInternalServerError, errors.New("eror getting asset"))
  2471  		log.Errorln("error getting GetGraphqlAssetQuotationFromDia", err)
  2472  	}
  2473  
  2474  	asset := dia.Asset{Symbol: selectedAsset.Symbol, Name: selectedAsset.CustomName, Blockchain: splitted[0], Address: splitted[1]}
  2475  	q := models.AssetQuotationFull{Symbol: asset.Symbol, Name: asset.Name, Address: asset.Address, Price: price, Blockchain: asset.Blockchain}
  2476  
  2477  	volumeYesterday, err := env.DataStore.Get24HoursAssetVolume(asset)
  2478  	if err != nil {
  2479  		log.Errorln("error getting Get24HoursAssetVolume", err)
  2480  	}
  2481  	q.VolumeYesterdayUSD = *volumeYesterday
  2482  	q.Time = time
  2483  	q.Source = strings.Join(source, ",")
  2484  	c.JSON(http.StatusOK, q)
  2485  }