github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/liquidity-scrapers/VelarScraper.go (about)

     1  package liquidityscrapers
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"errors"
     7  	"math/big"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/diadata-org/diadata/pkg/dia"
    13  	"github.com/diadata-org/diadata/pkg/dia/helpers/stackshelper"
    14  	"github.com/diadata-org/diadata/pkg/dia/helpers/velarhelper"
    15  	models "github.com/diadata-org/diadata/pkg/model"
    16  	"github.com/diadata-org/diadata/pkg/utils"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  type VelarLiquidityScraper struct {
    21  	logger                *logrus.Entry
    22  	api                   *stackshelper.StacksClient
    23  	velarClient           *velarhelper.VelarClient
    24  	poolChannel           chan dia.Pool
    25  	doneChannel           chan bool
    26  	blockchain            string
    27  	exchangeName          string
    28  	sleepTimeMilliseconds int
    29  	relDB                 *models.RelDB
    30  	datastore             *models.DB
    31  	handlerType           string
    32  }
    33  
    34  // NewVelarLiquidityScraper returns a new VelarLiquidityScraper initialized with default values.
    35  // The instance is asynchronously scraping as soon as it is created.
    36  // ENV values:
    37  //
    38  //	 	VELAR_SLEEP_TIMEOUT - (optional, millisecond), make timeout between API calls, default "stackshelper.DefaultSleepBetweenCalls" value
    39  //		VELAR_HIRO_API_KEY - (optional, string), Hiro Stacks API key, improves scraping performance, default = ""
    40  //		VELAR_DEBUG - (optional, bool), make stdout output with velar client http call, default = false
    41  func NewVelarLiquidityScraper(exchange dia.Exchange, relDB *models.RelDB, datastore *models.DB) *VelarLiquidityScraper {
    42  	envPrefix := strings.ToUpper(exchange.Name)
    43  
    44  	sleepBetweenCalls := utils.GetenvInt(envPrefix+"_SLEEP_TIMEOUT", stackshelper.DefaultSleepBetweenCalls)
    45  	hiroAPIKey := utils.Getenv(envPrefix+"_HIRO_API_KEY", "")
    46  	isDebug := utils.GetenvBool(envPrefix+"_DEBUG", false)
    47  
    48  	stacksClient := stackshelper.NewStacksClient(
    49  		log.WithContext(context.Background()).WithField("context", "StacksClient"),
    50  		utils.GetTimeDurationFromIntAsMilliseconds(sleepBetweenCalls),
    51  		hiroAPIKey,
    52  		isDebug,
    53  	)
    54  
    55  	velarClient := velarhelper.NewVelarClient(
    56  		log.WithContext(context.Background()).WithField("context", "VelarClient"),
    57  		isDebug,
    58  	)
    59  
    60  	sleepTime, err := strconv.Atoi(utils.Getenv("SLEEP_TIME_MILLISECONDS", "1000"))
    61  	if err != nil {
    62  		log.Error("parse SLEEP_TIME_MILLISECONDS: ", err)
    63  	}
    64  	s := &VelarLiquidityScraper{
    65  		poolChannel:           make(chan dia.Pool),
    66  		doneChannel:           make(chan bool),
    67  		exchangeName:          exchange.Name,
    68  		blockchain:            exchange.BlockChain.Name,
    69  		sleepTimeMilliseconds: sleepTime,
    70  		api:                   stacksClient,
    71  		velarClient:           velarClient,
    72  		relDB:                 relDB,
    73  		datastore:             datastore,
    74  		handlerType:           "liquidity",
    75  	}
    76  
    77  	s.logger = logrus.
    78  		New().
    79  		WithContext(context.Background()).
    80  		WithField("handlerType", s.handlerType).
    81  		WithField("context", "VelarLiquidityScraper")
    82  
    83  	go s.fetchPools()
    84  
    85  	return s
    86  }
    87  
    88  func (s *VelarLiquidityScraper) fetchPools() {
    89  	tickers, err := s.velarClient.GetAllTickers()
    90  	if err != nil {
    91  		s.logger.WithError(err).Error("failed to fetch velar tickers")
    92  		return
    93  	}
    94  
    95  	for _, t := range tickers {
    96  		time.Sleep(time.Duration(s.sleepTimeMilliseconds) * time.Millisecond)
    97  
    98  		balances, err := s.fetchPoolBalances(t.PoolID)
    99  		if err != nil {
   100  			s.logger.WithError(err).Error("failed to fetch velar pool balances")
   101  			continue
   102  		}
   103  
   104  		tokens := [...]string{t.BaseCurrency, t.TargetCurrency}
   105  		dbAssets := make([]dia.Asset, 0, len(tokens))
   106  
   107  		for _, address := range tokens {
   108  			assset, err := s.relDB.GetAsset(address, s.blockchain)
   109  			if err != nil {
   110  				s.logger.WithError(err).Errorf("failed to GetAsset with key: %s", address)
   111  				continue
   112  			}
   113  			dbAssets = append(dbAssets, assset)
   114  		}
   115  
   116  		if len(dbAssets) != len(tokens) {
   117  			s.logger.Error("found less than 2 assets for the pool pair")
   118  			continue
   119  		}
   120  
   121  		assetVolumes := make([]dia.AssetVolume, len(balances))
   122  
   123  		for i, b := range balances {
   124  			asset := dbAssets[i]
   125  			volume, _ := utils.StringToFloat64(b.String(), int64(asset.Decimals))
   126  
   127  			assetVolumes[i] = dia.AssetVolume{
   128  				Index:  uint8(i),
   129  				Asset:  asset,
   130  				Volume: volume,
   131  			}
   132  		}
   133  
   134  		pool := dia.Pool{
   135  			Exchange:     dia.Exchange{Name: s.exchangeName},
   136  			Blockchain:   dia.BlockChain{Name: s.blockchain},
   137  			Address:      t.ID,
   138  			Time:         time.Now(),
   139  			Assetvolumes: assetVolumes,
   140  		}
   141  
   142  		if pool.SufficientNativeBalance(GLOBAL_NATIVE_LIQUIDITY_THRESHOLD) {
   143  			s.datastore.GetPoolLiquiditiesUSD(&pool, priceCache)
   144  		}
   145  
   146  		s.logger.WithField("pool", pool).Info("sending pool to poolChannel")
   147  		s.poolChannel <- pool
   148  	}
   149  
   150  	s.doneChannel <- true
   151  }
   152  
   153  func (s *VelarLiquidityScraper) fetchPoolBalances(poolID string) ([]*big.Int, error) {
   154  	var data []byte
   155  	var err error
   156  
   157  	if strings.HasPrefix(poolID, "21000") {
   158  		// Handle univ2 liquidity pools
   159  		args := stackshelper.ContractCallArgs{Sender: velarhelper.DeployerAddressV2}
   160  		poolContract := "univ2-pool-v1_0_0-" + poolID[4:]
   161  
   162  		var resp []byte
   163  		resp, err = s.api.CallContractFunction(velarhelper.DeployerAddressV2, poolContract, "get-pool", args)
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  
   168  		if entry, ok := stackshelper.DeserializeCVResponse(resp); ok {
   169  			data = entry
   170  		} else {
   171  			err = errors.New("failed to call get-pool: runtime error")
   172  		}
   173  	} else {
   174  		value := new(big.Int)
   175  		value.SetString(poolID, 10)
   176  		key := hex.EncodeToString(stackshelper.SerializeCVUint(value))
   177  
   178  		data, err = s.api.GetDataMapEntry(velarhelper.VelarCoreAddress, "pools", key)
   179  	}
   180  
   181  	if err != nil {
   182  		s.logger.WithError(err).Error("failed to fetch velar pool information")
   183  		return nil, err
   184  	}
   185  
   186  	pool, err := stackshelper.DeserializeCVTuple(data)
   187  	if err != nil {
   188  		s.logger.WithError(err).Error("failed to deserialize cv tuple")
   189  		return nil, err
   190  	}
   191  
   192  	balance0, err := stackshelper.DeserializeCVUint(pool["reserve0"])
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	balance1, err := stackshelper.DeserializeCVUint(pool["reserve1"])
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	return []*big.Int{balance0, balance1}, nil
   203  }
   204  
   205  func (s *VelarLiquidityScraper) Pool() chan dia.Pool {
   206  	return s.poolChannel
   207  }
   208  
   209  func (s *VelarLiquidityScraper) Done() chan bool {
   210  	return s.doneChannel
   211  }