github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/liquidity-scrapers/Velodrome.go (about) 1 package liquidityscrapers 2 3 import ( 4 "math" 5 "math/big" 6 "strconv" 7 "strings" 8 "time" 9 10 "github.com/diadata-org/diadata/pkg/dia/helpers/ethhelper" 11 "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/velodrome" 12 models "github.com/diadata-org/diadata/pkg/model" 13 14 "github.com/diadata-org/diadata/pkg/dia" 15 "github.com/diadata-org/diadata/pkg/utils" 16 "github.com/ethereum/go-ethereum/accounts/abi/bind" 17 "github.com/ethereum/go-ethereum/common" 18 "github.com/ethereum/go-ethereum/ethclient" 19 ) 20 21 type VelodromePoolScraper struct { 22 RestClient *ethclient.Client 23 relDB *models.RelDB 24 datastore *models.DB 25 poolChannel chan dia.Pool 26 doneChannel chan bool 27 blockchain string 28 waitTime int 29 exchange dia.Exchange 30 } 31 32 var ( 33 velodromeWaitMilliseconds = "500" 34 restDialOptimism = "" 35 restDialSwellchain = "" 36 ) 37 38 func NewVelodromePoolScraper(exchange dia.Exchange, relDB *models.RelDB, datastore *models.DB) (us *VelodromePoolScraper) { 39 40 switch exchange.Name { 41 case dia.VelodromeExchange: 42 us = makeVelodromePoolScraper(exchange, relDB, datastore, restDialOptimism, velodromeWaitMilliseconds) 43 case dia.VelodromeExchangeSwellchain: 44 us = makeVelodromePoolScraper(exchange, relDB, datastore, restDialSwellchain, velodromeWaitMilliseconds) 45 case dia.VelodromeSlipstreamExchange: 46 us = makeVelodromePoolScraper(exchange, relDB, datastore, restDialOptimism, velodromeWaitMilliseconds) 47 case dia.AerodromeV1Exchange: 48 us = makeVelodromePoolScraper(exchange, relDB, datastore, restDialBase, velodromeWaitMilliseconds) 49 case dia.AerodromeSlipstreamExchange: 50 us = makeVelodromePoolScraper(exchange, relDB, datastore, restDialBase, velodromeWaitMilliseconds) 51 } 52 53 go func() { 54 us.fetchPools() 55 }() 56 return us 57 58 } 59 60 func makeVelodromePoolScraper(exchange dia.Exchange, relDB *models.RelDB, datastore *models.DB, restDial string, waitMilliseconds string) *VelodromePoolScraper { 61 var ( 62 restClient *ethclient.Client 63 err error 64 poolChannel = make(chan dia.Pool) 65 doneChannel = make(chan bool) 66 us *VelodromePoolScraper 67 waitTime int 68 ) 69 70 log.Infof("Init rest client for %s.", exchange.BlockChain.Name) 71 restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", restDial)) 72 if err != nil { 73 log.Fatal("init rest client: ", err) 74 } 75 76 waitTimeString := utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_WAIT_TIME", waitMilliseconds) 77 waitTime, err = strconv.Atoi(waitTimeString) 78 if err != nil { 79 log.Error("could not parse wait time: ", err) 80 waitTime = 500 81 } 82 83 us = &VelodromePoolScraper{ 84 RestClient: restClient, 85 relDB: relDB, 86 datastore: datastore, 87 poolChannel: poolChannel, 88 doneChannel: doneChannel, 89 blockchain: exchange.BlockChain.Name, 90 waitTime: waitTime, 91 exchange: exchange, 92 } 93 return us 94 } 95 96 // fetchPools iterates through all (Velodrome) pools and sends them into the pool channel. 97 func (us *VelodromePoolScraper) fetchPools() { 98 numPairs, err := us.getNumPairs() 99 if err != nil { 100 log.Fatal(err) 101 } 102 log.Info("Found ", numPairs, " pools") 103 104 for i := 0; i < numPairs; i++ { 105 time.Sleep(time.Duration(us.waitTime) * time.Millisecond) 106 pool, err := us.GetPoolByID(int64(numPairs - 1 - i)) 107 if err != nil { 108 log.Errorln("Error getting pair with ID ", numPairs-1-i) 109 } 110 log.Info("found pool: ", pool) 111 us.poolChannel <- pool 112 } 113 114 us.doneChannel <- true 115 } 116 117 // GetPoolByID returns the Velodrome Pool with the integer id @num. 118 func (us *VelodromePoolScraper) GetPoolByID(num int64) (dia.Pool, error) { 119 var contract *velodrome.PoolFactoryCaller 120 121 contract, err := velodrome.NewPoolFactoryCaller(common.HexToAddress(us.exchange.Contract), us.RestClient) 122 if err != nil { 123 log.Error("NewPoolFactoryCaller: ", err) 124 return dia.Pool{}, err 125 } 126 127 pairAddress, err := contract.AllPools(&bind.CallOpts{}, big.NewInt(num)) 128 if err != nil { 129 log.Error("AllPools: ", err) 130 return dia.Pool{}, err 131 } 132 log.Info("pool: ", pairAddress.Hex()) 133 134 pool, err := us.GetPoolByAddress(pairAddress) 135 if err != nil { 136 log.Error("GetPoolByAddress: ", err) 137 return dia.Pool{}, err 138 } 139 140 return pool, err 141 } 142 143 // Get a pool by its LP token address. 144 func (us *VelodromePoolScraper) GetPoolByAddress(pairAddress common.Address) (pool dia.Pool, err error) { 145 var ( 146 pairContract *velodrome.IPoolCaller 147 token0 dia.Asset 148 token1 dia.Asset 149 ) 150 151 connection := us.RestClient 152 pairContract, err = velodrome.NewIPoolCaller(pairAddress, connection) 153 if err != nil { 154 log.Error(err) 155 return dia.Pool{}, err 156 } 157 158 // Getting tokens from pair 159 address0, _ := pairContract.Token0(&bind.CallOpts{}) 160 address1, _ := pairContract.Token1(&bind.CallOpts{}) 161 162 // Only fetch assets from on-chain in case they are not in our DB. 163 token0, err = us.relDB.GetAsset(address0.Hex(), us.blockchain) 164 if err != nil { 165 token0, err = ethhelper.ETHAddressToAsset(address0, us.RestClient, us.blockchain) 166 if err != nil { 167 return 168 } 169 } 170 token1, err = us.relDB.GetAsset(address1.Hex(), us.blockchain) 171 if err != nil { 172 token1, err = ethhelper.ETHAddressToAsset(address1, us.RestClient, us.blockchain) 173 if err != nil { 174 return 175 } 176 } 177 178 if us.exchange.Name == dia.AerodromeSlipstreamExchange || us.exchange.Name == dia.VelodromeSlipstreamExchange { 179 us.GetLiquidityUniV3Type(token0, token1, pairAddress, &pool) 180 } else { 181 us.GetLiquidityUniV2Type(token0, token1, pairContract, &pool) 182 } 183 184 pool.Address = pairAddress.Hex() 185 pool.Blockchain = dia.BlockChain{Name: us.blockchain} 186 pool.Exchange = dia.Exchange{Name: us.exchange.Name} 187 188 return pool, nil 189 } 190 191 func (us *VelodromePoolScraper) GetLiquidityUniV2Type(token0 dia.Asset, token1 dia.Asset, pairContract *velodrome.IPoolCaller, pool *dia.Pool) { 192 liquidity, err := pairContract.GetReserves(&bind.CallOpts{}) 193 if err != nil { 194 log.Error("get reserves: ", err) 195 return 196 } 197 amount0, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidity.Reserve0), new(big.Float).SetFloat64(math.Pow10(int(token0.Decimals)))).Float64() 198 amount1, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidity.Reserve1), new(big.Float).SetFloat64(math.Pow10(int(token1.Decimals)))).Float64() 199 200 // TO DO: Fetch timestamp using block number? 201 pool.Time = time.Now() 202 203 // Fill Pool type with the above data 204 pool.Assetvolumes = append(pool.Assetvolumes, dia.AssetVolume{ 205 Asset: token0, 206 Volume: amount0, 207 Index: uint8(0), 208 }) 209 pool.Assetvolumes = append(pool.Assetvolumes, dia.AssetVolume{ 210 Asset: token1, 211 Volume: amount1, 212 Index: uint8(1), 213 }) 214 215 // Determine USD liquidity 216 if pool.SufficientNativeBalance(GLOBAL_NATIVE_LIQUIDITY_THRESHOLD) { 217 us.datastore.GetPoolLiquiditiesUSD(pool, priceCache) 218 } 219 } 220 221 func (us *VelodromePoolScraper) GetLiquidityUniV3Type(token0 dia.Asset, token1 dia.Asset, pairAddress common.Address, pool *dia.Pool) { 222 balance0Big, err := ethhelper.GetBalanceOf(common.HexToAddress(token0.Address), pairAddress, us.RestClient) 223 if err != nil { 224 log.Error("GetBalanceOf: ", err) 225 } 226 balance1Big, err := ethhelper.GetBalanceOf(common.HexToAddress(token1.Address), pairAddress, us.RestClient) 227 if err != nil { 228 log.Error("GetBalanceOf: ", err) 229 } 230 log.Infof("balance0 -- balance1 -- pool: %v -- %v -- %s ", balance0Big, balance1Big, pairAddress) 231 balance0, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(balance0Big), new(big.Float).SetFloat64(math.Pow10(int(token0.Decimals)))).Float64() 232 balance1, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(balance1Big), new(big.Float).SetFloat64(math.Pow10(int(token1.Decimals)))).Float64() 233 234 pool.Assetvolumes = append(pool.Assetvolumes, dia.AssetVolume{Asset: token0, Volume: balance0, Index: uint8(0)}) 235 pool.Assetvolumes = append(pool.Assetvolumes, dia.AssetVolume{Asset: token1, Volume: balance1, Index: uint8(1)}) 236 237 // Determine USD liquidity 238 if balance0 > GLOBAL_NATIVE_LIQUIDITY_THRESHOLD && balance1 > GLOBAL_NATIVE_LIQUIDITY_THRESHOLD { 239 us.datastore.GetPoolLiquiditiesUSD(pool, priceCache) 240 } 241 } 242 243 // GetDecimals returns the decimals of the token with address @tokenAddress 244 func (us *VelodromePoolScraper) GetDecimals(tokenAddress common.Address) (decimals uint8, err error) { 245 246 var contract *velodrome.IERC20MetadataCaller 247 contract, err = velodrome.NewIERC20MetadataCaller(tokenAddress, us.RestClient) 248 if err != nil { 249 log.Error(err) 250 return 251 } 252 decimals, err = contract.Decimals(&bind.CallOpts{}) 253 254 return 255 } 256 257 func (us *VelodromePoolScraper) GetName(tokenAddress common.Address) (name string, err error) { 258 259 var contract *velodrome.IERC20MetadataCaller 260 contract, err = velodrome.NewIERC20MetadataCaller(tokenAddress, us.RestClient) 261 if err != nil { 262 log.Error(err) 263 return 264 } 265 name, err = contract.Name(&bind.CallOpts{}) 266 267 return 268 } 269 270 func (us *VelodromePoolScraper) Pool() chan dia.Pool { 271 return us.poolChannel 272 } 273 274 func (us *VelodromePoolScraper) Done() chan bool { 275 return us.doneChannel 276 } 277 278 func (us *VelodromePoolScraper) getNumPairs() (int, error) { 279 var contract *velodrome.IPoolFactoryCaller 280 contract, err := velodrome.NewIPoolFactoryCaller(common.HexToAddress(us.exchange.Contract), us.RestClient) 281 if err != nil { 282 log.Error(err) 283 } 284 285 numPairs, err := contract.AllPoolsLength(&bind.CallOpts{}) 286 if err != nil { 287 return 0, err 288 } 289 return int(numPairs.Int64()), err 290 }