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

     1  package liquidityscrapers
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  	"unicode"
    10  
    11  	// "unicode"
    12  
    13  	"github.com/diadata-org/diadata/pkg/dia"
    14  	bifrosthelper "github.com/diadata-org/diadata/pkg/dia/helpers/bifrost-helper"
    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  const (
    21  	Blockchain = "Bifrost"
    22  )
    23  
    24  type BifrostLiquidityScraper struct {
    25  	logger                    *logrus.Entry
    26  	api                       *bifrosthelper.BifrostClient
    27  	poolChannel               chan dia.Pool
    28  	doneChannel               chan bool
    29  	blockchain                string
    30  	exchangeName              string
    31  	relDB                     *models.RelDB
    32  	datastore                 *models.DB
    33  	targetSwapContract        string
    34  	swapContractsLimit        int
    35  	handlerType               string
    36  	sleepBetweenContractCalls time.Duration
    37  }
    38  
    39  // NewBifrostLiquidityScraper returns a new BifrostLiquidityScraper initialized with default values.
    40  // The instance is asynchronously scraping as soon as it is created.
    41  // ENV values:
    42  //
    43  //	 	BIFROST_SLEEP_TIMEOUT - (optional,millisecond), make timeout between API calls, default "bifrosthelper.DefaultSleepBetweenContractCalls" value
    44  //		BIFROST_TARGET_SWAP_CONTRACT - (optional, string), useful for debug, default = ""
    45  //		BIFROST_DEBUG - (optional, bool), make stdout output with bifrost client http call, default = false
    46  func NewBifrostLiquidityScraper(exchange dia.Exchange, relDB *models.RelDB, datastore *models.DB) *BifrostLiquidityScraper {
    47  	targetSwapContract := utils.Getenv(
    48  		strings.ToUpper(exchange.Name)+"_TARGET_SWAP_CONTRACT",
    49  		"",
    50  	)
    51  	isDebug := utils.GetenvBool(strings.ToUpper(exchange.Name)+"_DEBUG", false)
    52  	sleepBetweenContractCalls := utils.GetTimeDurationFromIntAsMilliseconds(
    53  		utils.GetenvInt(
    54  			strings.ToUpper(exchange.Name)+"_SLEEP_TIMEOUT",
    55  			bifrosthelper.DefaultSleepBetweenContractCalls,
    56  		),
    57  	)
    58  	swapContractsLimit := utils.GetenvInt(
    59  		strings.ToUpper(exchange.Name)+"_SWAP_CONTRACTS_LIMIT",
    60  		bifrosthelper.DefaultSwapContractsLimit,
    61  	)
    62  
    63  	var (
    64  		poolChannel = make(chan dia.Pool)
    65  		doneChannel = make(chan bool)
    66  		scraper     *BifrostLiquidityScraper
    67  	)
    68  
    69  	bifrostClient := bifrosthelper.NewBifrostClient(
    70  		log.WithContext(context.Background()).WithField("context", "BifrostClient"),
    71  		sleepBetweenContractCalls,
    72  		isDebug,
    73  	)
    74  
    75  	scraper = &BifrostLiquidityScraper{
    76  		api:                       bifrostClient,
    77  		poolChannel:               poolChannel,
    78  		doneChannel:               doneChannel,
    79  		exchangeName:              exchange.Name,
    80  		blockchain:                Blockchain,
    81  		relDB:                     relDB,
    82  		datastore:                 datastore,
    83  		targetSwapContract:        targetSwapContract,
    84  		swapContractsLimit:        swapContractsLimit,
    85  		handlerType:               "liquidity",
    86  		sleepBetweenContractCalls: sleepBetweenContractCalls,
    87  	}
    88  	scraper.logger = logrus.
    89  		New().
    90  		WithContext(context.Background()).
    91  		WithField("handlerType", scraper.handlerType).
    92  		WithField("context", "BifrostLiquidityScraper")
    93  
    94  	go scraper.fetchPools()
    95  
    96  	return scraper
    97  }
    98  
    99  // func detectPrecision(input string) (uint8, error) {
   100  // 	normalized := normalizeNumber(input)
   101  // 	decimalIndex := strings.Index(normalized, ".")
   102  // 	if decimalIndex == -1 {
   103  // 		return 0, nil
   104  // 	}
   105  
   106  // 	precision := len(normalized) - decimalIndex - 1
   107  // 	return uint8(precision), nil
   108  // }
   109  
   110  func normalizeNumber(input string) string {
   111  	if strings.Contains(input, ",") && strings.Contains(input, ".") {
   112  		input = strings.ReplaceAll(input, ",", "")
   113  	} else if strings.Count(input, ",") > 1 {
   114  		input = strings.ReplaceAll(input, ",", "")
   115  	} else if strings.Count(input, ".") > 1 {
   116  		input = strings.ReplaceAll(input, ".", "")
   117  		input = strings.Replace(input, ",", ".", 1)
   118  	} else if strings.Contains(input, ",") {
   119  		input = strings.Replace(input, ",", ".", 1)
   120  	}
   121  
   122  	// Remove any remaining non-numeric characters (e.g., spaces)
   123  	return strings.Map(func(r rune) rune {
   124  		if unicode.IsDigit(r) || r == '.' {
   125  			return r
   126  		}
   127  		return -1
   128  	}, input)
   129  }
   130  
   131  func (s *BifrostLiquidityScraper) fetchPools() {
   132  	logger := s.logger.WithFields(logrus.Fields{
   133  		"function": "fetchPools",
   134  	})
   135  
   136  	// Fetch all pair tokens pool entries from api
   137  	bifrostPoolAssets, err := s.api.GetAllPoolAssets()
   138  	if err != nil {
   139  		s.logger.WithError(err).Error("failed to GetAllPoolAssets")
   140  	}
   141  
   142  	s.logger.Infof("Found %d pools", len(bifrostPoolAssets))
   143  
   144  	// Iterate all over the pool assets creating pool objects and sending them to the pool channel
   145  	for _, bPool := range bifrostPoolAssets {
   146  		dbAssets := make([]dia.Asset, 0)
   147  		for _, assetId := range bPool.Assets {
   148  			assetKey := strings.ToLower(assetId)
   149  			dbTokenInfo, err := s.relDB.GetAsset(assetKey, s.blockchain)
   150  			if err != nil {
   151  				logger.WithError(err).Error("Failed to GetAsset with address:%s, blockchain:%s", assetKey, s.blockchain)
   152  				continue
   153  			}
   154  
   155  			dbAssets = append(dbAssets, dbTokenInfo)
   156  		}
   157  
   158  		if len(dbAssets) != 2 {
   159  			logger.Error("found more than 2 asset types for the pool pair")
   160  			continue
   161  		}
   162  
   163  		if len(bPool.Balances) != 2 {
   164  			logger.Error("found more than 2 balances for the pool pair")
   165  			continue
   166  		}
   167  
   168  		tokenABalance, err := strconv.ParseFloat(normalizeNumber(bPool.Balances[0]), 64)
   169  		if err != nil {
   170  			logger.WithError(err).Error("failed to parse tokenA balance")
   171  			continue
   172  		}
   173  
   174  		tokenBBalance, err := strconv.ParseFloat(normalizeNumber(bPool.Balances[1]), 64)
   175  		if err != nil {
   176  			logger.WithError(err).Error("failed to parse tokenB balance")
   177  			continue
   178  		}
   179  
   180  		s.logger.WithFields(logrus.Fields{
   181  			"BalanceA": tokenABalance,
   182  			"BalanceB": tokenBBalance,
   183  		}).Info("Found balances")
   184  
   185  		tokenA := dia.AssetVolume{
   186  			Index:  0,
   187  			Asset:  dbAssets[0],
   188  			Volume: tokenABalance / math.Pow10(int(dbAssets[0].Decimals)),
   189  		}
   190  
   191  		tokenB := dia.AssetVolume{
   192  			Index:  1,
   193  			Asset:  dbAssets[1],
   194  			Volume: tokenBBalance / math.Pow10(int(dbAssets[1].Decimals)),
   195  		}
   196  
   197  		pool := dia.Pool{
   198  			Exchange:     dia.Exchange{Name: s.exchangeName},
   199  			Blockchain:   dia.BlockChain{Name: s.blockchain},
   200  			Address:      bPool.PoolId,
   201  			Time:         time.Now(),
   202  			Assetvolumes: []dia.AssetVolume{tokenA, tokenB},
   203  		}
   204  
   205  		// Determine USD liquidity.
   206  		if pool.SufficientNativeBalance(GLOBAL_NATIVE_LIQUIDITY_THRESHOLD) {
   207  			s.datastore.GetPoolLiquiditiesUSD(&pool, priceCache)
   208  		}
   209  
   210  		s.logger.WithFields(logrus.Fields{
   211  			"Address": pool.Address,
   212  		}).Info("sending pool to poolChannel")
   213  
   214  		s.poolChannel <- pool
   215  	}
   216  
   217  	s.doneChannel <- true
   218  }
   219  
   220  func (s *BifrostLiquidityScraper) Pool() chan dia.Pool {
   221  	return s.poolChannel
   222  }
   223  
   224  func (s *BifrostLiquidityScraper) Done() chan bool {
   225  	return s.doneChannel
   226  }