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  }