github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/liquidity-scrapers/CurvefiScraper.go (about) 1 package liquidityscrapers 2 3 import ( 4 "math" 5 "math/big" 6 "strings" 7 "time" 8 9 "github.com/diadata-org/diadata/pkg/dia/helpers/ethhelper" 10 "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/curvefi" 11 "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/curvefifactory" 12 "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/curvefimeta" 13 models "github.com/diadata-org/diadata/pkg/model" 14 "github.com/diadata-org/diadata/pkg/utils" 15 16 "github.com/diadata-org/diadata/pkg/dia" 17 "github.com/ethereum/go-ethereum/accounts/abi/bind" 18 "github.com/ethereum/go-ethereum/common" 19 "github.com/ethereum/go-ethereum/ethclient" 20 ) 21 22 var ( 23 curveRestDial = "" 24 ) 25 26 type CurveFIScraper struct { 27 RestClient *ethclient.Client 28 relDB *models.RelDB 29 datastore *models.DB 30 poolChannel chan dia.Pool 31 doneChannel chan bool 32 blockchain string 33 exchangeName string 34 } 35 36 type curveRegistry struct { 37 Address common.Address 38 Type int 39 } 40 41 type CurveCoin struct { 42 Symbol string 43 Decimals uint8 44 Address string 45 Name string 46 } 47 48 func NewCurveFIScraper(exchange dia.Exchange, relDB *models.RelDB, datastore *models.DB) *CurveFIScraper { 49 var ( 50 restClient *ethclient.Client 51 err error 52 poolChannel = make(chan dia.Pool) 53 doneChannel = make(chan bool) 54 scraper *CurveFIScraper 55 registries []curveRegistry 56 ) 57 58 log.Infof("Init rest client for %s.", exchange.BlockChain.Name) 59 restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", curveRestDial)) 60 if err != nil { 61 log.Fatal("init rest client: ", err) 62 } 63 64 switch exchange.Name { 65 case dia.CurveFIExchange: 66 basePools := curveRegistry{Type: 1, Address: common.HexToAddress(exchange.Contract)} 67 cryptoswapPools := curveRegistry{Type: 1, Address: common.HexToAddress("0x8F942C20D02bEfc377D41445793068908E2250D0")} 68 metaPools := curveRegistry{Type: 2, Address: common.HexToAddress("0xB9fC157394Af804a3578134A6585C0dc9cc990d4")} 69 stableSwapRegistry := curveRegistry{Type: 3, Address: common.HexToAddress("0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf")} 70 factoryPools := curveRegistry{Type: 3, Address: common.HexToAddress("0xF18056Bbd320E96A48e3Fbf8bC061322531aac99")} 71 factory2Pools := curveRegistry{Type: 3, Address: common.HexToAddress("0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d")} 72 registries = []curveRegistry{stableSwapRegistry, factoryPools, factory2Pools, basePools, cryptoswapPools, metaPools} 73 74 case dia.CurveFIExchangeFantom: 75 exchange.Contract = "" 76 // basePools := curveRegistry{Type: 1, Address: common.HexToAddress(exchange.Contract)} 77 stableSwapFactory := curveRegistry{Type: 2, Address: common.HexToAddress("0x686d67265703D1f124c45E33d47d794c566889Ba")} 78 registries = []curveRegistry{stableSwapFactory} 79 case dia.CurveFIExchangeMoonbeam: 80 // basePools := curveRegistry{Type: 1, Address: common.HexToAddress(exchange.Contract)} 81 stableSwapFactory := curveRegistry{Type: 2, Address: common.HexToAddress("0x4244eB811D6e0Ef302326675207A95113dB4E1F8")} 82 registries = []curveRegistry{stableSwapFactory} 83 case dia.CurveFIExchangePolygon: 84 stableSwapFactory := curveRegistry{Type: 2, Address: common.HexToAddress("0x722272D36ef0Da72FF51c5A65Db7b870E2e8D4ee")} 85 registries = []curveRegistry{stableSwapFactory} 86 case dia.CurveFIExchangeArbitrum: 87 stableSwapFactory := curveRegistry{Type: 2, Address: common.HexToAddress("0xb17b674D9c5CB2e441F8e196a2f048A81355d031")} 88 registries = []curveRegistry{stableSwapFactory} 89 90 } 91 92 scraper = &CurveFIScraper{ 93 RestClient: restClient, 94 relDB: relDB, 95 datastore: datastore, 96 poolChannel: poolChannel, 97 doneChannel: doneChannel, 98 blockchain: exchange.BlockChain.Name, 99 exchangeName: exchange.Name, 100 } 101 102 go func() { 103 for _, registry := range registries { 104 poolAddresses, err := scraper.fetchPoolAddresses(registry) 105 if err != nil { 106 log.Error("fetch pool addresses: ", err) 107 } 108 for _, poolAddress := range poolAddresses { 109 scraper.loadPoolData(poolAddress.Hex(), registry) 110 } 111 } 112 scraper.doneChannel <- true 113 }() 114 115 return scraper 116 } 117 118 func (scraper *CurveFIScraper) fetchPoolAddresses(registry curveRegistry) (poolAddresses []common.Address, err error) { 119 120 if registry.Type == 1 { 121 log.Info("load base type pools..") 122 var ( 123 contract *curvefi.CurvefiCaller 124 poolCount *big.Int 125 ) 126 contract, err = curvefi.NewCurvefiCaller(registry.Address, scraper.RestClient) 127 if err != nil { 128 log.Error("NewCurvefiCaller: ", err) 129 } 130 131 poolCount, err = contract.PoolCount(&bind.CallOpts{}) 132 if err != nil { 133 log.Error("PoolCount: ", err) 134 } 135 log.Infof("poolCount in registry %s: %v ", registry.Address.Hex(), int(poolCount.Int64())) 136 for i := 0; i < int(poolCount.Int64()); i++ { 137 poolAddress, errPool := contract.PoolList(&bind.CallOpts{}, big.NewInt(int64(i))) 138 if errPool != nil { 139 log.Error("PoolList: ", err) 140 } 141 poolAddresses = append(poolAddresses, poolAddress) 142 } 143 } 144 145 if registry.Type == 2 || registry.Type == 3 { 146 log.Info("load meta / factory type pools...") 147 var ( 148 contract *curvefimeta.CurvefimetaCaller 149 poolCount *big.Int 150 ) 151 contract, err = curvefimeta.NewCurvefimetaCaller(registry.Address, scraper.RestClient) 152 if err != nil { 153 log.Error("NewCurvefiCaller: ", err) 154 } 155 poolCount, err = contract.PoolCount(&bind.CallOpts{}) 156 if err != nil { 157 log.Error("PoolCount: ", err) 158 } 159 log.Infof("poolCount in registry %s: %v ", registry.Address.Hex(), int(poolCount.Int64())) 160 for i := 0; i < int(poolCount.Int64()); i++ { 161 poolAddress, err := contract.PoolList(&bind.CallOpts{}, big.NewInt(int64(i))) 162 if err != nil { 163 log.Error("PoolList: ", err) 164 } 165 poolAddresses = append(poolAddresses, poolAddress) 166 } 167 } 168 return 169 } 170 171 func (scraper *CurveFIScraper) loadPoolData(poolAddress string, registry curveRegistry) { 172 // We need to handle ETH with address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE because of pools such as: 173 // https://etherscan.io/address/0xDC24316b9AE028F1497c275EB9192a3Ea0f67022#readContract 174 175 var ( 176 poolCoins [8]common.Address 177 pool dia.Pool 178 ) 179 pool.Blockchain = dia.BlockChain{Name: scraper.blockchain} 180 pool.Exchange = dia.Exchange{ 181 Name: scraper.exchangeName, 182 Centralized: false, 183 Bridge: false, 184 BlockChain: pool.Blockchain, 185 } 186 pool.Address = poolAddress 187 188 if registry.Type == 1 { 189 contract, err := curvefi.NewCurvefiCaller(registry.Address, scraper.RestClient) 190 if err != nil { 191 log.Error("loadPoolData - NewCurvefiCaller: ", err) 192 return 193 } 194 195 poolCoins, err = contract.GetCoins(&bind.CallOpts{}, common.HexToAddress(poolAddress)) 196 if err != nil { 197 log.Error("loadPoolData - GetCoins: ", err) 198 return 199 } 200 201 liquidities, err := contract.GetBalances(&bind.CallOpts{}, common.HexToAddress(poolAddress)) 202 if err != nil { 203 log.Error("loadPoolData - GetBalances: ", err) 204 return 205 } 206 207 var i int 208 for i < 8 && poolCoins[i] != (common.Address{}) { 209 if poolCoins[i].Hex() == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" { 210 poolCoins[i] = common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") 211 } 212 asset, err := scraper.relDB.GetAsset(poolCoins[i].Hex(), scraper.blockchain) 213 if err != nil { 214 asset, err = ethhelper.ETHAddressToAsset(poolCoins[i], scraper.RestClient, scraper.blockchain) 215 if err != nil { 216 return 217 } 218 } 219 liquidity, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidities[i]), new(big.Float).SetFloat64(math.Pow10(int(asset.Decimals)))).Float64() 220 av := dia.AssetVolume{Asset: asset, Volume: liquidity, Index: uint8(i)} 221 pool.Assetvolumes = append(pool.Assetvolumes, av) 222 pool.Time = time.Now() 223 i++ 224 } 225 226 } 227 228 if registry.Type == 2 { 229 contract, err := curvefimeta.NewCurvefimetaCaller(registry.Address, scraper.RestClient) 230 if err != nil { 231 log.Error("loadPoolData - NewCurvefiCaller: ", err) 232 return 233 } 234 235 poolCoins, err := contract.GetCoins(&bind.CallOpts{}, common.HexToAddress(poolAddress)) 236 if err != nil { 237 log.Error("loadPoolData - GetCoins: ", err) 238 return 239 } 240 241 liquidities, err := contract.GetBalances(&bind.CallOpts{}, common.HexToAddress(poolAddress)) 242 if err != nil { 243 log.Error("loadPoolData - GetBalances: ", err) 244 return 245 } 246 247 var i int 248 for i < 4 && poolCoins[i] != (common.Address{}) { 249 if poolCoins[i].Hex() == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" { 250 poolCoins[i] = common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") 251 } 252 asset, err := scraper.relDB.GetAsset(poolCoins[i].Hex(), scraper.blockchain) 253 if err != nil { 254 asset, err = ethhelper.ETHAddressToAsset(poolCoins[i], scraper.RestClient, scraper.blockchain) 255 if err != nil { 256 return 257 } 258 } 259 liquidity, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidities[i]), new(big.Float).SetFloat64(math.Pow10(int(asset.Decimals)))).Float64() 260 av := dia.AssetVolume{Asset: asset, Volume: liquidity, Index: uint8(i)} 261 pool.Assetvolumes = append(pool.Assetvolumes, av) 262 pool.Time = time.Now() 263 i++ 264 } 265 } 266 if registry.Type == 3 { 267 contract, err := curvefifactory.NewCurvefifactoryCaller(common.HexToAddress(poolAddress), scraper.RestClient) 268 if err != nil { 269 log.Error("loadPoolData - NewCurvefiCaller: ", err) 270 } 271 272 var i int64 273 for i < 8 { 274 poolAssetAddress, err := contract.Coins(&bind.CallOpts{}, big.NewInt(i)) 275 if err != nil { 276 i++ 277 continue 278 } 279 if poolAssetAddress == (common.Address{}) { 280 i++ 281 continue 282 } 283 if poolAssetAddress.Hex() == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" { 284 poolAssetAddress = common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") 285 } 286 287 liquidityBig, err := contract.Balances(&bind.CallOpts{}, big.NewInt(i)) 288 if err != nil { 289 log.Error("Get Balances: ", err) 290 } 291 292 asset, err := scraper.relDB.GetAsset(poolAssetAddress.Hex(), scraper.blockchain) 293 if err != nil { 294 asset, err = ethhelper.ETHAddressToAsset(poolAssetAddress, scraper.RestClient, scraper.blockchain) 295 if err != nil { 296 return 297 } 298 } 299 liquidity, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidityBig), new(big.Float).SetFloat64(math.Pow10(int(asset.Decimals)))).Float64() 300 av := dia.AssetVolume{Asset: asset, Volume: liquidity, Index: uint8(i)} 301 pool.Assetvolumes = append(pool.Assetvolumes, av) 302 pool.Time = time.Now() 303 i++ 304 } 305 306 } 307 308 // Determine USD liquidity. 309 if pool.SufficientNativeBalance(GLOBAL_NATIVE_LIQUIDITY_THRESHOLD) { 310 scraper.datastore.GetPoolLiquiditiesUSD(&pool, priceCache) 311 } 312 313 scraper.poolChannel <- pool 314 315 } 316 317 func (scraper *CurveFIScraper) Pool() chan dia.Pool { 318 return scraper.poolChannel 319 } 320 321 func (scraper *CurveFIScraper) Done() chan bool { 322 return scraper.doneChannel 323 } 324 325 // getAssetFromCache returns the full asset given by blockchain and address, either from the map @localCache 326 // or from Postgres, in which latter case it also adds the asset to the local cache. 327 // Remember that maps are always passed by reference. 328 func (scraper *CurveFIScraper) getAssetFromCache(localCache map[string]dia.Asset, blockchain string, address string) dia.Asset { 329 if asset, ok := localCache[assetIdentifier(blockchain, address)]; ok { 330 return asset 331 } 332 fullAsset, err := scraper.relDB.GetAsset(address, blockchain) 333 if err != nil { 334 log.Warnf("could not find asset with address %s on blockchain %s in postgres: ", address, blockchain) 335 } 336 localCache[assetIdentifier(blockchain, address)] = fullAsset 337 return fullAsset 338 } 339 340 func assetIdentifier(blockchain string, address string) string { 341 return blockchain + "-" + address 342 }