github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/exchange-scrapers/UniswapV2Scraper.go (about)

     1  package scrapers
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"io/ioutil"
     7  	"math"
     8  	"math/big"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    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/dia/helpers"
    20  	"github.com/diadata-org/diadata/pkg/dia/helpers/configCollectors"
    21  	"github.com/diadata-org/diadata/pkg/utils"
    22  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    23  	"github.com/ethereum/go-ethereum/common"
    24  	"github.com/ethereum/go-ethereum/ethclient"
    25  )
    26  
    27  var (
    28  	exchangeFactoryContractAddress = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"
    29  	reverseBasetokens              *[]string
    30  	reverseQuotetokens             *[]string
    31  	mainBaseAssets                 = []string{
    32  		"0xdAC17F958D2ee523a2206206994597C13D831ec7",
    33  	}
    34  	poolMap = make(map[string]UniswapPair)
    35  )
    36  
    37  const (
    38  	restDialEth = ""
    39  	wsDialEth   = ""
    40  
    41  	restDialBase = ""
    42  	wsDialBase   = ""
    43  
    44  	restDialBSC = ""
    45  	wsDialBSC   = ""
    46  
    47  	restDialPolygon = ""
    48  	wsDialPolygon   = ""
    49  
    50  	restDialCelo = ""
    51  	wsDialCelo   = ""
    52  
    53  	restDialFantom = ""
    54  	wsDialFantom   = ""
    55  
    56  	restDialMoonriver = ""
    57  	wsDialMoonriver   = ""
    58  
    59  	restDialAurora = ""
    60  	wsDialAurora   = ""
    61  
    62  	restDialArbitrum = ""
    63  	wsDialArbitrum   = ""
    64  
    65  	restDialLinea = ""
    66  	wsDialLinea   = ""
    67  
    68  	restDialMetis = ""
    69  	wsDialMetis   = ""
    70  
    71  	restDialAvalanche = ""
    72  	wsDialAvalanche   = ""
    73  
    74  	restDialTelos = ""
    75  	wsDialTelos   = ""
    76  
    77  	restDialEvmos = ""
    78  	wsDialEvmos   = ""
    79  
    80  	restDialAstar = ""
    81  	wsDialAstar   = ""
    82  
    83  	restDialMoonbeam = ""
    84  	wsDialMoonbeam   = ""
    85  
    86  	restDialWanchain = ""
    87  	wsDialWanchain   = ""
    88  
    89  	restDialUnreal = ""
    90  	wsDialUnreal   = ""
    91  
    92  	uniswapWaitMilliseconds     = "25"
    93  	sushiswapWaitMilliseconds   = "100"
    94  	pancakeswapWaitMilliseconds = "200"
    95  	dfynWaitMilliseconds        = "100"
    96  	quickswapWaitMilliseconds   = "200"
    97  	ubeswapWaitMilliseconds     = "200"
    98  	spookyswapWaitMilliseconds  = "200"
    99  	solarbeamWaitMilliseconds   = "400"
   100  	trisolarisWaitMilliseconds  = "200"
   101  	metisWaitMilliseconds       = "200"
   102  	moonriverWaitMilliseconds   = "500"
   103  	avalancheWaitMilliseconds   = "200"
   104  	telosWaitMilliseconds       = "400"
   105  	evmosWaitMilliseconds       = "400"
   106  	astarWaitMilliseconds       = "1000"
   107  	moonbeamWaitMilliseconds    = "1000"
   108  	wanchainWaitMilliseconds    = "1000"
   109  )
   110  
   111  type UniswapToken struct {
   112  	Address  common.Address
   113  	Symbol   string
   114  	Decimals uint8
   115  	Name     string
   116  }
   117  
   118  type UniswapPair struct {
   119  	Token0      UniswapToken
   120  	Token1      UniswapToken
   121  	ForeignName string
   122  	Address     common.Address
   123  }
   124  
   125  type UniswapSwap struct {
   126  	ID         string
   127  	Timestamp  int64
   128  	Pair       UniswapPair
   129  	Amount0In  float64
   130  	Amount0Out float64
   131  	Amount1In  float64
   132  	Amount1Out float64
   133  }
   134  
   135  type UniswapScraper struct {
   136  	WsClient   *ethclient.Client
   137  	RestClient *ethclient.Client
   138  	relDB      *models.RelDB
   139  	// signaling channels for session initialization and finishing
   140  	//initDone     chan nothing
   141  	run          bool
   142  	shutdown     chan nothing
   143  	shutdownDone chan nothing
   144  	// error handling; to read error or closed, first acquire read lock
   145  	// only cleanup method should hold write lock
   146  	errorLock sync.RWMutex
   147  	error     error
   148  	closed    bool
   149  	// used to keep track of trading pairs that we subscribed to
   150  	pairScrapers map[string]*UniswapPairScraper
   151  	exchangeName string
   152  	chanTrades   chan *dia.Trade
   153  	waitTime     int
   154  	// If true, only pairs given in config file are scraped. Default is false.
   155  	listenByAddress  bool
   156  	fetchPoolsFromDB bool
   157  }
   158  
   159  // NewUniswapScraper returns a new UniswapScraper for the given pair
   160  func NewUniswapScraper(exchange dia.Exchange, scrape bool, relDB *models.RelDB) *UniswapScraper {
   161  	log.Info("NewUniswapScraper: ", exchange.Name)
   162  	var (
   163  		s                *UniswapScraper
   164  		listenByAddress  bool
   165  		fetchPoolsFromDB bool
   166  		err              error
   167  	)
   168  	exchangeFactoryContractAddress = exchange.Contract
   169  
   170  	listenByAddress, err = strconv.ParseBool(utils.Getenv("LISTEN_BY_ADDRESS", ""))
   171  	if err != nil {
   172  		log.Fatal("parse LISTEN_BY_ADDRESS: ", err)
   173  	}
   174  
   175  	fetchPoolsFromDB, err = strconv.ParseBool(utils.Getenv("FETCH_POOLS_FROM_DB", ""))
   176  	if err != nil {
   177  		log.Fatal("parse FETCH_POOLS_FROM_DB: ", err)
   178  	}
   179  
   180  	switch exchange.Name {
   181  	case dia.UniswapExchange:
   182  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialEth, wsDialEth, uniswapWaitMilliseconds)
   183  	case dia.UniswapExchangeBase:
   184  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialBase, wsDialBase, uniswapWaitMilliseconds)
   185  	case dia.SushiSwapExchange:
   186  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialEth, wsDialEth, sushiswapWaitMilliseconds)
   187  	case dia.SushiSwapExchangePolygon:
   188  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialPolygon, wsDialPolygon, metisWaitMilliseconds)
   189  	case dia.SushiSwapExchangeFantom:
   190  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialFantom, wsDialFantom, metisWaitMilliseconds)
   191  	case dia.SushiSwapExchangeArbitrum:
   192  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialArbitrum, wsDialArbitrum, metisWaitMilliseconds)
   193  	case dia.CamelotExchange:
   194  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialArbitrum, wsDialArbitrum, metisWaitMilliseconds)
   195  	case dia.PanCakeSwap:
   196  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialBSC, wsDialBSC, pancakeswapWaitMilliseconds)
   197  	case dia.DfynNetwork:
   198  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialPolygon, wsDialPolygon, dfynWaitMilliseconds)
   199  	case dia.QuickswapExchange:
   200  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialPolygon, wsDialPolygon, quickswapWaitMilliseconds)
   201  	case dia.UbeswapExchange:
   202  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialCelo, wsDialCelo, ubeswapWaitMilliseconds)
   203  	case dia.SpookyswapExchange:
   204  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialFantom, wsDialFantom, spookyswapWaitMilliseconds)
   205  	case dia.SpiritswapExchange:
   206  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialFantom, wsDialFantom, spookyswapWaitMilliseconds)
   207  	case dia.SolarbeamExchange:
   208  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialMoonriver, wsDialMoonriver, solarbeamWaitMilliseconds)
   209  	case dia.TrisolarisExchange:
   210  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialAurora, wsDialAurora, trisolarisWaitMilliseconds)
   211  	case dia.NetswapExchange:
   212  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialMetis, wsDialMetis, metisWaitMilliseconds)
   213  	case dia.HuckleberryExchange:
   214  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialMoonriver, wsDialMoonriver, moonriverWaitMilliseconds)
   215  	case dia.TraderJoeExchange:
   216  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialAvalanche, wsDialAvalanche, avalancheWaitMilliseconds)
   217  	case dia.PangolinExchange:
   218  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialAvalanche, wsDialAvalanche, avalancheWaitMilliseconds)
   219  	case dia.TethysExchange:
   220  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialMetis, wsDialMetis, metisWaitMilliseconds)
   221  	case dia.HermesExchange:
   222  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialMetis, wsDialMetis, metisWaitMilliseconds)
   223  	case dia.OmniDexExchange:
   224  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialTelos, wsDialTelos, telosWaitMilliseconds)
   225  	case dia.DiffusionExchange:
   226  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialEvmos, wsDialEvmos, evmosWaitMilliseconds)
   227  	case dia.ApeswapExchange:
   228  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialBSC, wsDialBSC, pancakeswapWaitMilliseconds)
   229  	case dia.BiswapExchange:
   230  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialBSC, wsDialBSC, pancakeswapWaitMilliseconds)
   231  	case dia.ArthswapExchange:
   232  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialAstar, wsDialAstar, astarWaitMilliseconds)
   233  	case dia.StellaswapExchange:
   234  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialMoonbeam, wsDialMoonbeam, moonbeamWaitMilliseconds)
   235  	case dia.WanswapExchange:
   236  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialWanchain, wsDialWanchain, wanchainWaitMilliseconds)
   237  	case dia.NileV1Exchange:
   238  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialLinea, wsDialLinea, wanchainWaitMilliseconds)
   239  	case dia.RamsesV1Exchange:
   240  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialArbitrum, wsDialArbitrum, wanchainWaitMilliseconds)
   241  	case dia.ThenaExchange:
   242  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialBSC, wsDialBSC, sushiswapWaitMilliseconds)
   243  	case dia.PearlfiStableswapExchange:
   244  		s = makeUniswapScraper(exchange, listenByAddress, fetchPoolsFromDB, restDialUnreal, wsDialUnreal, sushiswapWaitMilliseconds)
   245  
   246  	}
   247  
   248  	s.relDB = relDB
   249  
   250  	// Only include pools with (minimum) liquidity bigger than given env var.
   251  	liquidityThreshold, err := strconv.ParseFloat(utils.Getenv("LIQUIDITY_THRESHOLD", "0"), 64)
   252  	if err != nil {
   253  		liquidityThreshold = float64(0)
   254  		log.Warnf("parse liquidity threshold:  %v. Set to default %v", err, liquidityThreshold)
   255  	}
   256  	// Only include pools with (minimum) liquidity USD value bigger than given env var.
   257  	liquidityThresholdUSD, err := strconv.ParseFloat(utils.Getenv("LIQUIDITY_THRESHOLD_USD", "0"), 64)
   258  	if err != nil {
   259  		liquidityThresholdUSD = float64(0)
   260  		log.Warnf("parse liquidity threshold:  %v. Set to default %v", err, liquidityThresholdUSD)
   261  	}
   262  
   263  	// Fetch all pool with given liquidity threshold from database.
   264  	poolMap, err = s.makeUniPoolMap(liquidityThreshold, liquidityThresholdUSD)
   265  	if err != nil {
   266  		log.Fatal("build poolMap: ", err)
   267  	}
   268  
   269  	if scrape {
   270  		go s.mainLoop()
   271  	}
   272  	return s
   273  }
   274  
   275  // makeUniswapScraper returns a uniswap scraper as used in NewUniswapScraper.
   276  func makeUniswapScraper(exchange dia.Exchange, listenByAddress bool, fetchPoolsFromDB bool, restDial string, wsDial string, waitMilliseconds string) *UniswapScraper {
   277  	var (
   278  		restClient, wsClient *ethclient.Client
   279  		err                  error
   280  		s                    *UniswapScraper
   281  		waitTime             int
   282  	)
   283  
   284  	log.Infof("Init rest and ws client for %s.", exchange.BlockChain.Name)
   285  	restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", restDial))
   286  	if err != nil {
   287  		log.Fatal("init rest client: ", err)
   288  	}
   289  	wsClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_WS", wsDial))
   290  	if err != nil {
   291  		log.Fatal("init ws client: ", err)
   292  	}
   293  
   294  	waitTime, err = strconv.Atoi(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_WAIT_TIME", waitMilliseconds))
   295  	if err != nil {
   296  		log.Error("could not parse wait time: ", err)
   297  		waitTime = 500
   298  	}
   299  
   300  	s = &UniswapScraper{
   301  		WsClient:         wsClient,
   302  		RestClient:       restClient,
   303  		shutdown:         make(chan nothing),
   304  		shutdownDone:     make(chan nothing),
   305  		pairScrapers:     make(map[string]*UniswapPairScraper),
   306  		exchangeName:     exchange.Name,
   307  		error:            nil,
   308  		chanTrades:       make(chan *dia.Trade),
   309  		waitTime:         waitTime,
   310  		listenByAddress:  listenByAddress,
   311  		fetchPoolsFromDB: fetchPoolsFromDB,
   312  	}
   313  	return s
   314  }
   315  
   316  // runs in a goroutine until s is closed
   317  func (s *UniswapScraper) mainLoop() {
   318  
   319  	// Import tokens which appear as base token and we need a quotation for
   320  	var err error
   321  	reverseBasetokens, err = getReverseTokensFromConfig("uniswap/reverse_tokens/" + s.exchangeName + "Basetoken")
   322  	if err != nil {
   323  		log.Error("error getting tokens for which pairs should be reversed: ", err)
   324  	}
   325  	log.Info("reverse basetokens: ", reverseBasetokens)
   326  	reverseQuotetokens, err = getReverseTokensFromConfig("uniswap/reverse_tokens/" + s.exchangeName + "Quotetoken")
   327  	if err != nil {
   328  		log.Error("error getting tokens for which pairs should be reversed: ", err)
   329  	}
   330  	log.Info("reverse quotetokens: ", reverseQuotetokens)
   331  
   332  	// wait for all pairs have added into s.PairScrapers
   333  	time.Sleep(4 * time.Second)
   334  	s.run = true
   335  
   336  	if s.listenByAddress || s.fetchPoolsFromDB {
   337  
   338  		var wg sync.WaitGroup
   339  		count := 0
   340  		for address := range poolMap {
   341  			time.Sleep(time.Duration(s.waitTime) * time.Millisecond)
   342  			wg.Add(1)
   343  			go func(index int, address common.Address, w *sync.WaitGroup) {
   344  				defer w.Done()
   345  				s.ListenToPair(index, address)
   346  			}(count, common.HexToAddress(address), &wg)
   347  			count++
   348  		}
   349  		wg.Wait()
   350  
   351  	} else {
   352  
   353  		numPairs, err := s.getNumPairs()
   354  		if err != nil {
   355  			log.Fatal(err)
   356  		}
   357  		log.Info("Found ", numPairs, " pairs")
   358  		log.Info("Found ", len(s.pairScrapers), " pairScrapers")
   359  
   360  		if len(s.pairScrapers) == 0 {
   361  			s.error = errors.New("uniswap: No pairs to scrap provided")
   362  			log.Error(s.error.Error())
   363  		}
   364  
   365  		var wg sync.WaitGroup
   366  		for i := 0; i < numPairs; i++ {
   367  			time.Sleep(time.Duration(s.waitTime) * time.Millisecond)
   368  			wg.Add(1)
   369  			go func(index int, address common.Address, w *sync.WaitGroup) {
   370  				defer w.Done()
   371  				s.ListenToPair(index, address)
   372  			}(i, common.Address{}, &wg)
   373  		}
   374  		wg.Wait()
   375  
   376  	}
   377  }
   378  
   379  // ListenToPair subscribes to a uniswap pool.
   380  // If @byAddress is true, it listens by pool address, otherwise by index.
   381  func (s *UniswapScraper) ListenToPair(i int, address common.Address) {
   382  	var (
   383  		pair UniswapPair
   384  		err  error
   385  	)
   386  
   387  	if !s.listenByAddress && !s.fetchPoolsFromDB {
   388  		// Get pool info from on-chain. @poolMap is empty.
   389  		pair, err = s.GetPairByID(int64(i))
   390  		if err != nil {
   391  			log.Error("error fetching pair: ", err)
   392  		}
   393  	} else {
   394  		// Relevant pool info is retrieved from @poolMap.
   395  		pair = poolMap[address.Hex()]
   396  	}
   397  
   398  	if len(pair.Token0.Symbol) < 2 || len(pair.Token1.Symbol) < 2 {
   399  		log.Info("skip pair: ", pair.ForeignName)
   400  		return
   401  	}
   402  
   403  	if helpers.AddressIsBlacklisted(pair.Token0.Address) || helpers.AddressIsBlacklisted(pair.Token1.Address) {
   404  		log.Info("skip pair ", pair.ForeignName, ", address is blacklisted")
   405  		return
   406  	}
   407  	if helpers.PoolIsBlacklisted(pair.Address) {
   408  		log.Info("skip blacklisted pool ", pair.Address)
   409  		return
   410  	}
   411  
   412  	log.Info(i, ": add pair scraper for: ", pair.ForeignName, " with address ", pair.Address.Hex())
   413  	sink, err := s.GetSwapsChannel(pair.Address)
   414  	if err != nil {
   415  		log.Error("error fetching swaps channel: ", err)
   416  	}
   417  
   418  	go func() {
   419  		for {
   420  			rawSwap, ok := <-sink
   421  			if ok {
   422  				swap, err := s.normalizeUniswapSwap(*rawSwap, pair)
   423  				if err != nil {
   424  					log.Error("error normalizing swap: ", err)
   425  				}
   426  				price, volume := getSwapData(swap)
   427  				token0 := dia.Asset{
   428  					Address:    pair.Token0.Address.Hex(),
   429  					Symbol:     pair.Token0.Symbol,
   430  					Name:       pair.Token0.Name,
   431  					Decimals:   pair.Token0.Decimals,
   432  					Blockchain: Exchanges[s.exchangeName].BlockChain.Name,
   433  				}
   434  				token1 := dia.Asset{
   435  					Address:    pair.Token1.Address.Hex(),
   436  					Symbol:     pair.Token1.Symbol,
   437  					Name:       pair.Token1.Name,
   438  					Decimals:   pair.Token1.Decimals,
   439  					Blockchain: Exchanges[s.exchangeName].BlockChain.Name,
   440  				}
   441  				t := &dia.Trade{
   442  					Symbol:         pair.Token0.Symbol,
   443  					Pair:           pair.ForeignName,
   444  					Price:          price,
   445  					Volume:         volume,
   446  					BaseToken:      token1,
   447  					QuoteToken:     token0,
   448  					Time:           time.Unix(swap.Timestamp, 0),
   449  					PoolAddress:    rawSwap.Raw.Address.Hex(),
   450  					ForeignTradeID: swap.ID,
   451  					Source:         s.exchangeName,
   452  					VerifiedPair:   true,
   453  				}
   454  
   455  				// TO DO: Refactor approach for reversing pairs.
   456  				switch {
   457  				case utils.Contains(reverseBasetokens, pair.Token1.Address.Hex()):
   458  					// If we need quotation of a base token, reverse pair
   459  					tSwapped, err := dia.SwapTrade(*t)
   460  					if err == nil {
   461  						t = &tSwapped
   462  					}
   463  				case utils.Contains(reverseQuotetokens, pair.Token0.Address.Hex()):
   464  					// If we don't need quotation of quote token, reverse pair.
   465  					tSwapped, err := dia.SwapTrade(*t)
   466  					if err == nil {
   467  						t = &tSwapped
   468  					}
   469  				case token0.Address == "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" && !utils.Contains(&mainBaseAssets, token1.Address):
   470  					// Reverse almost all pairs WETH-XXX ...
   471  					if s.exchangeName == dia.UniswapExchange || s.exchangeName == dia.SushiSwapExchange {
   472  						tSwapped, err := dia.SwapTrade(*t)
   473  						if err == nil {
   474  							t = &tSwapped
   475  						}
   476  					}
   477  				// ...and USDT-XXX on Ethereum, i.e. Uniswap and Sushiswap
   478  				case token0.Address == mainBaseAssets[0] && token0.Blockchain == dia.ETHEREUM:
   479  					tSwapped, err := dia.SwapTrade(*t)
   480  					if err == nil {
   481  						t = &tSwapped
   482  					}
   483  				// Reverse USDC-XXX pairs on Fantom
   484  				case token0.Address == "0x04068DA6C83AFCFA0e13ba15A6696662335D5B75" && token0.Blockchain == dia.FANTOM:
   485  					tSwapped, err := dia.SwapTrade(*t)
   486  					if err == nil {
   487  						t = &tSwapped
   488  					}
   489  				}
   490  				if price > 0 {
   491  					log.Info("tx hash: ", swap.ID)
   492  					log.Infof("Got trade at time %v - symbol: %s, pair: %s, price: %v, volume:%v", t.Time, t.Symbol, t.Pair, t.Price, t.Volume)
   493  					// log.Infof("Base token info --- Symbol: %s - Address: %s - Blockchain: %s ", t.BaseToken.Symbol, t.BaseToken.Address, t.BaseToken.Blockchain)
   494  					// log.Info("----------------")
   495  					s.chanTrades <- t
   496  				}
   497  			}
   498  		}
   499  	}()
   500  }
   501  
   502  // GetSwapsChannel returns a channel for swaps of the pair with address @pairAddress
   503  func (s *UniswapScraper) GetSwapsChannel(pairAddress common.Address) (chan *uniswap.UniswapV2PairSwap, error) {
   504  
   505  	sink := make(chan *uniswap.UniswapV2PairSwap)
   506  	var pairFiltererContract *uniswap.UniswapV2PairFilterer
   507  	pairFiltererContract, err := uniswap.NewUniswapV2PairFilterer(pairAddress, s.WsClient)
   508  	if err != nil {
   509  		log.Fatal(err)
   510  	}
   511  
   512  	_, err = pairFiltererContract.WatchSwap(&bind.WatchOpts{}, sink, []common.Address{}, []common.Address{})
   513  	if err != nil {
   514  		log.Error("error in get swaps channel: ", err)
   515  	}
   516  
   517  	return sink, nil
   518  
   519  }
   520  
   521  // getReverseTokensFromConfig returns a list of addresses from config file.
   522  func getReverseTokensFromConfig(filename string) (*[]string, error) {
   523  
   524  	var reverseTokens []string
   525  
   526  	// Load file and read data
   527  	filehandle := configCollectors.ConfigFileConnectors(filename, ".json")
   528  	jsonFile, err := os.Open(filehandle)
   529  	if err != nil {
   530  		return &[]string{}, err
   531  	}
   532  	defer func() {
   533  		err = jsonFile.Close()
   534  		if err != nil {
   535  			log.Error(err)
   536  		}
   537  	}()
   538  
   539  	byteData, err := ioutil.ReadAll(jsonFile)
   540  	if err != nil {
   541  		return &[]string{}, err
   542  	}
   543  
   544  	// Unmarshal read data
   545  	type lockedAsset struct {
   546  		Address string `json:"Address"`
   547  		Symbol  string `json:"Symbol"`
   548  	}
   549  	type lockedAssetList struct {
   550  		AllAssets []lockedAsset `json:"Tokens"`
   551  	}
   552  	var allAssets lockedAssetList
   553  	err = json.Unmarshal(byteData, &allAssets)
   554  	if err != nil {
   555  		return &[]string{}, err
   556  	}
   557  
   558  	// Extract addresses
   559  	for _, token := range allAssets.AllAssets {
   560  		reverseTokens = append(reverseTokens, token.Address)
   561  	}
   562  
   563  	return &reverseTokens, nil
   564  }
   565  
   566  // getAddressesFromConfig returns a list of Uniswap pool addresses taken from a config file.
   567  func getAddressesFromConfig(filename string) (pairAddresses []common.Address, err error) {
   568  
   569  	// Load file and read data
   570  	filehandle := configCollectors.ConfigFileConnectors(filename, ".json")
   571  	jsonFile, err := os.Open(filehandle)
   572  	if err != nil {
   573  		return
   574  	}
   575  	defer func() {
   576  		err = jsonFile.Close()
   577  		if err != nil {
   578  			log.Error(err)
   579  		}
   580  	}()
   581  
   582  	byteData, err := ioutil.ReadAll(jsonFile)
   583  	if err != nil {
   584  		return
   585  	}
   586  
   587  	// Unmarshal read data
   588  	type scrapedPair struct {
   589  		Address     string `json:"Address"`
   590  		ForeignName string `json:"ForeignName"`
   591  	}
   592  	type scrapedPairList struct {
   593  		AllPairs []scrapedPair `json:"Pools"`
   594  	}
   595  	var allPairs scrapedPairList
   596  	err = json.Unmarshal(byteData, &allPairs)
   597  	if err != nil {
   598  		return
   599  	}
   600  
   601  	// Extract addresses
   602  	for _, token := range allPairs.AllPairs {
   603  		pairAddresses = append(pairAddresses, common.HexToAddress(token.Address))
   604  	}
   605  
   606  	return
   607  }
   608  
   609  // normalizeUniswapSwap takes a swap as returned by the swap contract's channel and converts it to a UniswapSwap type
   610  func (s *UniswapScraper) normalizeUniswapSwap(swap uniswap.UniswapV2PairSwap, pair UniswapPair) (normalizedSwap UniswapSwap, err error) {
   611  
   612  	decimals0 := int(pair.Token0.Decimals)
   613  	decimals1 := int(pair.Token1.Decimals)
   614  	amount0In, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount0In), new(big.Float).SetFloat64(math.Pow10(decimals0))).Float64()
   615  	amount0Out, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount0Out), new(big.Float).SetFloat64(math.Pow10(decimals0))).Float64()
   616  	amount1In, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount1In), new(big.Float).SetFloat64(math.Pow10(decimals1))).Float64()
   617  	amount1Out, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount1Out), new(big.Float).SetFloat64(math.Pow10(decimals1))).Float64()
   618  
   619  	normalizedSwap = UniswapSwap{
   620  		ID:         swap.Raw.TxHash.Hex(),
   621  		Timestamp:  time.Now().Unix(),
   622  		Pair:       pair,
   623  		Amount0In:  amount0In,
   624  		Amount0Out: amount0Out,
   625  		Amount1In:  amount1In,
   626  		Amount1Out: amount1Out,
   627  	}
   628  	return
   629  }
   630  
   631  // pairHealthCheck returns true if the involved tokens are not blacklisted and do not have zero entries
   632  func (up *UniswapPair) pairHealthCheck() bool {
   633  	if up.Token0.Symbol == "" || up.Token1.Symbol == "" || up.Token0.Address.Hex() == "" || up.Token1.Address.Hex() == "" {
   634  		return false
   635  	}
   636  	if helpers.SymbolIsBlackListed(up.Token0.Symbol) || helpers.SymbolIsBlackListed(up.Token1.Symbol) {
   637  		if helpers.SymbolIsBlackListed(up.Token0.Symbol) {
   638  			log.Infof("skip pair %s. symbol %s is blacklisted", up.ForeignName, up.Token0.Symbol)
   639  		} else {
   640  			log.Infof("skip pair %s. symbol %s is blacklisted", up.ForeignName, up.Token1.Symbol)
   641  		}
   642  		return false
   643  	}
   644  	if helpers.AddressIsBlacklisted(up.Token0.Address) || helpers.AddressIsBlacklisted(up.Token1.Address) {
   645  		log.Info("skip pair ", up.ForeignName, ", address is blacklisted")
   646  		return false
   647  	}
   648  	return true
   649  }
   650  
   651  // FetchAvailablePairs returns a list with all available trade pairs as dia.ExchangePair for the pairDiscorvery service
   652  func (s *UniswapScraper) FetchAvailablePairs() (pairs []dia.ExchangePair, err error) {
   653  	time.Sleep(100 * time.Millisecond)
   654  	uniPairs, err := s.GetAllPairs()
   655  	if err != nil {
   656  		return
   657  	}
   658  	for _, pair := range uniPairs {
   659  		if !pair.pairHealthCheck() {
   660  			continue
   661  		}
   662  		quotetoken := dia.Asset{
   663  			Symbol:     pair.Token0.Symbol,
   664  			Name:       pair.Token0.Name,
   665  			Address:    pair.Token0.Address.Hex(),
   666  			Decimals:   pair.Token0.Decimals,
   667  			Blockchain: Exchanges[s.exchangeName].BlockChain.Name,
   668  		}
   669  		basetoken := dia.Asset{
   670  			Symbol:     pair.Token1.Symbol,
   671  			Name:       pair.Token1.Name,
   672  			Address:    pair.Token1.Address.Hex(),
   673  			Decimals:   pair.Token1.Decimals,
   674  			Blockchain: Exchanges[s.exchangeName].BlockChain.Name,
   675  		}
   676  		pairToNormalise := dia.ExchangePair{
   677  			Symbol:         pair.Token0.Symbol,
   678  			ForeignName:    pair.ForeignName,
   679  			Exchange:       "UniswapV2",
   680  			Verified:       true,
   681  			UnderlyingPair: dia.Pair{BaseToken: basetoken, QuoteToken: quotetoken},
   682  		}
   683  		normalizedPair, _ := s.NormalizePair(pairToNormalise)
   684  		pairs = append(pairs, normalizedPair)
   685  	}
   686  
   687  	return
   688  }
   689  
   690  // FillSymbolData is not used by DEX scrapers.
   691  func (s *UniswapScraper) FillSymbolData(symbol string) (dia.Asset, error) {
   692  	return dia.Asset{}, nil
   693  }
   694  
   695  // GetAllPairs is similar to FetchAvailablePairs. But instead of dia.ExchangePairs it returns all pairs as UniswapPairs,
   696  // i.e. including the pair's address
   697  func (s *UniswapScraper) GetAllPairs() ([]UniswapPair, error) {
   698  	time.Sleep(20 * time.Millisecond)
   699  	connection := s.RestClient
   700  	var contract *uniswap.IUniswapV2FactoryCaller
   701  	contract, err := uniswap.NewIUniswapV2FactoryCaller(common.HexToAddress(exchangeFactoryContractAddress), connection)
   702  	if err != nil {
   703  		log.Error(err)
   704  	}
   705  
   706  	numPairs, err := contract.AllPairsLength(&bind.CallOpts{})
   707  	if err != nil {
   708  		return []UniswapPair{}, err
   709  	}
   710  	wg := sync.WaitGroup{}
   711  	defer wg.Wait()
   712  	pairs := make([]UniswapPair, int(numPairs.Int64()))
   713  	for i := 0; i < int(numPairs.Int64()); i++ {
   714  		// Sleep in order not to run into rate limits.
   715  		time.Sleep(time.Duration(s.waitTime) * time.Millisecond)
   716  		wg.Add(1)
   717  		go func(index int) {
   718  			defer wg.Done()
   719  			uniPair, err := s.GetPairByID(int64(index))
   720  			if err != nil {
   721  				log.Error("error retrieving pair by ID: ", err)
   722  				return
   723  			}
   724  			pairs[index] = uniPair
   725  		}(i)
   726  	}
   727  	return pairs, nil
   728  }
   729  
   730  func (up *UniswapScraper) NormalizePair(pair dia.ExchangePair) (dia.ExchangePair, error) {
   731  	return pair, nil
   732  }
   733  
   734  // GetPairByID returns the UniswapPair with the integer id @num
   735  func (s *UniswapScraper) GetPairByID(num int64) (UniswapPair, error) {
   736  	var contract *uniswap.IUniswapV2FactoryCaller
   737  	contract, err := uniswap.NewIUniswapV2FactoryCaller(common.HexToAddress(exchangeFactoryContractAddress), s.RestClient)
   738  	if err != nil {
   739  		log.Error(err)
   740  		return UniswapPair{}, err
   741  	}
   742  	numToken := big.NewInt(num)
   743  	pairAddress, err := contract.AllPairs(&bind.CallOpts{}, numToken)
   744  	if err != nil {
   745  		log.Error(err)
   746  		return UniswapPair{}, err
   747  	}
   748  
   749  	pair, err := s.GetPairByAddress(pairAddress)
   750  	if err != nil {
   751  		log.Error(err)
   752  		return UniswapPair{}, err
   753  	}
   754  	return pair, err
   755  }
   756  
   757  // GetPairByAddress returns the UniswapPair with pair address @pairAddress
   758  func (s *UniswapScraper) GetPairByAddress(pairAddress common.Address) (pair UniswapPair, err error) {
   759  	connection := s.RestClient
   760  	var pairContract *uniswap.IUniswapV2PairCaller
   761  	pairContract, err = uniswap.NewIUniswapV2PairCaller(pairAddress, connection)
   762  	if err != nil {
   763  		log.Error(err)
   764  		return UniswapPair{}, err
   765  	}
   766  
   767  	// Getting tokens from pair ---------------------
   768  	address0, _ := pairContract.Token0(&bind.CallOpts{})
   769  	address1, _ := pairContract.Token1(&bind.CallOpts{})
   770  	var token0Contract *uniswap.IERC20Caller
   771  	var token1Contract *uniswap.IERC20Caller
   772  	token0Contract, err = uniswap.NewIERC20Caller(address0, connection)
   773  	if err != nil {
   774  		log.Error(err)
   775  	}
   776  	token1Contract, err = uniswap.NewIERC20Caller(address1, connection)
   777  	if err != nil {
   778  		log.Error(err)
   779  	}
   780  	symbol0, err := token0Contract.Symbol(&bind.CallOpts{})
   781  	if err != nil {
   782  		log.Error(err)
   783  	}
   784  	symbol1, err := token1Contract.Symbol(&bind.CallOpts{})
   785  	if err != nil {
   786  		log.Error(err)
   787  	}
   788  	decimals0, err := s.GetDecimals(address0)
   789  	if err != nil {
   790  		log.Error(err)
   791  		return UniswapPair{}, err
   792  	}
   793  	decimals1, err := s.GetDecimals(address1)
   794  	if err != nil {
   795  		log.Error(err)
   796  		return UniswapPair{}, err
   797  	}
   798  
   799  	name0, err := s.GetName(address0)
   800  	if err != nil {
   801  		log.Error(err)
   802  		return UniswapPair{}, err
   803  	}
   804  	name1, err := s.GetName(address1)
   805  	if err != nil {
   806  		log.Error(err)
   807  		return UniswapPair{}, err
   808  	}
   809  	token0 := UniswapToken{
   810  		Address:  address0,
   811  		Symbol:   symbol0,
   812  		Decimals: decimals0,
   813  		Name:     name0,
   814  	}
   815  	token1 := UniswapToken{
   816  		Address:  address1,
   817  		Symbol:   symbol1,
   818  		Decimals: decimals1,
   819  		Name:     name1,
   820  	}
   821  	foreignName := symbol0 + "-" + symbol1
   822  	pair = UniswapPair{
   823  		ForeignName: foreignName,
   824  		Address:     pairAddress,
   825  		Token0:      token0,
   826  		Token1:      token1,
   827  	}
   828  	return pair, nil
   829  }
   830  
   831  // GetDecimals returns the decimals of the token with address @tokenAddress
   832  func (s *UniswapScraper) GetDecimals(tokenAddress common.Address) (decimals uint8, err error) {
   833  
   834  	var contract *uniswap.IERC20Caller
   835  	contract, err = uniswap.NewIERC20Caller(tokenAddress, s.RestClient)
   836  	if err != nil {
   837  		log.Error(err)
   838  		return
   839  	}
   840  	decimals, err = contract.Decimals(&bind.CallOpts{})
   841  
   842  	return
   843  }
   844  
   845  func (s *UniswapScraper) GetName(tokenAddress common.Address) (name string, err error) {
   846  
   847  	var contract *uniswap.IERC20Caller
   848  	contract, err = uniswap.NewIERC20Caller(tokenAddress, s.RestClient)
   849  	if err != nil {
   850  		log.Error(err)
   851  		return
   852  	}
   853  	name, err = contract.Name(&bind.CallOpts{})
   854  
   855  	return
   856  }
   857  
   858  // getNumPairs returns the number of available pairs on Uniswap
   859  func (s *UniswapScraper) getNumPairs() (int, error) {
   860  
   861  	var contract *uniswap.IUniswapV2FactoryCaller
   862  	contract, err := uniswap.NewIUniswapV2FactoryCaller(common.HexToAddress(exchangeFactoryContractAddress), s.RestClient)
   863  	if err != nil {
   864  		log.Error(err)
   865  	}
   866  
   867  	// Getting pairs ---------------
   868  	numPairs, err := contract.AllPairsLength(&bind.CallOpts{})
   869  	if err != nil {
   870  		return 0, err
   871  	}
   872  	return int(numPairs.Int64()), err
   873  }
   874  
   875  // getSwapData returns price, volume and sell/buy information of @swap
   876  func getSwapData(swap UniswapSwap) (price float64, volume float64) {
   877  	if swap.Amount0In == float64(0) {
   878  		volume = swap.Amount0Out
   879  		price = swap.Amount1In / swap.Amount0Out
   880  		return
   881  	}
   882  	volume = -swap.Amount0In
   883  	price = swap.Amount1Out / swap.Amount0In
   884  	return
   885  }
   886  
   887  // Close closes any existing API connections, as well as channels of
   888  // PairScrapers from calls to ScrapePair
   889  func (s *UniswapScraper) Close() error {
   890  	if s.closed {
   891  		return errors.New("UniswapScraper: Already closed")
   892  	}
   893  	s.WsClient.Close()
   894  	s.RestClient.Close()
   895  	close(s.shutdown)
   896  	<-s.shutdownDone
   897  	s.errorLock.RLock()
   898  	defer s.errorLock.RUnlock()
   899  	return s.error
   900  }
   901  
   902  // ScrapePair returns a PairScraper that can be used to get trades for a single pair from
   903  // this APIScraper
   904  func (s *UniswapScraper) ScrapePair(pair dia.ExchangePair) (PairScraper, error) {
   905  	s.errorLock.RLock()
   906  	defer s.errorLock.RUnlock()
   907  	if s.error != nil {
   908  		return nil, s.error
   909  	}
   910  	if s.closed {
   911  		return nil, errors.New("UniswapScraper: Call ScrapePair on closed scraper")
   912  	}
   913  	ps := &UniswapPairScraper{
   914  		parent: s,
   915  		pair:   pair,
   916  	}
   917  	s.pairScrapers[pair.ForeignName] = ps
   918  	return ps, nil
   919  }
   920  
   921  // UniswapPairScraper implements PairScraper for Uniswap
   922  type UniswapPairScraper struct {
   923  	parent *UniswapScraper
   924  	pair   dia.ExchangePair
   925  	closed bool
   926  }
   927  
   928  // Close stops listening for trades of the pair associated with s
   929  func (ps *UniswapPairScraper) Close() error {
   930  	ps.closed = true
   931  	return nil
   932  }
   933  
   934  // Channel returns a channel that can be used to receive trades
   935  func (ps *UniswapScraper) Channel() chan *dia.Trade {
   936  	return ps.chanTrades
   937  }
   938  
   939  // Error returns an error when the channel Channel() is closed
   940  // and nil otherwise
   941  func (ps *UniswapPairScraper) Error() error {
   942  	s := ps.parent
   943  	s.errorLock.RLock()
   944  	defer s.errorLock.RUnlock()
   945  	return s.error
   946  }
   947  
   948  // Pair returns the pair this scraper is subscribed to
   949  func (ps *UniswapPairScraper) Pair() dia.ExchangePair {
   950  	return ps.pair
   951  }
   952  
   953  // makeUniPoolMap returns a map with pool addresses as keys and the underlying UniswapPair as values.
   954  // If s.listenByAddress is true, it only loads the corresponding assets from the list.
   955  func (s *UniswapScraper) makeUniPoolMap(liquiThreshold float64, liquidityThresholdUSD float64) (map[string]UniswapPair, error) {
   956  	pm := make(map[string]UniswapPair)
   957  	var (
   958  		pools []dia.Pool
   959  		err   error
   960  	)
   961  
   962  	if s.listenByAddress {
   963  		// Only load pool info for addresses from json file.
   964  		poolAddresses, errAddr := getAddressesFromConfig("uniswap/subscribe_pools/" + s.exchangeName)
   965  		if errAddr != nil {
   966  			log.Error("fetch pool addresses from config file: ", errAddr)
   967  		}
   968  		for _, address := range poolAddresses {
   969  			pool, errPool := s.relDB.GetPoolByAddress(Exchanges[s.exchangeName].BlockChain.Name, address.Hex())
   970  			if errPool != nil {
   971  				log.Fatalf("Get pool with address %s: %v", address.Hex(), errPool)
   972  			}
   973  			pools = append(pools, pool)
   974  		}
   975  	} else if s.fetchPoolsFromDB {
   976  		// Load all pools above liqui threshold.
   977  		pools, err = s.relDB.GetAllPoolsExchange(s.exchangeName, liquiThreshold)
   978  		if err != nil {
   979  			return pm, err
   980  		}
   981  	} else {
   982  		// Pool info will be fetched from on-chain and poolMap is not needed.
   983  		return pm, nil
   984  	}
   985  
   986  	log.Info("Found ", len(pools), " pools.")
   987  	log.Info("make pool map...")
   988  	lowerBoundCount := 0
   989  	for _, pool := range pools {
   990  		if len(pool.Assetvolumes) != 2 {
   991  			log.Warn("not enough assets in pool with address: ", pool.Address)
   992  			continue
   993  		}
   994  
   995  		liquidity, lowerBound := pool.GetPoolLiquidityUSD()
   996  		// Discard pool if complete USD liquidity is below threshold.
   997  		if !lowerBound && liquidity < liquidityThresholdUSD {
   998  			continue
   999  		}
  1000  		if lowerBound {
  1001  			lowerBoundCount++
  1002  		}
  1003  
  1004  		up := UniswapPair{
  1005  			Address: common.HexToAddress(pool.Address),
  1006  		}
  1007  		if pool.Assetvolumes[0].Index == 0 {
  1008  			up.Token0 = asset2UniAsset(pool.Assetvolumes[0].Asset)
  1009  			up.Token1 = asset2UniAsset(pool.Assetvolumes[1].Asset)
  1010  		} else {
  1011  			up.Token0 = asset2UniAsset(pool.Assetvolumes[1].Asset)
  1012  			up.Token1 = asset2UniAsset(pool.Assetvolumes[0].Asset)
  1013  		}
  1014  		up.ForeignName = up.Token0.Symbol + "-" + up.Token1.Symbol
  1015  		pm[pool.Address] = up
  1016  	}
  1017  
  1018  	log.Infof("found %v subscribable pools.", len(pm))
  1019  	log.Infof("%v pools with lowerBound=true.", lowerBoundCount)
  1020  	return pm, err
  1021  }