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 }