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  }