github.com/diadata-org/diadata@v1.4.593/pkg/model/assets.go (about)

     1  package models
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  	"fmt"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/diadata-org/diadata/pkg/dia"
    13  	"github.com/go-redis/redis"
    14  	"github.com/jackc/pgtype"
    15  	"github.com/jackc/pgx/v4"
    16  )
    17  
    18  // GetKeyAsset returns an asset's key in the redis cache of the asset table.
    19  // @assetID refers to the primary key asset_id in the asset table.
    20  func (rdb *RelDB) GetKeyAsset(asset dia.Asset) (string, error) {
    21  	ID, err := rdb.GetAssetID(asset)
    22  	if err != nil {
    23  		return "", err
    24  	}
    25  	return keyAssetCache + ID, nil
    26  }
    27  
    28  // -------------------------------------------------------------
    29  // Postgres methods
    30  // -------------------------------------------------------------
    31  
    32  // 		-------------------------------------------------------------
    33  // 		asset TABLE methods
    34  // 		-------------------------------------------------------------
    35  
    36  // SetAsset stores an asset into postgres.
    37  func (rdb *RelDB) SetAsset(asset dia.Asset) error {
    38  	query := fmt.Sprintf("INSERT INTO %s (symbol,name,address,decimals,blockchain) VALUES ($1,$2,$3,$4,$5) ON CONFLICT (address,blockchain) DO NOTHING", assetTable)
    39  	_, err := rdb.postgresClient.Exec(context.Background(), query, asset.Symbol, asset.Name, asset.Address, strconv.Itoa(int(asset.Decimals)), asset.Blockchain)
    40  	if err != nil {
    41  		return err
    42  	}
    43  	return nil
    44  }
    45  
    46  // GetAssetID returns the unique identifier of @asset in postgres table asset, if the entry exists.
    47  func (rdb *RelDB) GetAssetID(asset dia.Asset) (ID string, err error) {
    48  	query := fmt.Sprintf("SELECT asset_id FROM %s WHERE address=$1 AND blockchain=$2", assetTable)
    49  	err = rdb.postgresClient.QueryRow(context.Background(), query, asset.Address, asset.Blockchain).Scan(&ID)
    50  	if err != nil {
    51  		return
    52  	}
    53  	return
    54  }
    55  
    56  func (rdb *RelDB) GetAssetMap(asset_id string) (ID string, err error) {
    57  	query := fmt.Sprintf("SELECT group_id FROM %s WHERE asset_id=$1", assetIdent)
    58  	err = rdb.postgresClient.QueryRow(context.Background(), query, asset_id).Scan(&ID)
    59  	if err != nil {
    60  		return
    61  	}
    62  	return
    63  }
    64  
    65  func (rdb *RelDB) GetAssetByGroupID(group_id string) (assets []dia.Asset, err error) {
    66  	var (
    67  		rows     pgx.Rows
    68  		decimals sql.NullInt64
    69  	)
    70  
    71  	query := fmt.Sprintf("SELECT symbol,name,address,blockchain,decimals FROM %s WHERE asset_id in (select asset_id from %s where group_id=$1)", assetTable, assetIdent)
    72  
    73  	rows, err = rdb.postgresClient.Query(context.Background(), query, group_id)
    74  	if err != nil {
    75  		return
    76  	}
    77  	defer rows.Close()
    78  
    79  	for rows.Next() {
    80  		var asset dia.Asset
    81  		err := rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &asset.Blockchain, &decimals)
    82  		if err != nil {
    83  			log.Error(err)
    84  		}
    85  		if decimals.Valid {
    86  			asset.Decimals = uint8(decimals.Int64)
    87  		}
    88  		// asset.Blockchain = blockchain
    89  		assets = append(assets, asset)
    90  	}
    91  	return
    92  }
    93  
    94  // SetAsset stores an asset into postgres.
    95  func (rdb *RelDB) InsertAssetMap(group_id string, asset_id string) error {
    96  	query := fmt.Sprintf("INSERT INTO %s (group_id,asset_id) VALUES ($1,$2)", assetIdent)
    97  	log.Println("query", query)
    98  
    99  	_, err := rdb.postgresClient.Exec(context.Background(), query, group_id, asset_id)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	return nil
   104  }
   105  func (rdb *RelDB) InsertNewAssetMap(asset_id string) error {
   106  	query := fmt.Sprintf("INSERT INTO %s (asset_id) VALUES ($1)", assetIdent)
   107  	log.Println("query", query)
   108  	_, err := rdb.postgresClient.Exec(context.Background(), query, asset_id)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	return nil
   113  }
   114  
   115  // GetAsset is the standard method in order to uniquely retrieve an asset from asset table.
   116  func (rdb *RelDB) GetAsset(address, blockchain string) (asset dia.Asset, err error) {
   117  	cachedAsset, errCache := rdb.GetAssetCache(blockchain, address)
   118  	if errCache == nil {
   119  		asset = cachedAsset
   120  		return
   121  	}
   122  	var decimals sql.NullInt64
   123  	query := fmt.Sprintf("SELECT symbol,name,address,decimals,blockchain FROM %s WHERE address=$1 AND blockchain=$2", assetTable)
   124  	err = rdb.postgresClient.QueryRow(context.Background(), query, address, blockchain).Scan(
   125  		&asset.Symbol,
   126  		&asset.Name,
   127  		&asset.Address,
   128  		&decimals,
   129  		&asset.Blockchain,
   130  	)
   131  	if err != nil {
   132  		return
   133  	}
   134  	if decimals.Valid {
   135  		asset.Decimals = uint8(decimals.Int64)
   136  	}
   137  
   138  	return
   139  }
   140  
   141  // GetAssetByID returns an asset by its uuid
   142  func (rdb *RelDB) GetAssetByID(assetID string) (asset dia.Asset, err error) {
   143  	var decimals sql.NullInt64
   144  	query := fmt.Sprintf("SELECT symbol,name,address,decimals,blockchain FROM %s WHERE asset_id=$1", assetTable)
   145  	err = rdb.postgresClient.QueryRow(context.Background(), query, assetID).Scan(&asset.Symbol, &asset.Name, &asset.Address, &decimals, &asset.Blockchain)
   146  	if err != nil {
   147  		return
   148  	}
   149  	if decimals.Valid {
   150  		asset.Decimals = uint8(decimals.Int64)
   151  	}
   152  	return
   153  }
   154  
   155  // GetAllAssets returns all assets on @blockchain from asset table.
   156  func (rdb *RelDB) GetAllAssets(blockchain string) (assets []dia.Asset, err error) {
   157  	var rows pgx.Rows
   158  	query := fmt.Sprintf("SELECT symbol,name,address,decimals FROM %s WHERE blockchain=$1", assetTable)
   159  	rows, err = rdb.postgresClient.Query(context.Background(), query, blockchain)
   160  	if err != nil {
   161  		return
   162  	}
   163  	defer rows.Close()
   164  
   165  	var decimals sql.NullInt64
   166  	for rows.Next() {
   167  		var asset dia.Asset
   168  		err := rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &decimals)
   169  		if err != nil {
   170  			log.Error(err)
   171  		}
   172  		if decimals.Valid {
   173  			asset.Decimals = uint8(decimals.Int64)
   174  		}
   175  		asset.Blockchain = blockchain
   176  		assets = append(assets, asset)
   177  	}
   178  	return
   179  }
   180  
   181  // GetAssetsBySymbolName returns a (possibly multiple) dia.Asset by its symbol and name from postgres.
   182  // If @name is an empty string, it returns all assets with @symbol.
   183  // If @symbol is an empty string, it returns all assets with @name.
   184  func (rdb *RelDB) GetAssetsBySymbolName(symbol, name string) (assets []dia.Asset, err error) {
   185  	var (
   186  		decimals sql.NullInt64
   187  		rows     pgx.Rows
   188  		query    string
   189  	)
   190  	if name == "" {
   191  		query = fmt.Sprintf(`
   192  		SELECT symbol,name,address,decimals,blockchain 
   193  		FROM %s a
   194  		INNER JOIN %s av
   195  		ON av.asset_id=a.asset_id
   196  		WHERE av.volume>0
   197  		AND a.blockchain!='Osmosis'
   198  		AND av.time_stamp IS NOT NULL
   199  		AND ( symbol ILIKE $1 || '%%' )
   200  		ORDER BY av.volume DESC`,
   201  			assetTable,
   202  			assetVolumeTable,
   203  		)
   204  		rows, err = rdb.postgresClient.Query(context.Background(), query, symbol)
   205  	} else if symbol == "" {
   206  		query = fmt.Sprintf(`
   207  		SELECT symbol,name,address,decimals,blockchain 
   208  		FROM %s a
   209  		INNER JOIN %s av
   210  		ON av.asset_id=a.asset_id
   211  		WHERE av.volume>0
   212  		AND a.blockchain!='Osmosis'
   213  		AND av.time_stamp IS NOT NULL
   214  		AND ( name ILIKE $1 || '%%' )
   215  		ORDER BY av.volume DESC`,
   216  			assetTable,
   217  			assetVolumeTable,
   218  		)
   219  		rows, err = rdb.postgresClient.Query(context.Background(), query, name)
   220  	} else {
   221  		query = fmt.Sprintf(`
   222  		SELECT symbol,name,address,decimals,blockchain 
   223  		FROM %s a 
   224  		INNER JOIN %s av 
   225  		ON av.asset_id=a.asset_id 
   226  		WHERE av.volume>0
   227  		AND a.blockchain!='Osmosis'
   228  		AND av.time_stamp IS NOT NULL
   229  		AND ( (symbol ILIKE $1 || '%%') OR ( name ILIKE $2 || '%%') )
   230  		ORDER BY av.volume DESC`,
   231  			assetTable,
   232  			assetVolumeTable,
   233  		)
   234  		rows, err = rdb.postgresClient.Query(context.Background(), query, name, symbol)
   235  	}
   236  	if err != nil {
   237  		return
   238  	}
   239  
   240  	defer rows.Close()
   241  	for rows.Next() {
   242  		var asset dia.Asset
   243  		err = rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &decimals, &asset.Blockchain)
   244  		if err != nil {
   245  			return
   246  		}
   247  		if decimals.Valid {
   248  			asset.Decimals = uint8(decimals.Int64)
   249  		}
   250  		assets = append(assets, asset)
   251  	}
   252  	return
   253  }
   254  
   255  // GetAssetsByAddress returns a (possibly multiple) dia.Asset by its address from postgres.
   256  func (rdb *RelDB) GetAssetsByAddress(address string) (assets []dia.Asset, err error) {
   257  	var (
   258  		decimals sql.NullInt64
   259  		rows     pgx.Rows
   260  	)
   261  	query := fmt.Sprintf(`
   262  	SELECT symbol,name,address,decimals,blockchain 
   263  	FROM %s a 
   264  	INNER JOIN %s av 
   265  	ON a.asset_id=av.asset_id
   266  	WHERE av.volume>0
   267  	AND av.time_stamp IS NOT NULL
   268  	AND ( address ILIKE $1 || '%%')
   269  	ORDER BY av.volume DESC`,
   270  		assetTable,
   271  		assetVolumeTable,
   272  	)
   273  	rows, err = rdb.postgresClient.Query(context.Background(), query, address)
   274  	if err != nil {
   275  		return
   276  	}
   277  	defer rows.Close()
   278  	for rows.Next() {
   279  		var asset dia.Asset
   280  		err = rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &decimals, &asset.Blockchain)
   281  		if err != nil {
   282  			return
   283  		}
   284  		if decimals.Valid {
   285  			asset.Decimals = uint8(decimals.Int64)
   286  		}
   287  		assets = append(assets, asset)
   288  	}
   289  	return
   290  }
   291  
   292  // GetFiatAssetBySymbol returns a fiat asset by its symbol. This is possible as
   293  // fiat currencies are uniquely defined by their symbol.
   294  func (rdb *RelDB) GetFiatAssetBySymbol(symbol string) (asset dia.Asset, err error) {
   295  	var decimals sql.NullInt64
   296  	query := fmt.Sprintf("SELECT name,address,decimals FROM %s WHERE symbol=$1 AND blockchain='Fiat'", assetTable)
   297  	err = rdb.postgresClient.QueryRow(context.Background(), query, symbol).Scan(&asset.Name, &asset.Address, &decimals)
   298  	if err != nil {
   299  		return
   300  	}
   301  	if decimals.Valid {
   302  		asset.Decimals = uint8(decimals.Int64)
   303  	}
   304  	asset.Symbol = symbol
   305  	asset.Blockchain = "Fiat"
   306  	// TO DO: Get Blockchain by name from postgres and add to asset
   307  	return
   308  }
   309  
   310  // 		-------------------------------------------------------------
   311  // 		exchangesymbol TABLE methods
   312  // 		-------------------------------------------------------------
   313  
   314  // SetExchangeSymbol writes unique data into exchangesymbol table if not yet in there.
   315  func (rdb *RelDB) SetExchangeSymbol(exchange string, symbol string) error {
   316  	query := fmt.Sprintf(`
   317  	INSERT INTO %s (symbol,exchange) 
   318  	SELECT $1,$2 
   319  	WHERE NOT EXISTS 
   320  	(SELECT 1 FROM exchangesymbol WHERE symbol=$1 AND exchange=$2)
   321  	`, exchangesymbolTable)
   322  	_, err := rdb.postgresClient.Exec(context.Background(), query, symbol, exchange)
   323  	if err != nil {
   324  		return err
   325  	}
   326  	return nil
   327  }
   328  
   329  func (rdb *RelDB) GetExchangeSymbol(exchange string, symbol string) (asset dia.Asset, err error) {
   330  	var decimals sql.NullInt64
   331  	var secondExchangeQuery string
   332  	if exchange == dia.BinanceExchange {
   333  		secondExchangeQuery = fmt.Sprintf(" OR es.exchange='%s' OR es.exchange='%s'", dia.Binance2Exchange, dia.Binance3Exchange)
   334  	}
   335  	if exchange == dia.BKEXExchange {
   336  		secondExchangeQuery = fmt.Sprintf(" OR es.exchange='%s'", dia.BKEX2Exchange)
   337  	}
   338  	query := fmt.Sprintf(`
   339  		SELECT a.symbol,a.name,a.address,a.blockchain,a.decimals
   340  		FROM %s es
   341  		INNER JOIN %s a
   342  		ON es.asset_id=a.asset_id
   343  		WHERE (es.exchange=$1 %s)
   344  		AND a.symbol ILIKE $2
   345  		`,
   346  		exchangesymbolTable,
   347  		assetTable,
   348  		secondExchangeQuery,
   349  	)
   350  	err = rdb.postgresClient.QueryRow(context.Background(), query, exchange, symbol).Scan(
   351  		&asset.Symbol,
   352  		&asset.Name,
   353  		&asset.Address,
   354  		&asset.Blockchain,
   355  		&decimals,
   356  	)
   357  	if err != nil {
   358  		return
   359  	}
   360  	if decimals.Valid {
   361  		asset.Decimals = uint8(decimals.Int64)
   362  	}
   363  	return
   364  }
   365  
   366  // GetAssets returns all assets which share the symbol ticker @symbol.
   367  func (rdb *RelDB) GetAssets(symbol string) (assets []dia.Asset, err error) {
   368  	query := fmt.Sprintf("SELECT symbol,name,address,decimals,blockchain FROM %s WHERE symbol=$1 ", assetTable)
   369  	var rows pgx.Rows
   370  	rows, err = rdb.postgresClient.Query(context.Background(), query, symbol)
   371  	if err != nil {
   372  		return
   373  	}
   374  	defer rows.Close()
   375  
   376  	for rows.Next() {
   377  		var decimals sql.NullInt64
   378  		asset := dia.Asset{}
   379  		err = rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &decimals, &asset.Blockchain)
   380  		if err != nil {
   381  			return
   382  		}
   383  		if decimals.Valid {
   384  			asset.Decimals = uint8(decimals.Int64)
   385  		}
   386  		assets = append(assets, asset)
   387  	}
   388  	return
   389  }
   390  
   391  // GetAssetExchnage returns all assets which share the symbol ticker @symbol.
   392  func (rdb *RelDB) GetAssetExchange(symbol string) (exchanges []string, err error) {
   393  
   394  	query := fmt.Sprintf(`
   395  	SELECT exchange 
   396  	FROM %s 
   397  	INNER JOIN %s 
   398  	ON asset.asset_id = exchangesymbol.asset_id 
   399  	WHERE exchangesymbol.symbol = $1
   400  	`, exchangesymbolTable, assetTable)
   401  	var rows pgx.Rows
   402  	rows, err = rdb.postgresClient.Query(context.Background(), query, symbol)
   403  	if err != nil {
   404  		return
   405  	}
   406  	defer rows.Close()
   407  
   408  	for rows.Next() {
   409  		var exchange string
   410  
   411  		err = rows.Scan(&exchange)
   412  		if err != nil {
   413  			return
   414  		}
   415  		exchanges = append(exchanges, exchange)
   416  	}
   417  	return
   418  }
   419  
   420  // GetUnverifiedExchangeSymbols returns all symbols from @exchange which haven't been verified yet.
   421  func (rdb *RelDB) GetUnverifiedExchangeSymbols(exchange string) (symbols []string, err error) {
   422  	query := fmt.Sprintf("SELECT symbol FROM %s WHERE exchange=$1 AND verified=false ORDER BY symbol ASC", exchangesymbolTable)
   423  	var rows pgx.Rows
   424  	rows, err = rdb.postgresClient.Query(context.Background(), query, exchange)
   425  	if err != nil {
   426  		return
   427  	}
   428  	defer rows.Close()
   429  	for rows.Next() {
   430  		symbol := ""
   431  		err = rows.Scan(&symbol)
   432  		if err != nil {
   433  			return []string{}, err
   434  		}
   435  		symbols = append(symbols, symbol)
   436  	}
   437  	return
   438  }
   439  
   440  // GetExchangeSymbols returns all symbols traded on @exchange.
   441  // If @exchange is the empty string, all symbols are returned.
   442  // If @substring is not the empty string, all symbols that begin with @substring (case insensitive) are returned.
   443  func (rdb *RelDB) GetExchangeSymbols(exchange string, substring string) (symbols []string, err error) {
   444  	var query string
   445  	var rows pgx.Rows
   446  	if exchange != "" {
   447  		if substring != "" {
   448  			query = fmt.Sprintf("SELECT symbol FROM %s WHERE exchange=$1 AND ( symbol ILIKE $2 || '%%')", exchangesymbolTable)
   449  			rows, err = rdb.postgresClient.Query(context.Background(), query, exchange, substring)
   450  
   451  		} else {
   452  			query = fmt.Sprintf("SELECT symbol FROM %s WHERE exchange=$1", exchangesymbolTable)
   453  			rows, err = rdb.postgresClient.Query(context.Background(), query, exchange)
   454  		}
   455  	} else {
   456  		if substring != "" {
   457  			query = fmt.Sprintf("SELECT symbol FROM %s WHERE ( symbol ILIKE $1 || '%%')", exchangesymbolTable)
   458  			rows, err = rdb.postgresClient.Query(context.Background(), query, substring)
   459  		} else {
   460  			query = fmt.Sprintf("SELECT symbol FROM %s", exchangesymbolTable)
   461  			rows, err = rdb.postgresClient.Query(context.Background(), query)
   462  		}
   463  	}
   464  	if err != nil {
   465  		return
   466  	}
   467  	defer rows.Close()
   468  
   469  	for rows.Next() {
   470  		symbol := ""
   471  		err = rows.Scan(&symbol)
   472  		if err != nil {
   473  			return []string{}, err
   474  		}
   475  		symbols = append(symbols, symbol)
   476  	}
   477  	return
   478  }
   479  
   480  // VerifyExchangeSymbol verifies @symbol on @exchange and maps it uniquely to @assetID in asset table.
   481  // It returns true if symbol,exchange is present and succesfully updated.
   482  func (rdb *RelDB) VerifyExchangeSymbol(exchange string, symbol string, assetID string) (bool, error) {
   483  	query := fmt.Sprintf("UPDATE %s SET verified=true,asset_id=$1 WHERE symbol=$2 AND exchange=$3", exchangesymbolTable)
   484  	resp, err := rdb.postgresClient.Exec(context.Background(), query, assetID, symbol, exchange)
   485  	if err != nil {
   486  		return false, err
   487  	}
   488  	var success bool
   489  	respSlice := strings.Split(string(resp), " ")
   490  	numUpdates := respSlice[1]
   491  	if numUpdates != "0" {
   492  		success = true
   493  	}
   494  	return success, nil
   495  }
   496  
   497  // GetExchangeSymbolAssetID returns the ID of the unique asset associated to @symbol on @exchange
   498  // in case the symbol is verified. An empty string if not.
   499  func (rdb *RelDB) GetExchangeSymbolAssetID(exchange string, symbol string) (assetID string, verified bool, err error) {
   500  	var uuid pgtype.UUID
   501  	query := fmt.Sprintf("SELECT asset_id, verified FROM %s WHERE symbol=$1 AND exchange=$2", exchangesymbolTable)
   502  	err = rdb.postgresClient.QueryRow(context.Background(), query, symbol, exchange).Scan(&uuid, &verified)
   503  	if err != nil {
   504  		return
   505  	}
   506  	val, err := uuid.Value()
   507  	if err != nil {
   508  		log.Error(err)
   509  	}
   510  	if val != nil {
   511  		assetID = val.(string)
   512  	}
   513  	return
   514  }
   515  
   516  // -------------------------------------------------------------
   517  // Blockchain methods
   518  // -------------------------------------------------------------
   519  
   520  func (rdb *RelDB) SetBlockchain(blockchain dia.BlockChain) (err error) {
   521  	fields := fmt.Sprintf("INSERT INTO %s (name,genesisdate,nativetoken_id,verificationmechanism,chain_id) VALUES ", blockchainTable)
   522  	values := "($1,$2,(SELECT asset_id FROM asset WHERE address=$3 AND blockchain=$1),$4,NULLIF($5,'')) "
   523  	conflict := `
   524  	ON CONFLICT (name) 
   525  	DO UPDATE SET 
   526  	genesisdate=$2,verificationmechanism=$4,chain_id=NULLIF($5,''),nativetoken_id=(SELECT asset_id FROM asset WHERE address=$3 AND blockchain=$1)
   527  	`
   528  
   529  	query := fields + values + conflict
   530  	_, err = rdb.postgresClient.Exec(context.Background(), query,
   531  		blockchain.Name,
   532  		blockchain.GenesisDate,
   533  		blockchain.NativeToken.Address,
   534  		blockchain.VerificationMechanism,
   535  		blockchain.ChainID,
   536  	)
   537  	if err != nil {
   538  		return err
   539  	}
   540  	return nil
   541  }
   542  
   543  func (rdb *RelDB) GetBlockchain(name string) (blockchain dia.BlockChain, err error) {
   544  	query := fmt.Sprintf(`
   545  	SELECT genesisdate,verificationmechanism,chain_id,address,symbol 
   546  	FROM %s 
   547  	INNER JOIN %s 
   548  	ON %s.nativetoken_id=%s.asset_id 
   549  	WHERE %s.name=$1
   550  	`, blockchainTable, assetTable, blockchainTable, assetTable, blockchainTable)
   551  	err = rdb.postgresClient.QueryRow(context.Background(), query, name).Scan(
   552  		&blockchain.GenesisDate,
   553  		&blockchain.VerificationMechanism,
   554  		&blockchain.ChainID,
   555  		&blockchain.NativeToken.Address,
   556  		&blockchain.NativeToken.Symbol,
   557  	)
   558  	if err != nil {
   559  		return
   560  	}
   561  	blockchain.Name = name
   562  	return
   563  }
   564  
   565  // GetAllBlockchains returns all blockchains from the blockchain table.
   566  // If fullAsset=true it returns the complete native token as asset, otherwise only its symbol string.
   567  func (rdb *RelDB) GetAllBlockchains(fullAsset bool) ([]dia.BlockChain, error) {
   568  	var (
   569  		blockchains []dia.BlockChain
   570  		query       string
   571  	)
   572  
   573  	if fullAsset {
   574  		query = fmt.Sprintf(`
   575  		SELECT b.name,b.genesisdate,a.Symbol,a.Name,a.Address,a.Decimals,b.verificationmechanism,b.chain_id 
   576  		FROM %s b 
   577  		LEFT JOIN %s a 
   578  		ON nativetoken_id = a.asset_id
   579  		`, blockchainTable, assetTable)
   580  	} else {
   581  		query = fmt.Sprintf(`
   582  		SELECT b.name,b.genesisdate,a.Symbol,b.verificationmechanism,b.chain_id 
   583  		FROM %s b 
   584  		LEFT JOIN %s a 
   585  		ON nativetoken_id = a.asset_id
   586  		`, blockchainTable, assetTable)
   587  	}
   588  
   589  	rows, err := rdb.postgresClient.Query(context.Background(), query)
   590  	if err != nil {
   591  		return []dia.BlockChain{}, err
   592  	}
   593  
   594  	defer rows.Close()
   595  	for rows.Next() {
   596  		var (
   597  			blockchain     dia.BlockChain
   598  			genDate        sql.NullFloat64
   599  			symbol         sql.NullString
   600  			verifMechanism sql.NullString
   601  			chainID        sql.NullString
   602  			//  fullAsset
   603  			name     sql.NullString
   604  			address  sql.NullString
   605  			decimals sql.NullInt64
   606  		)
   607  
   608  		if fullAsset {
   609  			err = rows.Scan(
   610  				&blockchain.Name,
   611  				&genDate,
   612  				&symbol,
   613  				&name,
   614  				&address,
   615  				&decimals,
   616  				&verifMechanism,
   617  				&chainID,
   618  			)
   619  		} else {
   620  			err = rows.Scan(
   621  				&blockchain.Name,
   622  				&genDate,
   623  				&symbol,
   624  				&verifMechanism,
   625  				&chainID,
   626  			)
   627  		}
   628  		if err != nil {
   629  			return []dia.BlockChain{}, err
   630  		}
   631  		if genDate.Valid {
   632  			blockchain.GenesisDate = int64(genDate.Float64)
   633  		}
   634  		if symbol.Valid {
   635  			blockchain.NativeToken.Symbol = symbol.String
   636  		}
   637  		if verifMechanism.Valid {
   638  			blockchain.VerificationMechanism = dia.VerificationMechanism(verifMechanism.String)
   639  		}
   640  		if chainID.Valid {
   641  			blockchain.ChainID = chainID.String
   642  		}
   643  		if fullAsset {
   644  			if name.Valid {
   645  				blockchain.NativeToken.Name = name.String
   646  			}
   647  			if address.Valid {
   648  				blockchain.NativeToken.Address = address.String
   649  			}
   650  			if decimals.Valid {
   651  				blockchain.NativeToken.Decimals = uint8(decimals.Int64)
   652  			}
   653  			blockchain.NativeToken.Blockchain = blockchain.Name
   654  		}
   655  		blockchains = append(blockchains, blockchain)
   656  	}
   657  
   658  	return blockchains, nil
   659  }
   660  
   661  // GetAllAssetsBlockchains returns all blockchain names existent in the asset table.
   662  func (rdb *RelDB) GetAllAssetsBlockchains() ([]string, error) {
   663  	var blockchains []string
   664  	query := fmt.Sprintf("SELECT DISTINCT blockchain FROM %s WHERE name!='' ORDER BY blockchain ASC", assetTable)
   665  	rows, err := rdb.postgresClient.Query(context.Background(), query)
   666  	if err != nil {
   667  		return []string{}, err
   668  	}
   669  	defer rows.Close()
   670  
   671  	for rows.Next() {
   672  		var blockchain string
   673  		err := rows.Scan(&blockchain)
   674  		if err != nil {
   675  			return []string{}, err
   676  		}
   677  		blockchains = append(blockchains, blockchain)
   678  	}
   679  
   680  	return blockchains, nil
   681  }
   682  
   683  // -------------------------------------------------------------
   684  // General methods
   685  // -------------------------------------------------------------
   686  
   687  // GetPage returns assets per page number. @hasNext is true iff there is a non-empty next page.
   688  func (rdb *RelDB) GetPage(pageNumber uint32) (assets []dia.Asset, hasNextPage bool, err error) {
   689  
   690  	pagesize := rdb.pagesize
   691  	skip := pagesize * pageNumber
   692  	rows, err := rdb.postgresClient.Query(context.Background(), "SELECT symbol,name,address,decimals,blockchain FROM asset LIMIT $1 OFFSET $2 ", pagesize, skip)
   693  	if err != nil {
   694  		return
   695  	}
   696  	defer rows.Close()
   697  
   698  	for rows.Next() {
   699  		fmt.Println("---")
   700  		var asset dia.Asset
   701  		err = rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &asset.Decimals, &asset.Blockchain)
   702  		if err != nil {
   703  			return
   704  		}
   705  		assets = append(assets, asset)
   706  	}
   707  	// Last page (or empty page)
   708  	if len(rows.RawValues()) < int(pagesize) {
   709  		hasNextPage = false
   710  		return
   711  	}
   712  	// No next page
   713  	nextPageRows, err := rdb.postgresClient.Query(context.Background(), "SELECT symbol,name,address,decimals,blockchain FROM asset LIMIT $1 OFFSET $2 ", pagesize, skip+1)
   714  	if len(nextPageRows.RawValues()) == 0 {
   715  		hasNextPage = false
   716  		return
   717  	}
   718  	defer nextPageRows.Close()
   719  	hasNextPage = true
   720  	return
   721  }
   722  
   723  // Count returns the number of assets stored in postgres
   724  func (rdb *RelDB) Count() (count uint32, err error) {
   725  	err = rdb.postgresClient.QueryRow(context.Background(), "SELECT COUNT(*) FROM asset").Scan(&count)
   726  	if err != nil {
   727  		return
   728  	}
   729  	return
   730  }
   731  
   732  // -------------------------------------------------------------
   733  // Caching layer
   734  // -------------------------------------------------------------
   735  
   736  // SetAssetCache stores @asset in redis, using its primary key in postgres as key.
   737  // As a consequence, @asset is only cached iff it exists in postgres.
   738  func (rdb *RelDB) SetAssetCache(asset dia.Asset) error {
   739  	return rdb.redisClient.Set(keyAssetCache+asset.Identifier(), &asset, 0).Err()
   740  }
   741  
   742  // GetAssetCache returns an asset by its asset_id as defined in asset table in postgres
   743  func (rdb *RelDB) GetAssetCache(blockchain string, address string) (asset dia.Asset, err error) {
   744  	asset.Blockchain = blockchain
   745  	asset.Address = address
   746  	err = rdb.redisClient.Get(keyAssetCache + asset.Identifier()).Scan(&asset)
   747  	return
   748  }
   749  
   750  // CountCache returns the number of assets in the cache
   751  func (rdb *RelDB) CountCache() (uint32, error) {
   752  	keysPattern := keyAssetCache + "*"
   753  	allAssets := rdb.redisClient.Keys(keysPattern).Val()
   754  	return uint32(len(allAssets)), nil
   755  }
   756  
   757  // -------------- Caching exchange pairs -------------------
   758  
   759  // SetExchangePairCache stores @pairs in redis
   760  func (rdb *RelDB) SetExchangePairCache(exchange string, pair dia.ExchangePair) error {
   761  	key := keyExchangePairCache + exchange + "_" + pair.ForeignName
   762  	return rdb.redisClient.Set(key, &pair, 0).Err()
   763  }
   764  
   765  // GetExchangePairCache returns an exchange pair by @exchange and @foreigName
   766  func (rdb *RelDB) GetExchangePairCache(exchange string, foreignName string) (dia.ExchangePair, error) {
   767  	exchangePair := dia.ExchangePair{}
   768  	err := rdb.redisClient.Get(keyExchangePairCache + exchange + "_" + foreignName).Scan(&exchangePair)
   769  	if err != nil {
   770  		if !errors.Is(err, redis.Nil) {
   771  			log.Errorf("GetExchangePairCache on %s with foreign name %s: %v\n", exchange, foreignName, err)
   772  		}
   773  		return exchangePair, err
   774  	}
   775  	return exchangePair, nil
   776  }
   777  
   778  func (rdb *RelDB) SetAssetVolume24H(asset dia.Asset, volume float64, timestamp time.Time) error {
   779  
   780  	initialStr := fmt.Sprintf("INSERT INTO %s (asset_id,volume,time_stamp) VALUES ", assetVolumeTable)
   781  	substring := fmt.Sprintf(
   782  		"((SELECT asset_id FROM asset WHERE address='%s' AND blockchain='%s'),%f,to_timestamp(%v))",
   783  		asset.Address,
   784  		asset.Blockchain,
   785  		volume,
   786  		timestamp.Unix(),
   787  	)
   788  	conflict := " ON CONFLICT (asset_id) DO UPDATE SET volume=EXCLUDED.volume,time_stamp=EXCLUDED.time_stamp"
   789  
   790  	query := initialStr + substring + conflict
   791  	_, err := rdb.postgresClient.Exec(context.Background(), query)
   792  	if err != nil {
   793  		return err
   794  	}
   795  	return nil
   796  }
   797  
   798  func (rdb *RelDB) GetLastAssetVolume24H(asset dia.Asset) (volume float64, err error) {
   799  	query := fmt.Sprintf("SELECT volume FROM %s INNER JOIN %s ON assetvolume.asset_id = asset.asset_id WHERE address=$1 AND blockchain=$2", assetVolumeTable, assetTable)
   800  	err = rdb.postgresClient.QueryRow(context.Background(), query, asset.Address, asset.Blockchain).Scan(&volume)
   801  	return
   802  }
   803  
   804  func (rdb *RelDB) GetTopAssetByVolume(symbol string) (assets []dia.Asset, err error) {
   805  	query := fmt.Sprintf(`
   806  	SELECT symbol,name,address,decimals,blockchain 
   807  	FROM %s 
   808  	INNER JOIN %s 
   809  	ON asset.asset_id = assetvolume.asset_id 
   810  	WHERE symbol=$1 
   811  	ORDER BY volume DESC
   812  	`, assetTable, assetVolumeTable)
   813  
   814  	var rows pgx.Rows
   815  	rows, err = rdb.postgresClient.Query(context.Background(), query, symbol)
   816  	if err != nil {
   817  		return
   818  	}
   819  	defer rows.Close()
   820  
   821  	for rows.Next() {
   822  		var decimals sql.NullInt64
   823  		asset := dia.Asset{}
   824  		err = rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &decimals, &asset.Blockchain)
   825  		if err != nil {
   826  			return
   827  		}
   828  		if decimals.Valid {
   829  			asset.Decimals = uint8(decimals.Int64)
   830  		}
   831  		assets = append(assets, asset)
   832  	}
   833  	return
   834  }
   835  
   836  func (rdb *RelDB) GetByLimit(limit, skip uint32) (assets []dia.Asset, assetIds []string, err error) {
   837  
   838  	rows, err := rdb.postgresClient.Query(
   839  		context.Background(),
   840  		"SELECT asset_id,symbol,name,address,decimals,blockchain FROM asset LIMIT $1 OFFSET $2",
   841  		limit,
   842  		skip,
   843  	)
   844  	if err != nil {
   845  		return
   846  	}
   847  	defer rows.Close()
   848  
   849  	for rows.Next() {
   850  
   851  		var (
   852  			decimals sql.NullInt64
   853  			assetID  string
   854  			asset    dia.Asset
   855  		)
   856  		err = rows.Scan(&assetID, &asset.Symbol, &asset.Name, &asset.Address, &decimals, &asset.Blockchain)
   857  		if err != nil {
   858  			return
   859  		}
   860  		if decimals.Valid {
   861  			asset.Decimals = uint8(decimals.Int64)
   862  		}
   863  
   864  		assets = append(assets, asset)
   865  		assetIds = append(assetIds, assetID)
   866  	}
   867  
   868  	return
   869  }
   870  
   871  // GetAssetsWithVolByBlockchain returns all assets from assetvolume table that have a timestamp in the time-range (@starttime,@endtime].
   872  // If blockchain is a non-empty string it only returns assets from @blockchain.
   873  func (rdb *RelDB) GetAssetsWithVolByBlockchain(starttime time.Time, endtime time.Time, blockchain string) (assets []dia.AssetVolume, err error) {
   874  	var (
   875  		query string
   876  		rows  pgx.Rows
   877  	)
   878  
   879  	query = fmt.Sprintf(`
   880  	SELECT * FROM (
   881  		SELECT DISTINCT ON (address,blockchain) symbol,name,address,decimals,blockchain,volume
   882  		FROM %s 
   883  		INNER JOIN %s
   884  		ON (asset.asset_id = assetvolume.asset_id)
   885  		WHERE time_stamp>to_timestamp($1) and time_stamp<=to_timestamp($2)`,
   886  		assetTable,
   887  		assetVolumeTable,
   888  	)
   889  	if blockchain != "" {
   890  		query += " AND asset.blockchain=$3)"
   891  	} else {
   892  		query += (")")
   893  	}
   894  	query += " sub ORDER BY volume DESC"
   895  
   896  	if blockchain != "" {
   897  		rows, err = rdb.postgresClient.Query(context.Background(), query, starttime.Unix(), endtime.Unix(), blockchain)
   898  	} else {
   899  		rows, err = rdb.postgresClient.Query(context.Background(), query, starttime.Unix(), endtime.Unix())
   900  	}
   901  
   902  	if err != nil {
   903  		return
   904  	}
   905  	defer rows.Close()
   906  
   907  	for rows.Next() {
   908  		var (
   909  			decimals sql.NullInt64
   910  			volume   float64
   911  		)
   912  		asset := dia.Asset{}
   913  		err = rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &decimals, &asset.Blockchain, &volume)
   914  		if err != nil {
   915  			return
   916  		}
   917  		if decimals.Valid {
   918  			asset.Decimals = uint8(decimals.Int64)
   919  		}
   920  		assetvolume := dia.AssetVolume{Asset: asset, Volume: volume}
   921  		assets = append(assets, assetvolume)
   922  	}
   923  	return
   924  }
   925  
   926  // GetSortedAssetSymbols search asstet by symbol
   927  func (rdb *RelDB) GetSortedAssetSymbols(numAssets int64, skip int64, search string) (volumeSortedAssets []dia.AssetVolume, err error) {
   928  	var (
   929  		queryString string
   930  		query       string
   931  		rows        pgx.Rows
   932  	)
   933  
   934  	if numAssets == 0 {
   935  		queryString = `
   936  		SELECT a.symbol,a.name,a.address,a.decimals,a.blockchain,av.volume 
   937  		FROM %s a
   938  		INNER JOIN %s av
   939  		ON (a.asset_id = av.asset_id) 
   940  		WHERE ( a.symbol ILIKE $1 || '%%' )
   941  		ORDER BY av.volume 
   942  		DESC LIMIT 100`
   943  		query = fmt.Sprintf(queryString, assetTable, assetVolumeTable)
   944  		rows, err = rdb.postgresClient.Query(context.Background(), query, search)
   945  	} else {
   946  		queryString = `
   947  		SELECT DISTINCT ON (av.volume,av.asset_id)  a.symbol,a.name,a.address,a.decimals,a.blockchain,av.volume 
   948  		FROM %s a
   949  		INNER JOIN %s av 
   950  		ON a.asset_id=av.asset_id 
   951  		INNER JOIN %s es 
   952  		ON av.asset_id=es.asset_id 
   953  		INNER JOIN %s e 
   954  		ON es.exchange=e.name 
   955  		WHERE e.centralized=true 
   956  		AND (a.symbol ILIKE $1 || '%%' )
   957  		ORDER BY av.volume 
   958  		DESC LIMIT $2 
   959  		OFFSET $3`
   960  		query = fmt.Sprintf(queryString, assetTable, assetVolumeTable, exchangesymbolTable, exchangeTable)
   961  		rows, err = rdb.postgresClient.Query(context.Background(), query, search, numAssets, skip)
   962  	}
   963  	if err != nil {
   964  		return
   965  	}
   966  
   967  	defer rows.Close()
   968  
   969  	for rows.Next() {
   970  		var (
   971  			decimals sql.NullInt64
   972  			volume   float64
   973  		)
   974  		asset := dia.Asset{}
   975  		err = rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &decimals, &asset.Blockchain, &volume)
   976  		if err != nil {
   977  			return
   978  		}
   979  		if decimals.Valid {
   980  			asset.Decimals = uint8(decimals.Int64)
   981  		}
   982  		assetvolume := dia.AssetVolume{Asset: asset, Volume: volume}
   983  		volumeSortedAssets = append(volumeSortedAssets, assetvolume)
   984  	}
   985  	return
   986  
   987  }
   988  
   989  // GetAssetsWithVOL returns the first @numAssets assets with entry in the assetvolume table, sorted by volume in descending order.
   990  // If @numAssets==0, all assets are returned.
   991  // If @substring is not the empty string, results are filtered by the first letters being @substring.
   992  func (rdb *RelDB) GetAssetsWithVOL(starttime time.Time, numAssets int64, skip int64, onlycex bool, blockchain string) (volumeSortedAssets []dia.AssetVolume, err error) {
   993  	var (
   994  		queryString string
   995  		query       string
   996  		rows        pgx.Rows
   997  	)
   998  	if numAssets == 0 {
   999  		numAssets = 100
  1000  	}
  1001  
  1002  	if !onlycex {
  1003  
  1004  		if blockchain == "" {
  1005  			queryString = `
  1006  			SELECT symbol,name,address,decimals,blockchain,volume 
  1007  			FROM %s a INNER JOIN %s av ON (a.asset_id = av.asset_id) 
  1008  			WHERE av.time_stamp>to_timestamp($1)
  1009  			ORDER BY av.volume 
  1010  			DESC LIMIT $2 OFFSET $3`
  1011  			query = fmt.Sprintf(queryString, assetTable, assetVolumeTable)
  1012  			rows, err = rdb.postgresClient.Query(context.Background(), query, starttime.Unix(), numAssets, skip)
  1013  		} else {
  1014  			queryString = `
  1015  			SELECT symbol,name,address,decimals,blockchain,volume 
  1016  			FROM %s a INNER JOIN %s av ON (a.asset_id = av.asset_id) 
  1017  			WHERE blockchain= $1
  1018  			AND av.time_stamp>to_timestamp($2)
  1019  			ORDER BY av.volume 
  1020  			DESC LIMIT $3 OFFSET $4`
  1021  			query = fmt.Sprintf(queryString, assetTable, assetVolumeTable)
  1022  			rows, err = rdb.postgresClient.Query(context.Background(), query, blockchain, starttime.Unix(), numAssets, skip)
  1023  		}
  1024  
  1025  	} else {
  1026  		if blockchain == "" {
  1027  			queryString = `
  1028  			SELECT DISTINCT ON (av.volume,av.asset_id)  a.symbol,a.name,
  1029  			a.address,a.decimals,a.blockchain,av.volume 
  1030  			FROM %s  av INNER JOIN %s a ON av.asset_id=a.asset_id 
  1031  			INNER JOIN %s es ON av.asset_id=es.asset_id INNER JOIN %s e 
  1032  			ON es.exchange=e.name 
  1033  			WHERE e.centralized=true 
  1034  			ORDER BY av.volume 
  1035  			DESC  LIMIT $1 OFFSET $2`
  1036  			query = fmt.Sprintf(queryString, assetVolumeTable, assetTable, exchangesymbolTable, exchangeTable)
  1037  			rows, err = rdb.postgresClient.Query(context.Background(), query, numAssets, skip)
  1038  		} else {
  1039  			queryString = `
  1040  			SELECT DISTINCT ON (av.volume,av.asset_id) 
  1041  			a.symbol,a.name,a.address,a.decimals,a.blockchain,av.volume 
  1042  			FROM %s  av 
  1043  			INNER JOIN %s a  ON av.asset_id=a.asset_id 
  1044  			INNER JOIN %s es ON av.asset_id=es.asset_id 
  1045  			INNER JOIN %s e ON es.exchange=e.name 
  1046  			WHERE e.centralized=true AND a.blockchain = $1 
  1047  			ORDER BY av.volume 
  1048  			DESC  LIMIT $2 OFFSET $3`
  1049  			query = fmt.Sprintf(queryString, assetVolumeTable, assetTable, exchangesymbolTable, exchangeTable)
  1050  			rows, err = rdb.postgresClient.Query(context.Background(), query, blockchain, numAssets, skip)
  1051  		}
  1052  
  1053  	}
  1054  	if err != nil {
  1055  		return
  1056  	}
  1057  
  1058  	defer rows.Close()
  1059  
  1060  	for rows.Next() {
  1061  		var (
  1062  			decimals sql.NullInt64
  1063  			volume   float64
  1064  		)
  1065  		asset := dia.Asset{}
  1066  		err = rows.Scan(&asset.Symbol, &asset.Name, &asset.Address, &decimals, &asset.Blockchain, &volume)
  1067  		if err != nil {
  1068  			return
  1069  		}
  1070  		if decimals.Valid {
  1071  			asset.Decimals = uint8(decimals.Int64)
  1072  		}
  1073  		assetvolume := dia.AssetVolume{Asset: asset, Volume: volume}
  1074  		volumeSortedAssets = append(volumeSortedAssets, assetvolume)
  1075  	}
  1076  	return
  1077  }
  1078  
  1079  // GetAssetSource returns all exchanges @asset is traded on.
  1080  // For @cex true, only CEXes are returned. Otherwise only DEXes.
  1081  func (rdb *RelDB) GetAssetSource(asset dia.Asset, cex bool) (exchanges []string, err error) {
  1082  	var query string
  1083  	if cex {
  1084  		query = fmt.Sprintf(`
  1085  		SELECT DISTINCT ON (es.exchange) es.exchange 
  1086  		FROM %s es 
  1087  		INNER JOIN %s a ON es.asset_id = a.asset_id 
  1088  		WHERE a.blockchain=$1 AND a.address=$2
  1089  		`, exchangesymbolTable, assetTable)
  1090  	} else {
  1091  		query = fmt.Sprintf(`
  1092  		SELECT  DISTINCT ON (p.exchange) p.exchange
  1093  		FROM %s p 
  1094  		INNER JOIN %s pa ON p.pool_id=pa.pool_id 
  1095  		INNER JOIN %s a ON pa.asset_id=a.asset_id 
  1096  		WHERE a.blockchain=$1 AND a.address=$2
  1097  		`, poolTable, poolassetTable, assetTable)
  1098  	}
  1099  
  1100  	rows, err := rdb.postgresClient.Query(context.Background(), query, asset.Blockchain, asset.Address)
  1101  	if err != nil {
  1102  		return
  1103  	}
  1104  	defer rows.Close()
  1105  
  1106  	for rows.Next() {
  1107  		var (
  1108  			exchange string
  1109  		)
  1110  
  1111  		err = rows.Scan(&exchange)
  1112  		if err != nil {
  1113  			return
  1114  		}
  1115  
  1116  		exchanges = append(exchanges, exchange)
  1117  	}
  1118  	return
  1119  
  1120  }
  1121  
  1122  // GetAssetsWithVOLInflux returns all assets that have an entry in Influx's volumes table and hence have been traded since @timeInit.
  1123  func (datastore *DB) GetAssetsWithVOLInflux(timeInit time.Time) ([]dia.Asset, error) {
  1124  	var quotedAssets []dia.Asset
  1125  	q := fmt.Sprintf("SELECT address,blockchain,value FROM %s WHERE filter='VOL120' AND exchange='' AND time>%d AND time<now()", influxDbFiltersTable, timeInit.UnixNano())
  1126  	res, err := queryInfluxDB(datastore.influxClient, q)
  1127  	if err != nil {
  1128  		return quotedAssets, err
  1129  	}
  1130  
  1131  	// Filter and store all unique assets from the filters table.
  1132  	uniqueMap := make(map[dia.Asset]struct{})
  1133  	if len(res) > 0 && len(res[0].Series) > 0 {
  1134  		if len(res[0].Series[0].Values) > 0 {
  1135  			var asset dia.Asset
  1136  			for _, val := range res[0].Series[0].Values {
  1137  				if val[1] == nil || val[2] == nil {
  1138  					continue
  1139  				}
  1140  				asset.Address = val[1].(string)
  1141  				asset.Blockchain = val[2].(string)
  1142  				if _, ok := uniqueMap[asset]; !ok {
  1143  					quotedAssets = append(quotedAssets, asset)
  1144  					uniqueMap[asset] = struct{}{}
  1145  				}
  1146  			}
  1147  		} else {
  1148  			return quotedAssets, errors.New("no recent assets with volume in influx")
  1149  		}
  1150  	} else {
  1151  		return quotedAssets, errors.New("no recent asset with volume in influx")
  1152  	}
  1153  	return quotedAssets, nil
  1154  }