github.com/diadata-org/diadata@v1.4.593/pkg/graphql/resolver/root.go (about)

     1  package resolver
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/diadata-org/diadata/pkg/dia"
    11  	queryhelper "github.com/diadata-org/diadata/pkg/dia/helpers/queryHelper"
    12  	scrapers "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers"
    13  	"github.com/diadata-org/diadata/pkg/utils"
    14  	"github.com/ethereum/go-ethereum/common"
    15  
    16  	models "github.com/diadata-org/diadata/pkg/model"
    17  	graphql "github.com/graph-gophers/graphql-go"
    18  	"github.com/sirupsen/logrus"
    19  )
    20  
    21  var (
    22  	log                   = logrus.New()
    23  	EXCHANGES             = scrapers.Exchanges
    24  	BLOCKCHAINS           = scrapers.Blockchains
    25  	lookbackTradesNumber  = 10
    26  	errInvalidInputParams = errors.New("invalid input params")
    27  	statusCodesMap        = make(map[string]int32)
    28  	statusError           = "err"
    29  	EXCHANGESYMBOL_CACHE  = make(map[string]dia.Asset)
    30  )
    31  
    32  const (
    33  	PAIR_SEPARATOR                 = "-"
    34  	LIST_SEPARATOR                 = ","
    35  	TRADE_VOLUME_THRESHOLD_DEFAULT = float64(0.001)
    36  )
    37  
    38  func init() {
    39  	statusCodesMap[""] = int32(0)
    40  	statusCodesMap[statusError] = int32(1)
    41  	statusCodesMap["pair not available on"] = int32(2)
    42  	statusCodesMap["exchange name not valid"] = int32(3)
    43  	statusCodesMap["not available on"] = int32(7)
    44  }
    45  
    46  // Resolver is the root resolver
    47  type DiaResolver struct {
    48  	DS              models.DB
    49  	RelDB           models.RelDB
    50  	InfluxBatchSize int64
    51  }
    52  
    53  func (r *DiaResolver) GetSupply(ctx context.Context, args struct{ Symbol graphql.NullString }) (*SupplyResolver, error) {
    54  	if containsSpecialChars(*args.Symbol.Value) {
    55  		return nil, errInvalidInputParams
    56  	}
    57  	q, err := r.DS.GetLatestSupply(*args.Symbol.Value, &r.RelDB)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	return &SupplyResolver{q: q}, nil
    62  }
    63  
    64  func (r *DiaResolver) GetSupplies(ctx context.Context, args struct{ Symbol graphql.NullString }) (*[]*SupplyResolver, error) {
    65  	starttime := time.Unix(1, 0)
    66  	endtime := time.Now()
    67  	if containsSpecialChars(*args.Symbol.Value) {
    68  		return nil, errInvalidInputParams
    69  	}
    70  
    71  	q, err := r.DS.GetSupply(*args.Symbol.Value, starttime, endtime, &r.RelDB)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	var sr []*SupplyResolver
    77  
    78  	for _, supply := range q {
    79  		sr = append(sr, &SupplyResolver{q: &supply})
    80  
    81  	}
    82  	return &sr, nil
    83  }
    84  
    85  type TradeBlock struct {
    86  	Trades []dia.Trade
    87  }
    88  
    89  type Asset struct {
    90  	Address    graphql.NullString
    91  	BlockChain graphql.NullString
    92  }
    93  
    94  type FeedSelection struct {
    95  	Address            graphql.NullString
    96  	Blockchain         graphql.NullString
    97  	LiquidityThreshold graphql.NullFloat
    98  	Exchangepairs      *[]Exchangepair
    99  }
   100  
   101  type Exchangepair struct {
   102  	Exchange           graphql.NullString
   103  	Pairs              *[]graphql.NullString
   104  	LiquidityThreshold graphql.NullFloat
   105  }
   106  
   107  func (r *DiaResolver) GetChart(ctx context.Context, args struct {
   108  	Filter               graphql.NullString
   109  	BlockDurationSeconds graphql.NullInt
   110  	BlockShiftSeconds    graphql.NullInt
   111  	Symbol               graphql.NullString
   112  	StartTime            graphql.NullTime
   113  	EndTime              graphql.NullTime
   114  	Exchanges            *[]graphql.NullString
   115  	Address              graphql.NullString
   116  	BlockChain           graphql.NullString
   117  	BaseAsset            *[]Asset
   118  }) (*[]*FilterPointResolver, error) {
   119  	fpr, _ := r.GetChartMeta(ctx, args)
   120  	return fpr.fpr, nil
   121  }
   122  
   123  // TO DO: Use context?
   124  func (r *DiaResolver) GetChartMeta(ctx context.Context, args struct {
   125  	Filter               graphql.NullString
   126  	BlockDurationSeconds graphql.NullInt
   127  	BlockShiftSeconds    graphql.NullInt
   128  	Symbol               graphql.NullString
   129  	StartTime            graphql.NullTime
   130  	EndTime              graphql.NullTime
   131  	Exchanges            *[]graphql.NullString
   132  	Address              graphql.NullString
   133  	BlockChain           graphql.NullString
   134  	BaseAsset            *[]Asset
   135  }) (*FilterPointMetaResolver, error) {
   136  
   137  	if containsSpecialChars(*args.Symbol.Value) || containsSpecialChars(*args.Address.Value) || containsSpecialChars(*args.BlockChain.Value) {
   138  		return nil, errInvalidInputParams
   139  	}
   140  	var (
   141  		blockShiftSeconds int64
   142  		tradeBlocks       []queryhelper.Block
   143  		blockchain        string
   144  		address           string
   145  		sr                FilterPointMetaResolver
   146  		asset             dia.Asset
   147  		err               error
   148  		baseAssets        []dia.Asset
   149  	)
   150  
   151  	// Parsing input parameters.
   152  	filter := args.Filter.Value
   153  	if containsSpecialChars(*filter) {
   154  		return nil, errInvalidInputParams
   155  	}
   156  
   157  	blockSizeSeconds := int64(*args.BlockDurationSeconds.Value)
   158  	if args.BlockShiftSeconds.Value != nil {
   159  		blockShiftSeconds = int64(*args.BlockShiftSeconds.Value)
   160  	}
   161  	if args.Address.Value != nil {
   162  		if containsSpecialChars(*args.Address.Value) {
   163  			return nil, errInvalidInputParams
   164  		}
   165  		address = *args.Address.Value
   166  
   167  	}
   168  	if args.BlockChain.Value != nil {
   169  		if containsSpecialChars(*args.BlockChain.Value) {
   170  			return nil, errInvalidInputParams
   171  		}
   172  		blockchain = *args.BlockChain.Value
   173  	}
   174  	symbol := *args.Symbol.Value
   175  	starttime := args.StartTime.Value.Time
   176  	endtime := args.EndTime.Value.Time
   177  	starttimeimmutable := args.StartTime.Value.Time
   178  	endtimeimmutable := args.EndTime.Value.Time
   179  
   180  	if containsSpecialChars(symbol) {
   181  		return nil, errInvalidInputParams
   182  	}
   183  	exchanges := args.Exchanges
   184  	var exchangesString []string
   185  	if exchanges != nil {
   186  		for _, v := range *exchanges {
   187  			if containsSpecialChars(*v.Value) {
   188  				continue
   189  			}
   190  			exchangesString = append(exchangesString, *v.Value)
   191  		}
   192  	}
   193  
   194  	// Fetch base assets.
   195  	argsbaseasset := args.BaseAsset
   196  	if argsbaseasset != nil {
   197  		for _, baseasset := range *argsbaseasset {
   198  			if containsSpecialChars(*baseasset.Address.Value) || containsSpecialChars(*baseasset.BlockChain.Value) {
   199  				continue
   200  			}
   201  			asset, err = r.RelDB.GetAsset(*baseasset.Address.Value, *baseasset.BlockChain.Value)
   202  			if err != nil {
   203  				log.Errorf("Asset not found with address %s and blockchain %s ", address, blockchain)
   204  				continue
   205  			}
   206  			baseAssets = append(baseAssets, asset)
   207  		}
   208  	}
   209  
   210  	// Get quote asset either by blockchain&address or by topAsset method.
   211  	if address != "" && blockchain != "" {
   212  		asset, err = r.RelDB.GetAsset(address, blockchain)
   213  		if err != nil {
   214  			log.Errorf("Asset not found with address %s and blockchain %s ", address, blockchain)
   215  			return &sr, err
   216  		}
   217  	} else {
   218  		assets, err := r.RelDB.GetTopAssetByVolume(symbol)
   219  		if err != nil {
   220  			log.Errorf("Asset not found with symbol %s ", symbol)
   221  			return &sr, err
   222  		}
   223  
   224  		log.Infoln("All assets having same symbol", assets)
   225  		asset = assets[0]
   226  	}
   227  
   228  	log.Infoln("Asset Selected", asset)
   229  	var (
   230  		filterPoints, emaFilterPoints []dia.FilterPoint
   231  		filterMetadata                *dia.FilterPointMetadata
   232  	)
   233  
   234  	if *filter != "ema" {
   235  
   236  		if endtime.After(time.Now()) {
   237  			endtime = time.Now()
   238  		}
   239  
   240  		// Limit the (time-)range of the query to 24 hours.
   241  		maxStartTime := endtime.Add(-time.Duration(60*24) * time.Minute)
   242  		if starttime.Before(maxStartTime) {
   243  			starttime = maxStartTime
   244  		}
   245  
   246  		// Set blockShiftSeconds = blockSizeSeconds per default if not given.
   247  		if blockShiftSeconds == 0 {
   248  			blockShiftSeconds = blockSizeSeconds
   249  		}
   250  
   251  		// (Potentially) decrease starttime such that an integer number of bins fits into the whole range.
   252  		if endtime.Unix()-starttime.Unix() < blockSizeSeconds {
   253  			// Just one block.
   254  			starttime = time.Unix(endtime.Unix()-blockSizeSeconds, 0)
   255  		} else {
   256  			starttime = time.Unix(starttime.Unix()-(blockShiftSeconds-((endtime.Unix()-starttime.Unix()-blockSizeSeconds)%blockShiftSeconds)), 0)
   257  		}
   258  
   259  		// Make time bins according to block size and block shift parameters.
   260  		bins := utils.MakeBins(starttime, endtime, blockSizeSeconds, blockShiftSeconds)
   261  
   262  		// Fetch trades.
   263  		var trades []dia.Trade
   264  		if blockShiftSeconds <= blockSizeSeconds {
   265  			// Fetch all trades in time range.
   266  			trades, err = r.DS.GetTradesByExchangesAndBaseAssets(asset, baseAssets, exchangesString, starttime, endtime, 0)
   267  			if err != nil {
   268  				log.Error("GetTradesByExchangesAndBaseAssets: ", err)
   269  				return &sr, err
   270  			}
   271  		} else {
   272  			// Fetch trades batched for disjoint bins.
   273  			var starttimes, endtimes []time.Time
   274  			for _, bin := range bins {
   275  				starttimes = append(starttimes, bin.Starttime)
   276  				endtimes = append(endtimes, bin.Endtime)
   277  			}
   278  			trades, err = r.DS.GetTradesByExchangesBatched(asset, baseAssets, exchangesString, starttimes, endtimes, 0)
   279  			if err != nil {
   280  				log.Error("GetTradesByExchangesAndBaseAssets: ", err)
   281  				return &sr, err
   282  			}
   283  		}
   284  		log.Println("Generating blocks, Total Trades", len(trades))
   285  		log.Info("generating bins. Total bins: ", len(bins))
   286  
   287  		if len(trades) > 0 && len(bins) > 0 {
   288  			// In case the first bin is empty, look for the last trade before @starttime.
   289  			if !utils.IsInBin(trades[0].Time, bins[0]) {
   290  				previousTrade, baseAsseteErr := r.DS.GetTradesByExchangesAndBaseAssets(asset, baseAssets, exchangesString, endtime.AddDate(0, 0, -10), starttime, 1)
   291  				if baseAsseteErr != nil || len(previousTrade) == 0 {
   292  					log.Error("get initial trade: ", err, baseAsseteErr)
   293  					// Fill with a zero trade so we can build blocks.
   294  					auxTrade := trades[0]
   295  					auxTrade.Volume = 0
   296  					auxTrade.Price = 0
   297  					auxTrade.EstimatedUSDPrice = 0
   298  					trades = append([]dia.Trade{auxTrade}, trades...)
   299  				} else {
   300  					trades = append([]dia.Trade{previousTrade[0]}, trades...)
   301  				}
   302  			}
   303  			tradeBlocks = queryhelper.NewBlockGenerator(trades).GenerateBlocks(blockSizeSeconds, blockShiftSeconds, bins)
   304  			log.Println("Total TradeBlocks", len(tradeBlocks))
   305  		}
   306  
   307  	} else if *filter == "ema" {
   308  		emaFilterPoints, err = r.DS.GetFilter("MA120", asset, "", starttimeimmutable, endtimeimmutable)
   309  		if err != nil {
   310  			log.Errorln("Error getting filter", err)
   311  		}
   312  	}
   313  
   314  	switch *filter {
   315  	case "ema":
   316  		{
   317  			filterPoints, filterMetadata = queryhelper.FilterEMA(emaFilterPoints, asset, int(blockSizeSeconds))
   318  		}
   319  	case "mair":
   320  		{
   321  			filterPoints, filterMetadata = queryhelper.FilterMAIR(tradeBlocks, asset, int(blockSizeSeconds))
   322  		}
   323  	case "ma":
   324  		{
   325  			filterPoints, filterMetadata = queryhelper.FilterMA(tradeBlocks, asset, int(blockSizeSeconds))
   326  		}
   327  	case "vwap":
   328  		{
   329  			filterPoints, filterMetadata = queryhelper.FilterVWAP(tradeBlocks, asset, int(blockSizeSeconds))
   330  		}
   331  	case "vwapir":
   332  		{
   333  			filterPoints, filterMetadata = queryhelper.FilterVWAPIR(tradeBlocks, asset, int(blockSizeSeconds))
   334  		}
   335  	case "medir":
   336  		{
   337  			filterPoints, filterMetadata = queryhelper.FilterMEDIR(tradeBlocks, asset, int(blockSizeSeconds))
   338  		}
   339  	case "vol":
   340  		{
   341  			filterPoints, filterMetadata = queryhelper.FilterVOL(tradeBlocks, asset, int(blockSizeSeconds))
   342  		}
   343  
   344  	}
   345  
   346  	var fpr []*FilterPointResolver
   347  
   348  	for _, fp := range filterPoints {
   349  		fpr = append(fpr, &FilterPointResolver{q: fp})
   350  
   351  	}
   352  
   353  	return &FilterPointMetaResolver{fpr: &fpr, min: filterMetadata.Min, max: filterMetadata.Max}, nil
   354  }
   355  
   356  // GetxcFeed returns filter points for a (possibly) cross chain feed given by @QuoteAssets.
   357  func (r *DiaResolver) GetxcFeed(ctx context.Context, args struct {
   358  	Filter            graphql.NullString
   359  	QuoteAssets       *[]Asset
   360  	Exchanges         *[]graphql.NullString
   361  	BlockSizeSeconds  graphql.NullInt
   362  	BlockShiftSeconds graphql.NullInt
   363  	StartTime         graphql.NullTime
   364  	EndTime           graphql.NullTime
   365  }) (*[]*FilterPointResolver, error) {
   366  
   367  	// --- Parse input data ---
   368  	var (
   369  		vr           *[]*FilterPointResolver
   370  		tradeBlocks  []queryhelper.Block
   371  		filterPoints []dia.FilterPoint
   372  	)
   373  
   374  	filter := args.Filter.Value
   375  	if containsSpecialChars(*filter) {
   376  		return nil, errInvalidInputParams
   377  	}
   378  
   379  	var quoteAssets []dia.Asset
   380  	if len(*args.QuoteAssets) > 0 {
   381  		for i := range *args.QuoteAssets {
   382  			quoteAssets = append(quoteAssets, dia.Asset{Address: *(*args.QuoteAssets)[i].Address.Value, Blockchain: *(*args.QuoteAssets)[i].BlockChain.Value})
   383  		}
   384  	}
   385  
   386  	var exchanges []string
   387  	if len(*args.Exchanges) > 0 {
   388  		for i := range *args.Exchanges {
   389  			if containsSpecialChars(*(*args.Exchanges)[i].Value) {
   390  				continue
   391  			}
   392  			exchanges = append(exchanges, *(*args.Exchanges)[i].Value)
   393  		}
   394  	}
   395  
   396  	blockSizeSeconds := int64(*args.BlockSizeSeconds.Value)
   397  	var blockShiftSeconds int64
   398  	if args.BlockShiftSeconds.Value != nil {
   399  		blockShiftSeconds = int64(*args.BlockShiftSeconds.Value)
   400  	} else {
   401  		blockShiftSeconds = blockSizeSeconds
   402  	}
   403  
   404  	// --- Make time bins according to block size and block shift parameters ---
   405  	starttime := args.StartTime.Value.Time
   406  	endtime := args.EndTime.Value.Time
   407  	if args.EndTime.Set {
   408  		endtime = args.EndTime.Value.Time
   409  	}
   410  	if endtime.After(time.Now()) {
   411  		endtime = time.Now()
   412  	}
   413  
   414  	// --- Limit the (time-)range of the query to 24 hours ---
   415  	maxStartTime := endtime.Add(-time.Duration(60*24) * time.Minute)
   416  	if starttime.Before(maxStartTime) {
   417  		starttime = maxStartTime
   418  	}
   419  
   420  	// (Potentially) decrease starttime such that an integer number of bins fits into the whole range.
   421  	if endtime.Unix()-starttime.Unix() < blockSizeSeconds {
   422  		// Just one block.
   423  		starttime = time.Unix(endtime.Unix()-blockSizeSeconds, 0)
   424  	} else {
   425  		starttime = time.Unix(starttime.Unix()-(blockShiftSeconds-((endtime.Unix()-starttime.Unix()-blockSizeSeconds)%blockShiftSeconds)), 0)
   426  	}
   427  
   428  	// --- Make time bins according to block size and block shift parameters ---
   429  	bins := utils.MakeBins(starttime, endtime, blockSizeSeconds, blockShiftSeconds)
   430  	var (
   431  		starttimes []time.Time
   432  		endtimes   []time.Time
   433  	)
   434  	if blockShiftSeconds <= blockSizeSeconds {
   435  		// Bins overlap and hence all trades in total time range are needed.
   436  		starttimes = []time.Time{starttime}
   437  		endtimes = []time.Time{endtime}
   438  	} else {
   439  		// Bins are disjoint. Hence, only fetch trades per bin.
   440  		for _, bin := range bins {
   441  			starttimes = append(starttimes, bin.Starttime)
   442  			endtimes = append(endtimes, bin.Endtime)
   443  		}
   444  	}
   445  
   446  	// --- Fetch trades ---
   447  	trades, err := r.DS.GetxcTradesByExchangesBatched(quoteAssets, exchanges, starttimes, endtimes)
   448  	if err != nil {
   449  		return vr, err
   450  	}
   451  
   452  	if len(trades) > 0 && len(bins) > 0 {
   453  		// In case the first bin is empty, look for the last trade before @starttime.
   454  		if !utils.IsInBin(trades[0].Time, bins[0]) {
   455  			trades = r.fillFirstBin(quoteAssets, exchanges, trades, starttime.AddDate(0, 0, -10), starttime)
   456  		}
   457  		tradeBlocks = queryhelper.NewBlockGenerator(trades).GenerateBlocks(blockSizeSeconds, blockShiftSeconds, bins)
   458  		log.Println("Total TradeBlocks", len(tradeBlocks))
   459  	}
   460  
   461  	switch *filter {
   462  	case "mair":
   463  		{
   464  			filterPoints, _ = queryhelper.FilterMAIR(tradeBlocks, quoteAssets[0], int(blockSizeSeconds))
   465  		}
   466  	case "ma":
   467  		{
   468  			filterPoints, _ = queryhelper.FilterMA(tradeBlocks, quoteAssets[0], int(blockSizeSeconds))
   469  		}
   470  	case "vwap":
   471  		{
   472  			filterPoints, _ = queryhelper.FilterVWAP(tradeBlocks, quoteAssets[0], int(blockSizeSeconds))
   473  		}
   474  	case "vwapir":
   475  		{
   476  			filterPoints, _ = queryhelper.FilterVWAPIR(tradeBlocks, quoteAssets[0], int(blockSizeSeconds))
   477  		}
   478  	case "medir":
   479  		{
   480  			filterPoints, _ = queryhelper.FilterMEDIR(tradeBlocks, quoteAssets[0], int(blockSizeSeconds))
   481  		}
   482  	case "vol":
   483  		{
   484  			filterPoints, _ = queryhelper.FilterVOL(tradeBlocks, quoteAssets[0], int(blockSizeSeconds))
   485  		}
   486  	}
   487  
   488  	var fpr []*FilterPointResolver
   489  	for _, fp := range filterPoints {
   490  		fpr = append(fpr, &FilterPointResolver{q: fp})
   491  	}
   492  
   493  	return &fpr, nil
   494  }
   495  
   496  // TO DO: Use context?
   497  func (r *DiaResolver) GetFeed(ctx context.Context, args struct {
   498  	Filter               graphql.NullString
   499  	BlockSizeSeconds     graphql.NullInt
   500  	BlockShiftSeconds    graphql.NullInt
   501  	StartTime            graphql.NullTime
   502  	EndTime              graphql.NullTime
   503  	TradeVolumeThreshold graphql.NullFloat
   504  	NativeDenomination   graphql.NullBool
   505  	FeedSelection        *[]FeedSelection
   506  }) (*[]*FilterPointExtendedResolver, error) {
   507  	var (
   508  		tradeBlocks          []queryhelper.Block
   509  		sr                   *[]*FilterPointExtendedResolver
   510  		starttime            time.Time
   511  		endtime              time.Time
   512  		blockShiftSeconds    int64
   513  		tradeVolumeThreshold float64
   514  		err                  error
   515  		nativeDenomination   bool
   516  	)
   517  
   518  	// Parsing input parameters.
   519  	if args.Filter.Value == nil {
   520  		return sr, errors.New("filter must be set")
   521  	}
   522  	filter := *args.Filter.Value
   523  	if containsSpecialChars(filter) {
   524  		return nil, errInvalidInputParams
   525  	}
   526  	blockSizeSeconds := int64(*args.BlockSizeSeconds.Value)
   527  	if args.BlockShiftSeconds.Value != nil {
   528  		blockShiftSeconds = int64(*args.BlockShiftSeconds.Value)
   529  	}
   530  	// Set blockShiftSeconds = blockSizeSeconds per default if not given.
   531  	if blockShiftSeconds == 0 {
   532  		blockShiftSeconds = blockSizeSeconds
   533  	}
   534  
   535  	// Handle timestamps.
   536  	if args.EndTime.Value != nil {
   537  		endtime = args.EndTime.Value.Time
   538  	} else {
   539  		endtime = time.Now()
   540  	}
   541  	if args.StartTime.Value != nil {
   542  		starttime = args.StartTime.Value.Time
   543  	} else {
   544  		starttime = endtime.Add(-time.Duration(1 * time.Hour))
   545  	}
   546  	if endtime.Before(starttime) {
   547  		return sr, errors.New("startTime must be before EndTime")
   548  	}
   549  	if endtime.After(time.Now()) {
   550  		endtime = time.Now()
   551  	}
   552  	// --- Limit the (time-)range of the query to 24 hours ---
   553  	maxStartTime := endtime.Add(-time.Duration(60*24) * time.Minute)
   554  	if starttime.Before(maxStartTime) {
   555  		starttime = maxStartTime
   556  	}
   557  	// (Potentially) decrease starttime such that an integer number of bins fits into the whole range.
   558  	if endtime.Unix()-starttime.Unix() < blockSizeSeconds {
   559  		// Just one block.
   560  		starttime = time.Unix(endtime.Unix()-blockSizeSeconds, 0)
   561  	} else {
   562  		starttime = time.Unix(starttime.Unix()-(blockShiftSeconds-((endtime.Unix()-starttime.Unix()-blockSizeSeconds)%blockShiftSeconds)), 0)
   563  	}
   564  	// starttimeimmutable := starttime
   565  	// endtimeimmutable := endtime
   566  
   567  	if args.TradeVolumeThreshold.Value != nil {
   568  		tradeVolumeThreshold = *args.TradeVolumeThreshold.Value
   569  	} else {
   570  		tradeVolumeThreshold = TRADE_VOLUME_THRESHOLD_DEFAULT
   571  	}
   572  
   573  	// If nativeDenomination is true, price is returned in terms of the respective base asset.
   574  	// Default is false, i.e. price is returned in USD denomination.
   575  	if args.NativeDenomination.Value != nil {
   576  		nativeDenomination = *args.NativeDenomination.Value
   577  	}
   578  
   579  	if args.FeedSelection == nil {
   580  		return sr, errors.New("at least 1 asset must be selected")
   581  	}
   582  	feedselection, statusMessage, statusCode, err := r.castLocalFeedSelection(*args.FeedSelection)
   583  	if err != nil {
   584  		return sr, err
   585  	}
   586  
   587  	var (
   588  		// filterPoints, emaFilterPoints []dia.FilterPoint
   589  		filterPoints []dia.FilterPointExtended
   590  	)
   591  
   592  	// Make time bins according to block size and block shift parameters.
   593  	bins := utils.MakeBins(starttime, endtime, blockSizeSeconds, blockShiftSeconds)
   594  
   595  	// Fetch trades.
   596  	var (
   597  		trades     []dia.Trade
   598  		starttimes []time.Time
   599  		endtimes   []time.Time
   600  	)
   601  
   602  	if blockShiftSeconds <= blockSizeSeconds {
   603  		// Fetch all trades in time range.
   604  		starttimes = []time.Time{starttime}
   605  		endtimes = []time.Time{endtime}
   606  	} else {
   607  		// Fetch trades batched for disjoint bins.
   608  		for _, bin := range bins {
   609  			starttimes = append(starttimes, bin.Starttime)
   610  			endtimes = append(endtimes, bin.Endtime)
   611  		}
   612  	}
   613  	trades, err = r.DS.GetTradesByFeedSelection(feedselection, starttimes, endtimes, 0)
   614  	if err != nil {
   615  		return sr, err
   616  	}
   617  	log.Println("Generating blocks, Total Trades", len(trades))
   618  	log.Info("generating bins. Total bins: ", len(bins))
   619  
   620  	if len(bins) > 0 {
   621  		// In case the first bin is empty, look for the last trades before @starttime
   622  		// in order to select the most recent one with sufficient volume.
   623  		if len(trades) == 0 || !utils.IsInBin(trades[0].Time, bins[0]) || trades[0].VolumeUSD() < tradeVolumeThreshold {
   624  			previousTrade, err := r.DS.GetTradesByFeedSelection(feedselection, []time.Time{endtime.AddDate(0, 0, -10)}, []time.Time{starttime}, lookbackTradesNumber)
   625  			if len(previousTrade) == 0 {
   626  				log.Error("get initial trade: ", err)
   627  				// Fill with a zero trade so we can build blocks.
   628  				auxTrade := trades[0]
   629  				auxTrade.Volume = 0
   630  				auxTrade.Price = 0
   631  				auxTrade.EstimatedUSDPrice = 0
   632  				trades = append([]dia.Trade{auxTrade}, trades...)
   633  			} else {
   634  				for _, t := range previousTrade {
   635  					if t.VolumeUSD() > tradeVolumeThreshold {
   636  						trades = append([]dia.Trade{t}, trades...)
   637  						break
   638  					}
   639  				}
   640  			}
   641  		}
   642  		tradeBlocks = queryhelper.NewBlockGenerator(trades).GenerateBlocks(blockSizeSeconds, blockShiftSeconds, bins)
   643  		log.Println("Total TradeBlocks", len(tradeBlocks))
   644  	}
   645  
   646  	switch filter {
   647  	case "mair":
   648  		{
   649  			filterPoints = queryhelper.FilterMAIRextended(tradeBlocks, feedselection[0].Asset, int(blockSizeSeconds), tradeVolumeThreshold, nativeDenomination)
   650  		}
   651  	case "ma":
   652  		{
   653  			filterPoints = queryhelper.FilterMAextended(tradeBlocks, feedselection[0].Asset, int(blockSizeSeconds), tradeVolumeThreshold, nativeDenomination)
   654  		}
   655  	case "vwap":
   656  		{
   657  			filterPoints = queryhelper.FilterVWAPextended(tradeBlocks, feedselection[0].Asset, int(blockSizeSeconds), tradeVolumeThreshold, nativeDenomination)
   658  		}
   659  	case "vwapir":
   660  		{
   661  			filterPoints = queryhelper.FilterVWAPIRextended(tradeBlocks, feedselection[0].Asset, int(blockSizeSeconds), tradeVolumeThreshold, nativeDenomination)
   662  		}
   663  	case "medir":
   664  		{
   665  			filterPoints = queryhelper.FilterMEDIRextended(tradeBlocks, feedselection[0].Asset, int(blockSizeSeconds), tradeVolumeThreshold, nativeDenomination)
   666  		}
   667  	case "vol":
   668  		{
   669  			filterPoints = queryhelper.FilterVOLextended(tradeBlocks, feedselection[0].Asset, int(blockSizeSeconds))
   670  		}
   671  
   672  	}
   673  
   674  	var fper []*FilterPointExtendedResolver
   675  
   676  	for _, fpe := range filterPoints {
   677  		fpe.StatusMessage = statusMessage
   678  		fpe.StatusCode = statusCode
   679  		fper = append(fper, &FilterPointExtendedResolver{q: fpe})
   680  	}
   681  
   682  	return &fper, nil
   683  }
   684  
   685  // GetFeedAggregation computes stats such as volume and number of trades fo a given FeedSelection.
   686  func (r *DiaResolver) GetFeedAggregation(ctx context.Context, args struct {
   687  	StartTime            graphql.NullTime
   688  	EndTime              graphql.NullTime
   689  	TradeVolumeThreshold graphql.NullFloat
   690  	FeedSelection        *[]FeedSelection
   691  }) (*[]*FeedSelectionAggregatedResolver, error) {
   692  	var (
   693  		sr                   []*FeedSelectionAggregatedResolver
   694  		starttime            time.Time
   695  		endtime              time.Time
   696  		tradeVolumeThreshold float64
   697  		err                  error
   698  	)
   699  
   700  	// Handle timestamps.
   701  	if args.EndTime.Value != nil {
   702  		endtime = args.EndTime.Value.Time
   703  	} else {
   704  		endtime = time.Now()
   705  	}
   706  	if args.StartTime.Value != nil {
   707  		starttime = args.StartTime.Value.Time
   708  	} else {
   709  		starttime = endtime.Add(-time.Duration(1 * time.Hour))
   710  	}
   711  	if endtime.Before(starttime) {
   712  		return &sr, errors.New("startTime must be before EndTime")
   713  	}
   714  	if endtime.After(time.Now()) {
   715  		endtime = time.Now()
   716  	}
   717  	// --- Limit the (time-)range of the query to 24 hours ---
   718  	maxStartTime := endtime.Add(-time.Duration(7*24*60) * time.Minute)
   719  	if starttime.Before(maxStartTime) {
   720  		starttime = maxStartTime
   721  	}
   722  
   723  	if args.TradeVolumeThreshold.Value != nil {
   724  		tradeVolumeThreshold = *args.TradeVolumeThreshold.Value
   725  	} else {
   726  		tradeVolumeThreshold = TRADE_VOLUME_THRESHOLD_DEFAULT
   727  	}
   728  
   729  	if args.FeedSelection == nil {
   730  		return &sr, errors.New("at least 1 asset must be selected")
   731  	}
   732  	feedselection, statusMessage, statusCode, err := r.castLocalFeedSelection(*args.FeedSelection)
   733  	if err != nil {
   734  		return &sr, err
   735  	}
   736  
   737  	// Get aggregated data in given time-range.
   738  	fsa, err := r.DS.GetAggregatedFeedSelection(feedselection, starttime, endtime, tradeVolumeThreshold)
   739  	if err != nil {
   740  		log.Error("GetAggregatedFeedSelection: ", err)
   741  		return &sr, err
   742  	}
   743  
   744  	// Fill response slice.
   745  	var fsar []*FeedSelectionAggregatedResolver
   746  	for _, fs := range fsa {
   747  		fs.StatusMessage = statusMessage
   748  		fs.StatusCode = statusCode
   749  		if fs.Pooladdress != "" {
   750  			pool, err := r.RelDB.GetPoolByAddress(fs.Basetoken.Blockchain, fs.Pooladdress)
   751  			if err != nil {
   752  				log.Error("GetPoolByAddress: ", err)
   753  			}
   754  			if pool.Time.After(time.Now().AddDate(0, 0, -7)) {
   755  				fs.PoolLiquidityUSD, _ = pool.GetPoolLiquidityUSD()
   756  			}
   757  		}
   758  		fsar = append(fsar, &FeedSelectionAggregatedResolver{q: fs})
   759  	}
   760  
   761  	return &fsar, nil
   762  }
   763  
   764  func (r *DiaResolver) GetVWALP(ctx context.Context, args struct {
   765  	Quotetokenblockchain graphql.NullString
   766  	Quotetokenaddress    graphql.NullString
   767  	BaseAssets           *[]Asset
   768  	Exchanges            *[]graphql.NullString
   769  	BlockDurationSeconds graphql.NullInt
   770  	EndTime              graphql.NullTime
   771  	BasisPoints          graphql.NullInt
   772  }) (*VWALPResolver, error) {
   773  
   774  	// --- Parse input data ---
   775  	var vr *VWALPResolver
   776  	if containsSpecialChars(*args.Quotetokenaddress.Value) || containsSpecialChars(*args.Quotetokenblockchain.Value) {
   777  		return nil, errInvalidInputParams
   778  	}
   779  
   780  	quoteAsset, err := r.RelDB.GetAsset(*args.Quotetokenaddress.Value, *args.Quotetokenblockchain.Value)
   781  	if err != nil {
   782  		log.Error("GetAsset: ", err)
   783  	}
   784  
   785  	var baseAssets []dia.Asset
   786  	if len(*args.BaseAssets) > 0 {
   787  		for i := range *args.BaseAssets {
   788  			if containsSpecialChars(*(*args.BaseAssets)[i].Address.Value) || containsSpecialChars(*(*args.BaseAssets)[i].BlockChain.Value) {
   789  				continue
   790  			}
   791  			baseAssets = append(baseAssets, dia.Asset{Address: *(*args.BaseAssets)[i].Address.Value, Blockchain: *(*args.BaseAssets)[i].BlockChain.Value})
   792  		}
   793  	}
   794  
   795  	var exchanges []string
   796  	if len(*args.Exchanges) > 0 {
   797  		for i := range *args.Exchanges {
   798  			if containsSpecialChars(*(*args.Exchanges)[i].Value) {
   799  				continue
   800  			}
   801  			exchanges = append(exchanges, *(*args.Exchanges)[i].Value)
   802  		}
   803  	}
   804  
   805  	BlockDurationSeconds := *args.BlockDurationSeconds.Value
   806  	basisPoints := *args.BasisPoints.Value
   807  	endtime := time.Now()
   808  	if args.EndTime.Set {
   809  		endtime = args.EndTime.Value.Time
   810  	}
   811  	//  -----------------------
   812  
   813  	// Fetch trades from Influx.
   814  	trades, err := r.DS.GetTradesByExchangesAndBaseAssets(
   815  		quoteAsset,
   816  		baseAssets,
   817  		exchanges,
   818  		endtime.Add(-time.Duration(BlockDurationSeconds)*time.Second),
   819  		endtime,
   820  		0,
   821  	)
   822  	if err != nil {
   823  		return vr, err
   824  	}
   825  
   826  	tradesByExchange := make(map[string][]dia.Trade)
   827  	for _, trade := range trades {
   828  		tradesByExchange[trade.Source] = append(tradesByExchange[trade.Source], trade)
   829  	}
   830  
   831  	// Get last trades and volumes.
   832  	var lastPrices []float64
   833  	var volumes []float64
   834  	for exchange := range tradesByExchange {
   835  		block := queryhelper.Block{Trades: tradesByExchange[exchange], TimeStamp: endtime.UnixNano()}
   836  		filterPoints, _ := queryhelper.FilterVOL([]queryhelper.Block{block}, quoteAsset, int(BlockDurationSeconds))
   837  		if len(filterPoints) > 0 {
   838  			lastPrices = append(lastPrices, filterPoints[0].LastTrade.EstimatedUSDPrice)
   839  			volumes = append(volumes, filterPoints[0].Value)
   840  		}
   841  	}
   842  
   843  	// Outlier detection.
   844  	prices, volumes, _, err := utils.DiscardOutliers(lastPrices, volumes, float64(basisPoints))
   845  	if err != nil {
   846  		log.Error("DiscardOutliers: ", err)
   847  	}
   848  
   849  	// Build vwap.
   850  	var vwap float64
   851  	var volTotal float64
   852  	for i := range prices {
   853  		vwap += prices[i] * volumes[i]
   854  		volTotal += volumes[i]
   855  	}
   856  	if volTotal > 0 {
   857  		vwap /= volTotal
   858  	}
   859  
   860  	var response vwalp
   861  	response.Symbol = quoteAsset.Symbol
   862  	response.Value = vwap
   863  	response.Time = endtime
   864  
   865  	return &VWALPResolver{q: response}, nil
   866  }
   867  
   868  func (r *DiaResolver) fillFirstBin(assets []dia.Asset, exchanges []string, trades []dia.Trade, starttime time.Time, endtime time.Time) []dia.Trade {
   869  	previousTrade, quoteAssetsErr := r.DS.GetxcTradesByExchangesBatched(assets, exchanges, []time.Time{starttime}, []time.Time{endtime})
   870  	if quoteAssetsErr != nil || len(previousTrade) == 0 {
   871  		log.Error("get initial trade: ", quoteAssetsErr)
   872  		// Fill with a zero trade so we can build blocks.
   873  		auxTrade := trades[0]
   874  		auxTrade.Volume = 0
   875  		auxTrade.Price = 0
   876  		auxTrade.EstimatedUSDPrice = 0
   877  		trades = append([]dia.Trade{auxTrade}, trades...)
   878  	} else {
   879  		trades = append([]dia.Trade{previousTrade[len(previousTrade)-1]}, trades...)
   880  		log.Warn("previous trade: ", previousTrade[len(previousTrade)-1])
   881  	}
   882  	return trades
   883  }
   884  
   885  // castLocalFeedSelection casts the input selection into a @[]dia.FeedSelection type
   886  // so that we can call the corresponding influx trades getter.
   887  // It also checks for admissibility of the underlying input data.
   888  func (r *DiaResolver) castLocalFeedSelection(fs []FeedSelection) ([]dia.FeedSelection, string, int32, error) {
   889  	var dfs []dia.FeedSelection
   890  	var warning string
   891  	var warnings []string
   892  	for _, localFeedSelection := range fs {
   893  		var diaFeedSelection dia.FeedSelection
   894  
   895  		// Parse asset.
   896  		if localFeedSelection.Address.Value == nil || localFeedSelection.Blockchain.Value == nil {
   897  			err := errors.New("missing asset's address and blockchain")
   898  			return dfs, warning, 1, err
   899  		}
   900  		diaFeedSelection.Asset = dia.Asset{
   901  			Address:    normalizeAddress(*localFeedSelection.Address.Value, *localFeedSelection.Blockchain.Value),
   902  			Blockchain: *localFeedSelection.Blockchain.Value,
   903  		}
   904  
   905  		// Parse liquidity threshold.
   906  		if localFeedSelection.LiquidityThreshold.Value != nil {
   907  			diaFeedSelection.LiquidityThreshold = *localFeedSelection.LiquidityThreshold.Value
   908  		}
   909  
   910  		// Check for exchange input. Continue if neither of exchange and liquidty threshold are set.
   911  		if localFeedSelection.Exchangepairs == nil && diaFeedSelection.LiquidityThreshold == 0 {
   912  			dfs = append(dfs, diaFeedSelection)
   913  			continue
   914  		}
   915  
   916  		// Fetch admissible pools in case liquidity threshold is set.
   917  		if diaFeedSelection.LiquidityThreshold > 0 {
   918  			pools, errPools := r.RelDB.GetPoolsByAsset(diaFeedSelection.Asset, 0, diaFeedSelection.LiquidityThreshold)
   919  			if errPools != nil {
   920  				return dfs, warning, 1, errPools
   921  			}
   922  			for _, pool := range pools {
   923  
   924  				// In order to append pool to corresponding entry of diaFeedSelection first check
   925  				// if the exchange was already added.
   926  				var poolAdded bool
   927  				for i := range diaFeedSelection.Exchangepairs {
   928  					if diaFeedSelection.Exchangepairs[i].Exchange == pool.Exchange {
   929  						diaFeedSelection.Exchangepairs[i].Pools = append(diaFeedSelection.Exchangepairs[i].Pools, pool)
   930  						poolAdded = true
   931  						break
   932  					}
   933  				}
   934  				if !poolAdded {
   935  					var eps dia.ExchangepairSelection
   936  					eps.Exchange = pool.Exchange
   937  					eps.Pools = append(eps.Pools, pool)
   938  					diaFeedSelection.Exchangepairs = append(diaFeedSelection.Exchangepairs, eps)
   939  				}
   940  
   941  			}
   942  			if len(pools) > 0 {
   943  				dfs = append(dfs, diaFeedSelection)
   944  			}
   945  			// Continue, i.e. ignore (possibly) given exchangepairs.
   946  			continue
   947  		}
   948  
   949  		// Parse exchanges.
   950  		for _, ep := range *localFeedSelection.Exchangepairs {
   951  			var diaExchangepairselection dia.ExchangepairSelection
   952  			if ep.Exchange.Value == nil {
   953  				err := errors.New("missing exchange name")
   954  				return dfs, warning, statusCodesMap[statusError], err
   955  			}
   956  
   957  			// Check if exchange is valid.
   958  			var exchangeOk bool
   959  			var exchange dia.Exchange
   960  			for e := range EXCHANGES {
   961  				if strings.EqualFold(*ep.Exchange.Value, e) {
   962  					exchangeOk = true
   963  					exchange = EXCHANGES[e]
   964  					break
   965  				}
   966  			}
   967  			if !exchangeOk && *ep.Exchange.Value != "" {
   968  				warnings = append(warnings, fmt.Sprintf("exchange name not valid: %s. ", *ep.Exchange.Value))
   969  				continue
   970  			}
   971  			diaExchangepairselection.Exchange = exchange
   972  
   973  			// Select all pairs on given exchange if not specified.
   974  			if ep.Pairs == nil {
   975  				// Check if asset is traded as quotetoken on exchange and emit warning if not.
   976  				eps, err := r.RelDB.GetExchangepairsByAsset(
   977  					dia.Asset{
   978  						Address:    diaFeedSelection.Asset.Address,
   979  						Blockchain: diaFeedSelection.Asset.Blockchain,
   980  					},
   981  					exchange.Name,
   982  					false,
   983  				)
   984  				log.Infof("Number of pairs on exchange %s: %v", exchange.Name, len(eps))
   985  				for _, e := range eps {
   986  					log.Info("ForeignName: ", e.ForeignName)
   987  				}
   988  				if err != nil {
   989  					log.Errorf("GetExchangepairsByAsset for %s on %s: %v", diaFeedSelection.Asset.Address, diaFeedSelection.Asset.Blockchain, err)
   990  				}
   991  				if len(eps) == 0 {
   992  					warnings = append(warnings, fmt.Sprintf("%s-%s: asset not available on %s. ", diaFeedSelection.Asset.Blockchain, diaFeedSelection.Asset.Address, exchange.Name))
   993  				}
   994  
   995  				diaFeedSelection.Exchangepairs = append(diaFeedSelection.Exchangepairs, diaExchangepairselection)
   996  				continue
   997  			}
   998  
   999  			// Check whether pairs or pools are given.
  1000  			var pairs bool
  1001  			var pairsCount int
  1002  			for _, p := range *ep.Pairs {
  1003  				if p.Value == nil {
  1004  					return dfs, warning, statusCodesMap[statusError], errors.New("missing pair identifier")
  1005  				}
  1006  				if len(strings.Split(*p.Value, PAIR_SEPARATOR)) == 2 {
  1007  					pairs = true
  1008  					pairsCount++
  1009  				}
  1010  			}
  1011  			if !(pairsCount == len(*ep.Pairs) || pairsCount == 0) {
  1012  				return dfs, warning, statusCodesMap[statusError], errors.New("pair/pool notation not consistent")
  1013  			}
  1014  
  1015  			if exchange.Name != "" && exchange.Centralized {
  1016  				if !pairs {
  1017  					return dfs, warning, statusCodesMap[statusError], errors.New("wrong notation for pairs")
  1018  				}
  1019  				for _, p := range *ep.Pairs {
  1020  					if len(strings.Split(*p.Value, PAIR_SEPARATOR)) < 2 {
  1021  						return dfs, warning, statusCodesMap[statusError], errors.New("pair not in requested format TokenA-TokenB")
  1022  					}
  1023  
  1024  					quoteAsset, errQuote := r.GetAssetByExchangesymbol(exchange.Name, strings.Split(*p.Value, PAIR_SEPARATOR)[0])
  1025  					baseAsset, errBase := r.GetAssetByExchangesymbol(exchange.Name, strings.Split(*p.Value, PAIR_SEPARATOR)[1])
  1026  					if errQuote != nil || errBase != nil {
  1027  						warnings = append(warnings, fmt.Sprintf("%s: pair not available on %s. ", *p.Value, exchange.Name))
  1028  					}
  1029  					pair := dia.Pair{
  1030  						QuoteToken: quoteAsset,
  1031  						BaseToken:  baseAsset,
  1032  					}
  1033  					diaExchangepairselection.Pairs = append(diaExchangepairselection.Pairs, pair)
  1034  				}
  1035  			} else if exchange.Name == "" {
  1036  				diaExchangepairselection.Exchange.Centralized = true
  1037  				for _, p := range *ep.Pairs {
  1038  
  1039  					// Check admissibility of input.
  1040  					pairString := strings.Split(*p.Value, LIST_SEPARATOR)
  1041  					if len(pairString) < 2 {
  1042  						return dfs, warning, statusCodesMap[statusError], errors.New("exactly 2 assets must be given for each pair")
  1043  					}
  1044  					var pair dia.Pair
  1045  
  1046  					if len(strings.Split(pairString[0], PAIR_SEPARATOR)) < 2 {
  1047  						return dfs, warning, statusCodesMap[statusError], errors.New("asset not in requested format Blockchain-Address")
  1048  					}
  1049  					blockchainQuotetoken := strings.Title(strings.ToLower(strings.Split(pairString[0], PAIR_SEPARATOR)[0]))
  1050  					pair.QuoteToken.Address = normalizeAddress(strings.Split(pairString[0], PAIR_SEPARATOR)[1], blockchainQuotetoken)
  1051  					pair.QuoteToken.Blockchain = blockchainQuotetoken
  1052  
  1053  					blockchainBasetoken := strings.Title(strings.ToLower(strings.Split(pairString[1], PAIR_SEPARATOR)[0]))
  1054  					pair.BaseToken.Address = normalizeAddress(strings.Split(pairString[1], PAIR_SEPARATOR)[1], blockchainBasetoken)
  1055  					pair.BaseToken.Blockchain = blockchainBasetoken
  1056  
  1057  					diaExchangepairselection.Pairs = append(diaExchangepairselection.Pairs, pair)
  1058  				}
  1059  			} else {
  1060  				for _, p := range *ep.Pairs {
  1061  					pool, err := r.RelDB.GetPoolByAddress(
  1062  						diaFeedSelection.Asset.Blockchain,
  1063  						normalizeAddress(*p.Value, diaFeedSelection.Asset.Blockchain),
  1064  					)
  1065  					if err != nil {
  1066  						return dfs, warning, statusCodesMap[statusError], err
  1067  					}
  1068  					diaExchangepairselection.Pools = append(diaExchangepairselection.Pools, pool)
  1069  				}
  1070  			}
  1071  			diaFeedSelection.Exchangepairs = append(diaFeedSelection.Exchangepairs, diaExchangepairselection)
  1072  		}
  1073  
  1074  		dfs = append(dfs, diaFeedSelection)
  1075  	}
  1076  	uniqueWarnings := ""
  1077  	for _, w := range utils.UniqueStrings(warnings) {
  1078  		uniqueWarnings += w
  1079  	}
  1080  	return dfs, uniqueWarnings, statusCodes(uniqueWarnings), nil
  1081  }
  1082  
  1083  // GetAssetByExchangesymbol is a poor man's cache that returns the underlying asset given
  1084  // a @symbol on @exchange.
  1085  func (r *DiaResolver) GetAssetByExchangesymbol(exchange string, symbol string) (dia.Asset, error) {
  1086  	if asset, ok := EXCHANGESYMBOL_CACHE[exchangesymbolIdentifier(exchange, symbol)]; ok {
  1087  		return asset, nil
  1088  	}
  1089  	asset, err := r.RelDB.GetExchangeSymbol(exchange, symbol)
  1090  	if err != nil {
  1091  		return dia.Asset{}, err
  1092  	}
  1093  	EXCHANGESYMBOL_CACHE[exchangesymbolIdentifier(exchange, symbol)] = asset
  1094  	return asset, nil
  1095  }
  1096  
  1097  // Returns the EIP55 compliant address in case @blockchain has an Ethereum ChainID.
  1098  func makeAddressEIP55Compliant(address string, blockchain string) string {
  1099  	if strings.Contains(BLOCKCHAINS[blockchain].ChainID, "Ethereum") {
  1100  		return common.HexToAddress(address).Hex()
  1101  	}
  1102  	return address
  1103  }
  1104  
  1105  // Normalize address depending on the blockchain.
  1106  func normalizeAddress(address string, blockchain string) string {
  1107  	if strings.Contains(BLOCKCHAINS[blockchain].ChainID, "Ethereum") {
  1108  		return makeAddressEIP55Compliant(address, blockchain)
  1109  	}
  1110  	if BLOCKCHAINS[blockchain].Name == dia.OSMOSIS {
  1111  		if strings.Contains(address, "ibc-") && len(strings.Split(address, "-")[1]) > 1 {
  1112  			return "ibc/" + strings.Split(address, "-")[1]
  1113  		}
  1114  	}
  1115  	return address
  1116  }
  1117  
  1118  func containsSpecialChars(s string) bool {
  1119  	return strings.ContainsAny(s, "!@#$%^&*()'\"|{}[];><?/`~,")
  1120  }
  1121  
  1122  func statusCodes(statusMessage string) int32 {
  1123  	var statusCode int32
  1124  	if strings.Contains(statusMessage, statusError) {
  1125  		return statusCodesMap[statusError]
  1126  	}
  1127  	for status := range statusCodesMap {
  1128  		if strings.Contains(statusMessage, status) {
  1129  			statusCode += statusCodesMap[status]
  1130  		}
  1131  	}
  1132  	return statusCode
  1133  }
  1134  
  1135  func exchangesymbolIdentifier(exchange string, symbol string) string {
  1136  	return exchange + "_" + symbol
  1137  }