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 }