github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/liquidity-scrapers/UniswapV2.go (about) 1 package liquidityscrapers 2 3 import ( 4 "encoding/json" 5 "io/ioutil" 6 "math" 7 "math/big" 8 "os" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/diadata-org/diadata/pkg/dia/helpers/configCollectors" 14 "github.com/diadata-org/diadata/pkg/dia/helpers/ethhelper" 15 "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/uniswap" 16 models "github.com/diadata-org/diadata/pkg/model" 17 18 "github.com/diadata-org/diadata/pkg/dia" 19 "github.com/diadata-org/diadata/pkg/utils" 20 "github.com/ethereum/go-ethereum/accounts/abi/bind" 21 "github.com/ethereum/go-ethereum/common" 22 "github.com/ethereum/go-ethereum/ethclient" 23 ) 24 25 type UniswapPair struct { 26 Token0 dia.Asset 27 Token1 dia.Asset 28 ForeignName string 29 Address common.Address 30 } 31 32 const ( 33 restDialEthereum = "" 34 restDialBase = "" 35 restDialBSC = "" 36 restDialPolygon = "" 37 restDialCelo = "" 38 restDialFantom = "" 39 restDialMoonriver = "" 40 restDialArbitrum = "" 41 restDialLinea = "" 42 restDialAurora = "" 43 restDialMetis = "" 44 restDialAvalanche = "" 45 restDialTelos = "" 46 restDialEvmos = "" 47 restDialAstar = "" 48 restDialMoonbeam = "" 49 restDialWanchain = "" 50 restDialUnreal = "" 51 52 uniswapWaitMilliseconds = "25" 53 sushiswapWaitMilliseconds = "100" 54 pancakeswapWaitMilliseconds = "100" 55 dfynWaitMilliseconds = "100" 56 ubeswapWaitMilliseconds = "200" 57 spookyswapWaitMilliseconds = "200" 58 spiritswapWaitMilliseconds = "200" 59 solarbeamWaitMilliseconds = "200" 60 trisolarisWaitMilliseconds = "200" 61 metisWaitMilliseconds = "200" 62 moonriverWaitMilliseconds = "500" 63 avalancheWaitMilliseconds = "200" 64 telosWaitMilliseconds = "400" 65 evmosWaitMilliseconds = "400" 66 astarWaitMilliseconds = "1000" 67 moonbeamWaitMilliseconds = "1000" 68 wanchainWaitMilliseconds = "1000" 69 ) 70 71 type UniswapScraper struct { 72 RestClient *ethclient.Client 73 relDB *models.RelDB 74 datastore *models.DB 75 poolChannel chan dia.Pool 76 doneChannel chan bool 77 blockchain string 78 waitTime int 79 exchangeName string 80 pathToPools string 81 } 82 83 var exchangeFactoryContractAddress string 84 85 func NewUniswapScraper(exchange dia.Exchange, relDB *models.RelDB, datastore *models.DB) (us *UniswapScraper) { 86 87 pathToPools := utils.Getenv("PATH_TO_POOLS", "") 88 89 switch exchange.Name { 90 case dia.UniswapExchange: 91 us = makeUniswapPoolScraper(exchange, pathToPools, restDialEthereum, relDB, datastore, uniswapWaitMilliseconds) 92 case dia.UniswapExchangeBase: 93 us = makeUniswapPoolScraper(exchange, pathToPools, restDialBase, relDB, datastore, uniswapWaitMilliseconds) 94 case dia.SushiSwapExchange: 95 us = makeUniswapPoolScraper(exchange, pathToPools, restDialEthereum, relDB, datastore, sushiswapWaitMilliseconds) 96 case dia.SushiSwapExchangePolygon: 97 us = makeUniswapPoolScraper(exchange, pathToPools, restDialPolygon, relDB, datastore, sushiswapWaitMilliseconds) 98 case dia.SushiSwapExchangeFantom: 99 us = makeUniswapPoolScraper(exchange, pathToPools, restDialFantom, relDB, datastore, sushiswapWaitMilliseconds) 100 case dia.SushiSwapExchangeArbitrum: 101 us = makeUniswapPoolScraper(exchange, pathToPools, restDialArbitrum, relDB, datastore, sushiswapWaitMilliseconds) 102 case dia.CamelotExchange: 103 us = makeUniswapPoolScraper(exchange, pathToPools, restDialArbitrum, relDB, datastore, sushiswapWaitMilliseconds) 104 case dia.PanCakeSwap: 105 us = makeUniswapPoolScraper(exchange, pathToPools, restDialBSC, relDB, datastore, pancakeswapWaitMilliseconds) 106 case dia.DfynNetwork: 107 us = makeUniswapPoolScraper(exchange, pathToPools, restDialPolygon, relDB, datastore, dfynWaitMilliseconds) 108 case dia.QuickswapExchange: 109 us = makeUniswapPoolScraper(exchange, pathToPools, restDialPolygon, relDB, datastore, dfynWaitMilliseconds) 110 case dia.UbeswapExchange: 111 us = makeUniswapPoolScraper(exchange, pathToPools, restDialCelo, relDB, datastore, ubeswapWaitMilliseconds) 112 case dia.SpookyswapExchange: 113 us = makeUniswapPoolScraper(exchange, pathToPools, restDialFantom, relDB, datastore, spookyswapWaitMilliseconds) 114 case dia.SpiritswapExchange: 115 us = makeUniswapPoolScraper(exchange, pathToPools, restDialFantom, relDB, datastore, spiritswapWaitMilliseconds) 116 case dia.SolarbeamExchange: 117 us = makeUniswapPoolScraper(exchange, pathToPools, restDialMoonriver, relDB, datastore, solarbeamWaitMilliseconds) 118 case dia.TrisolarisExchange: 119 us = makeUniswapPoolScraper(exchange, pathToPools, restDialAurora, relDB, datastore, trisolarisWaitMilliseconds) 120 case dia.NetswapExchange: 121 us = makeUniswapPoolScraper(exchange, pathToPools, restDialMetis, relDB, datastore, metisWaitMilliseconds) 122 case dia.HuckleberryExchange: 123 us = makeUniswapPoolScraper(exchange, pathToPools, restDialMoonriver, relDB, datastore, moonriverWaitMilliseconds) 124 case dia.TraderJoeExchange: 125 us = makeUniswapPoolScraper(exchange, pathToPools, restDialAvalanche, relDB, datastore, avalancheWaitMilliseconds) 126 case dia.PangolinExchange: 127 us = makeUniswapPoolScraper(exchange, pathToPools, restDialAvalanche, relDB, datastore, avalancheWaitMilliseconds) 128 case dia.TethysExchange: 129 us = makeUniswapPoolScraper(exchange, pathToPools, restDialMetis, relDB, datastore, metisWaitMilliseconds) 130 case dia.HermesExchange: 131 us = makeUniswapPoolScraper(exchange, pathToPools, restDialMetis, relDB, datastore, metisWaitMilliseconds) 132 case dia.OmniDexExchange: 133 us = makeUniswapPoolScraper(exchange, pathToPools, restDialTelos, relDB, datastore, telosWaitMilliseconds) 134 case dia.DiffusionExchange: 135 us = makeUniswapPoolScraper(exchange, pathToPools, restDialEvmos, relDB, datastore, evmosWaitMilliseconds) 136 case dia.ArthswapExchange: 137 us = makeUniswapPoolScraper(exchange, pathToPools, restDialAstar, relDB, datastore, astarWaitMilliseconds) 138 case dia.ApeswapExchange: 139 us = makeUniswapPoolScraper(exchange, pathToPools, restDialAstar, relDB, datastore, astarWaitMilliseconds) 140 case dia.BiswapExchange: 141 us = makeUniswapPoolScraper(exchange, pathToPools, restDialAstar, relDB, datastore, astarWaitMilliseconds) 142 case dia.StellaswapExchange: 143 us = makeUniswapPoolScraper(exchange, pathToPools, restDialMoonbeam, relDB, datastore, moonbeamWaitMilliseconds) 144 case dia.WanswapExchange: 145 us = makeUniswapPoolScraper(exchange, pathToPools, restDialWanchain, relDB, datastore, wanchainWaitMilliseconds) 146 case dia.NileV1Exchange: 147 us = makeUniswapPoolScraper(exchange, pathToPools, restDialLinea, relDB, datastore, wanchainWaitMilliseconds) 148 case dia.RamsesV1Exchange: 149 us = makeUniswapPoolScraper(exchange, pathToPools, restDialArbitrum, relDB, datastore, wanchainWaitMilliseconds) 150 case dia.ThenaExchange: 151 us = makeUniswapPoolScraper(exchange, pathToPools, restDialBSC, relDB, datastore, sushiswapWaitMilliseconds) 152 case dia.PearlfiStableswapExchange: 153 us = makeUniswapPoolScraper(exchange, pathToPools, restDialUnreal, relDB, datastore, sushiswapWaitMilliseconds) 154 } 155 156 exchangeFactoryContractAddress = exchange.Contract 157 158 go func() { 159 us.fetchPools() 160 }() 161 return us 162 163 } 164 165 // makeUniswapPoolScraper returns an asset source as used in NewUniswapAssetSource. 166 func makeUniswapPoolScraper(exchange dia.Exchange, pathToPools string, restDial string, relDB *models.RelDB, datastore *models.DB, waitMilliseconds string) *UniswapScraper { 167 var ( 168 restClient *ethclient.Client 169 err error 170 poolChannel = make(chan dia.Pool) 171 doneChannel = make(chan bool) 172 us *UniswapScraper 173 waitTime int 174 ) 175 176 log.Infof("Init rest client for %s.", exchange.BlockChain.Name) 177 restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", restDial)) 178 if err != nil { 179 log.Fatal("init rest client: ", err) 180 } 181 182 waitTimeString := utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_WAIT_TIME", waitMilliseconds) 183 waitTime, err = strconv.Atoi(waitTimeString) 184 if err != nil { 185 log.Error("could not parse wait time: ", err) 186 waitTime = 500 187 } 188 189 us = &UniswapScraper{ 190 RestClient: restClient, 191 relDB: relDB, 192 datastore: datastore, 193 poolChannel: poolChannel, 194 doneChannel: doneChannel, 195 blockchain: exchange.BlockChain.Name, 196 waitTime: waitTime, 197 exchangeName: exchange.Name, 198 pathToPools: pathToPools, 199 } 200 return us 201 } 202 203 // fetchPools iterates through all (Uniswap) pools and sends them into the pool channel. 204 // In case the path us.pathToPools is not empty, it only takes into account pools found in this path. 205 func (us *UniswapScraper) fetchPools() { 206 207 if us.pathToPools != "" { 208 209 // Collect all pool addresses from json file. 210 poolAddresses, err := getAddressesFromConfig("liquidity-scrapers/uniswapv2/" + us.pathToPools) 211 if err != nil { 212 log.Error("fetch pool addresses from config file: ", err) 213 } 214 numPairs := len(poolAddresses) 215 log.Infof("fetch %d pools: %v", numPairs, poolAddresses) 216 217 for _, pool := range poolAddresses { 218 time.Sleep(time.Duration(us.waitTime) * time.Millisecond) 219 pool, err := us.GetPoolByAddress(pool) 220 if err != nil { 221 log.Errorln("Error getting pool ", pool) 222 } 223 log.Info("found pool: ", pool) 224 us.poolChannel <- pool 225 } 226 227 } else { 228 229 numPairs, err := us.getNumPairs() 230 if err != nil { 231 log.Fatal(err) 232 } 233 log.Info("Found ", numPairs, " pools") 234 235 for i := 0; i < numPairs; i++ { 236 time.Sleep(time.Duration(us.waitTime) * time.Millisecond) 237 pool, err := us.GetPoolByID(int64(numPairs - 1 - i)) 238 if err != nil { 239 log.Errorln("Error getting pair with ID ", numPairs-1-i) 240 } 241 log.Info("found pool: ", pool) 242 us.poolChannel <- pool 243 } 244 } 245 us.doneChannel <- true 246 } 247 248 // GetPoolByID returns the Uniswap Pool with the integer id @num. 249 func (us *UniswapScraper) GetPoolByID(num int64) (dia.Pool, error) { 250 var contract *uniswap.IUniswapV2FactoryCaller 251 252 contract, err := uniswap.NewIUniswapV2FactoryCaller(common.HexToAddress(exchangeFactoryContractAddress), us.RestClient) 253 if err != nil { 254 log.Error(err) 255 return dia.Pool{}, err 256 } 257 258 pairAddress, err := contract.AllPairs(&bind.CallOpts{}, big.NewInt(num)) 259 if err != nil { 260 log.Error(err) 261 return dia.Pool{}, err 262 } 263 264 pool, err := us.GetPoolByAddress(pairAddress) 265 if err != nil { 266 log.Error(err) 267 return dia.Pool{}, err 268 } 269 270 return pool, err 271 } 272 273 // Get a pool by its LP token address. 274 func (us *UniswapScraper) GetPoolByAddress(pairAddress common.Address) (pool dia.Pool, err error) { 275 var ( 276 pairContract *uniswap.IUniswapV2PairCaller 277 token0 dia.Asset 278 token1 dia.Asset 279 ) 280 281 connection := us.RestClient 282 pairContract, err = uniswap.NewIUniswapV2PairCaller(pairAddress, connection) 283 if err != nil { 284 log.Error(err) 285 return dia.Pool{}, err 286 } 287 288 // Getting tokens from pair 289 address0, _ := pairContract.Token0(&bind.CallOpts{}) 290 address1, _ := pairContract.Token1(&bind.CallOpts{}) 291 292 // Only fetch assets from on-chain in case they are not in our DB. 293 token0, err = us.relDB.GetAsset(address0.Hex(), us.blockchain) 294 if err != nil { 295 token0, err = ethhelper.ETHAddressToAsset(address0, us.RestClient, us.blockchain) 296 if err != nil { 297 return 298 } 299 } 300 token1, err = us.relDB.GetAsset(address1.Hex(), us.blockchain) 301 if err != nil { 302 token1, err = ethhelper.ETHAddressToAsset(address1, us.RestClient, us.blockchain) 303 if err != nil { 304 return 305 } 306 } 307 308 // Getting liquidity 309 liquidity, err := pairContract.GetReserves(&bind.CallOpts{}) 310 if err != nil { 311 log.Error("get reserves: ", err) 312 } 313 amount0, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidity.Reserve0), new(big.Float).SetFloat64(math.Pow10(int(token0.Decimals)))).Float64() 314 amount1, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidity.Reserve1), new(big.Float).SetFloat64(math.Pow10(int(token1.Decimals)))).Float64() 315 316 // TO DO: Fetch timestamp using block number? 317 pool.Time = time.Now() 318 319 // Fill Pool type with the above data 320 pool.Assetvolumes = append(pool.Assetvolumes, dia.AssetVolume{ 321 Asset: token0, 322 Volume: amount0, 323 Index: uint8(0), 324 }) 325 pool.Assetvolumes = append(pool.Assetvolumes, dia.AssetVolume{ 326 Asset: token1, 327 Volume: amount1, 328 Index: uint8(1), 329 }) 330 331 // Determine USD liquidity 332 if pool.SufficientNativeBalance(GLOBAL_NATIVE_LIQUIDITY_THRESHOLD) { 333 us.datastore.GetPoolLiquiditiesUSD(&pool, priceCache) 334 } 335 336 pool.Address = pairAddress.Hex() 337 pool.Blockchain = dia.BlockChain{Name: us.blockchain} 338 pool.Exchange = dia.Exchange{Name: us.exchangeName} 339 340 return pool, nil 341 } 342 343 // GetDecimals returns the decimals of the token with address @tokenAddress 344 func (us *UniswapScraper) GetDecimals(tokenAddress common.Address) (decimals uint8, err error) { 345 346 var contract *uniswap.IERC20Caller 347 contract, err = uniswap.NewIERC20Caller(tokenAddress, us.RestClient) 348 if err != nil { 349 log.Error(err) 350 return 351 } 352 decimals, err = contract.Decimals(&bind.CallOpts{}) 353 354 return 355 } 356 357 func (us *UniswapScraper) GetName(tokenAddress common.Address) (name string, err error) { 358 359 var contract *uniswap.IERC20Caller 360 contract, err = uniswap.NewIERC20Caller(tokenAddress, us.RestClient) 361 if err != nil { 362 log.Error(err) 363 return 364 } 365 name, err = contract.Name(&bind.CallOpts{}) 366 367 return 368 } 369 370 func (us *UniswapScraper) Pool() chan dia.Pool { 371 return us.poolChannel 372 } 373 374 func (us *UniswapScraper) Done() chan bool { 375 return us.doneChannel 376 } 377 378 func (us *UniswapScraper) getNumPairs() (int, error) { 379 var contract *uniswap.IUniswapV2FactoryCaller 380 contract, err := uniswap.NewIUniswapV2FactoryCaller(common.HexToAddress(exchangeFactoryContractAddress), us.RestClient) 381 if err != nil { 382 log.Error(err) 383 } 384 385 numPairs, err := contract.AllPairsLength(&bind.CallOpts{}) 386 return int(numPairs.Int64()), err 387 } 388 389 // getAddressesFromConfig returns a list of Uniswap pool addresses taken from a config file. 390 func getAddressesFromConfig(filename string) (pairAddresses []common.Address, err error) { 391 392 // Load file and read data 393 filehandle := configCollectors.ConfigFileConnectors(filename, ".json") 394 jsonFile, err := os.Open(filehandle) 395 if err != nil { 396 return 397 } 398 defer func() { 399 err = jsonFile.Close() 400 if err != nil { 401 log.Error(err) 402 } 403 }() 404 405 byteData, err := ioutil.ReadAll(jsonFile) 406 if err != nil { 407 return 408 } 409 410 // Unmarshal read data 411 type scrapedPair struct { 412 Address string `json:"Address"` 413 ForeignName string `json:"ForeignName"` 414 } 415 type scrapedPairList struct { 416 AllPairs []scrapedPair `json:"Pools"` 417 } 418 var allPairs scrapedPairList 419 err = json.Unmarshal(byteData, &allPairs) 420 if err != nil { 421 return 422 } 423 424 // Extract addresses 425 for _, token := range allPairs.AllPairs { 426 pairAddresses = append(pairAddresses, common.HexToAddress(token.Address)) 427 } 428 429 return 430 }