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

     1  package scrapers
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"math"
     7  	"math/big"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/uniswap"
    14  
    15  	"github.com/diadata-org/diadata/pkg/dia"
    16  	models "github.com/diadata-org/diadata/pkg/model"
    17  	"github.com/diadata-org/diadata/pkg/utils"
    18  	"github.com/ethereum/go-ethereum"
    19  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    20  	"github.com/ethereum/go-ethereum/common"
    21  	"github.com/ethereum/go-ethereum/ethclient"
    22  )
    23  
    24  type UniswapHistoryScraper struct {
    25  	WsClient   *ethclient.Client
    26  	RestClient *ethclient.Client
    27  	// signaling channels for session initialization and finishing
    28  	//initDone     chan nothing
    29  	run          bool
    30  	shutdown     chan nothing
    31  	shutdownDone chan nothing
    32  	// error handling; to read error or closed, first acquire read lock
    33  	// only cleanup method should hold write lock
    34  	errorLock sync.RWMutex
    35  	error     error
    36  	closed    bool
    37  	// used to keep track of trading pairs that we subscribed to
    38  	pairScrapers  map[string]*UniswapHistoryPairScraper
    39  	exchangeName  string
    40  	chanTrades    chan *dia.Trade
    41  	waitTime      int
    42  	genesisBlock  uint64
    43  	finalBlock    uint64
    44  	pairmap       map[common.Address]UniswapPair
    45  	pairAddresses []common.Address
    46  	//db            *models.RelDB
    47  	// If true, only pairs given in config file are scraped. Default is false.
    48  	listenByAddress bool
    49  }
    50  
    51  const (
    52  	// genesisBlockUniswap = uint64(10019990)
    53  	// genesisBlockUniswap            = uint64(10520000)
    54  	// genesisBlockUniswap            = uint64(12575772)
    55  	filterQueryBlockNums           = 50
    56  	uniswapHistoryWaitMilliseconds = "1000"
    57  )
    58  
    59  // NewUniswapScraper returns a new UniswapScraper for the given pair
    60  func NewUniswapHistoryScraper(exchange dia.Exchange, scrape bool, relDB *models.RelDB) *UniswapHistoryScraper {
    61  	log.Info("NewUniswapHistoryScraper: ", exchange.Name)
    62  	var s *UniswapHistoryScraper
    63  	var listenByAddress bool
    64  	exchangeFactoryContractAddress = exchange.Contract
    65  
    66  	switch exchange.Name {
    67  	case dia.UniswapExchange:
    68  		listenByAddress = true
    69  		s = makeUniswapHistoryScraper(exchange, listenByAddress, restDialEth, wsDialEth, uniswapHistoryWaitMilliseconds)
    70  	case dia.SushiSwapExchange:
    71  		listenByAddress = false
    72  		s = makeUniswapHistoryScraper(exchange, listenByAddress, restDialEth, wsDialEth, sushiswapWaitMilliseconds)
    73  	case dia.PanCakeSwap:
    74  		listenByAddress = true
    75  		s = makeUniswapHistoryScraper(exchange, listenByAddress, restDialBSC, wsDialBSC, pancakeswapWaitMilliseconds)
    76  	case dia.DfynNetwork:
    77  		listenByAddress = false
    78  		s = makeUniswapHistoryScraper(exchange, listenByAddress, restDialPolygon, wsDialPolygon, dfynWaitMilliseconds)
    79  	}
    80  
    81  	if scrape {
    82  		go s.mainLoop()
    83  	}
    84  	return s
    85  }
    86  
    87  // makeUniswapScraper returns a uniswap scraper as used in NewUniswapScraper.
    88  func makeUniswapHistoryScraper(exchange dia.Exchange, listenByAddress bool, restDial string, wsDial string, waitMilliseconds string) *UniswapHistoryScraper {
    89  	var restClient, wsClient *ethclient.Client
    90  	var err error
    91  	var s *UniswapHistoryScraper
    92  
    93  	log.Infof("Init rest and ws client for %s.", exchange.BlockChain.Name)
    94  	restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", restDial))
    95  	if err != nil {
    96  		log.Fatal("init rest client: ", err)
    97  	}
    98  	wsClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_WS", wsDial))
    99  	if err != nil {
   100  		log.Fatal("init ws client: ", err)
   101  	}
   102  
   103  	var waitTime int
   104  	waitTimeString := utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_WAIT_TIME", waitMilliseconds)
   105  	waitTime, err = strconv.Atoi(waitTimeString)
   106  	if err != nil {
   107  		log.Error("could not parse wait time: ", err)
   108  		waitTime = 500
   109  	}
   110  
   111  	startblockstring := utils.Getenv("FIRST_BLOCK", "")
   112  	startblock, err := strconv.ParseUint(startblockstring, 10, 64)
   113  	if err != nil {
   114  		log.Fatal("parse startblock: ", err)
   115  	}
   116  	finalblockstring := utils.Getenv("FINAL_BLOCK", "")
   117  	finalblock, err := strconv.ParseUint(finalblockstring, 10, 64)
   118  	if err != nil {
   119  		log.Fatal("parse final block: ", err)
   120  	}
   121  
   122  	s = &UniswapHistoryScraper{
   123  		WsClient:        wsClient,
   124  		RestClient:      restClient,
   125  		shutdown:        make(chan nothing),
   126  		shutdownDone:    make(chan nothing),
   127  		pairScrapers:    make(map[string]*UniswapHistoryPairScraper),
   128  		exchangeName:    exchange.Name,
   129  		error:           nil,
   130  		chanTrades:      make(chan *dia.Trade),
   131  		waitTime:        waitTime,
   132  		listenByAddress: listenByAddress,
   133  		genesisBlock:    uint64(startblock),
   134  		finalBlock:      uint64(finalblock),
   135  	}
   136  	return s
   137  }
   138  
   139  func (s *UniswapHistoryScraper) loadPairMap() {
   140  	var maps []map[common.Address]UniswapPair
   141  
   142  	if s.listenByAddress {
   143  
   144  		// Collect all pair addresses from json file.
   145  		pairAddresses, err := getAddressesFromConfig("uniswap/subscribe_pools/" + s.exchangeName + "History")
   146  		if err != nil {
   147  			log.Error("fetch pool addresses from config file: ", err)
   148  		}
   149  		numPairs := len(pairAddresses)
   150  		log.Infof("listening to %d pools: %v", numPairs, pairAddresses)
   151  		for _, pairAddress := range pairAddresses {
   152  			uniPair, err := s.GetPairByAddress(pairAddress)
   153  			if err != nil {
   154  				log.Errorf("get pair with address %s: %v", pairAddress.Hex(), err)
   155  			}
   156  			auxmap := make(map[common.Address]UniswapPair)
   157  			auxmap[pairAddress] = uniPair
   158  			maps = append(maps, auxmap)
   159  		}
   160  
   161  	} else {
   162  		numPairs, err := s.getNumPairs()
   163  		if err != nil {
   164  			log.Fatal(err)
   165  		}
   166  		// numPairs := 1
   167  		batchSize := 1000
   168  
   169  		log.Infof("load all %d pairs: ", numPairs)
   170  
   171  		var wg sync.WaitGroup
   172  		for k := 0; k < numPairs/batchSize; k++ {
   173  			time.Sleep(8 * time.Second)
   174  			for i := batchSize * k; i < batchSize*(k+1); i++ {
   175  				wg.Add(1)
   176  				auxmap := make(map[common.Address]UniswapPair)
   177  				go func(index int, w *sync.WaitGroup) {
   178  					defer w.Done()
   179  					pair, err := s.GetPairByID(int64(index))
   180  					if err != nil {
   181  						log.Error(err)
   182  					}
   183  					auxmap[pair.Address] = pair
   184  				}(i, &wg)
   185  				maps = append(maps, auxmap)
   186  			}
   187  			wg.Wait()
   188  		}
   189  		for i := numPairs - numPairs%batchSize; i < numPairs; i++ {
   190  			wg.Add(1)
   191  			auxmap := make(map[common.Address]UniswapPair)
   192  			go func(index int, w *sync.WaitGroup) {
   193  				defer w.Done()
   194  				pair, err := s.GetPairByID(int64(index))
   195  				if err != nil {
   196  					log.Error(err)
   197  				}
   198  				auxmap[pair.Address] = pair
   199  			}(i, &wg)
   200  			maps = append(maps, auxmap)
   201  		}
   202  		wg.Wait()
   203  
   204  		log.Info("len: ", len(maps))
   205  	}
   206  
   207  	pairmap := make(map[common.Address]UniswapPair)
   208  	for _, m := range maps {
   209  		for i, j := range m {
   210  			pairmap[i] = j
   211  		}
   212  	}
   213  
   214  	log.Info("len: ", len(pairmap))
   215  	s.pairmap = pairmap
   216  }
   217  
   218  // runs in a goroutine until s is closed
   219  func (s *UniswapHistoryScraper) mainLoop() {
   220  
   221  	// Import tokens which appear as base token and we need a quotation for
   222  	var err error
   223  	reverseBasetokens, err = getReverseTokensFromConfig("uniswap/reverse_tokens/" + s.exchangeName + "Basetoken")
   224  	if err != nil {
   225  		log.Error("error getting tokens for which pairs should be reversed: ", err)
   226  	}
   227  	log.Info("reverse pairs with following base tokens: ", reverseBasetokens)
   228  
   229  	// wait for all pairs have added into s.PairScrapers
   230  	time.Sleep(4 * time.Second)
   231  	s.run = true
   232  
   233  	// load all pairs into and from pair map.
   234  	s.loadPairMap()
   235  	var addresses []common.Address
   236  	for k := range s.pairmap {
   237  		addresses = append(addresses, k)
   238  	}
   239  	s.pairAddresses = addresses
   240  
   241  	if len(addresses) == 0 {
   242  		s.error = errors.New("uniswap: No pairs to scrape provided")
   243  		log.Error(s.error.Error())
   244  	} else {
   245  		log.Infof("%d pairs loaded.", len(addresses))
   246  	}
   247  
   248  	// latestBlock, err := s.RestClient.BlockByNumber(context.Background(), nil)
   249  	// if err != nil {
   250  	// 	log.Error("get current block number: ", err)
   251  	// }
   252  	finalBlock := s.finalBlock
   253  	startblock := s.genesisBlock
   254  	endblock := startblock + uint64(filterQueryBlockNums)
   255  
   256  	for startblock < finalBlock {
   257  		err := s.fetchSwaps(startblock, endblock)
   258  		if err != nil {
   259  			if strings.Contains(err.Error(), "EOF") {
   260  				endblock = startblock + (endblock-startblock)/2
   261  				time.Sleep(2 * time.Second)
   262  				continue
   263  			}
   264  			log.Error("get filter logs: ", err)
   265  		}
   266  		startblock = endblock
   267  		endblock = startblock + filterQueryBlockNums
   268  		time.Sleep(time.Duration(s.waitTime) * time.Millisecond)
   269  	}
   270  
   271  	time.Sleep(20 * 24 * time.Hour)
   272  
   273  	// ---------------------------------------------------------------------------
   274  	// Concurrent block scraping
   275  	// ---------------------------------------------------------------------------
   276  
   277  	// a := s.genesisBlock
   278  	// b := latestBlock.NumberU64()
   279  	// numSubblocks := 1
   280  	// startblocks := []uint64{}
   281  	// finalBlocks := []uint64{}
   282  	// for k := 0; k < numSubblocks; k++ {
   283  	// 	startblocks = append(startblocks, a+uint64(k)*(b-a)/uint64(numSubblocks))
   284  	// 	finalBlocks = append(finalBlocks, a+uint64(k+1)*(b-a)/uint64(numSubblocks))
   285  	// }
   286  
   287  	// var wg sync.WaitGroup
   288  	// for i := 0; i < len(startblocks); i++ {
   289  	// 	startblock := startblocks[i]
   290  	// 	endblock := startblocks[i] + uint64(filterQueryBlockNums)
   291  
   292  	// 	wg.Add(1)
   293  	// 	go func(startblock, endblock uint64, index int, w *sync.WaitGroup) {
   294  	// 		defer w.Done()
   295  	// 		for startblock < finalBlocks[index] {
   296  	// 			err := s.fetchSwaps(startblock, endblock)
   297  	// 			if err != nil {
   298  	// 				if strings.Contains(err.Error(), "EOF") {
   299  	// 					endblock = startblock + (endblock-startblock)/2
   300  	// 					time.Sleep(2 * time.Second)
   301  	// 					continue
   302  	// 				}
   303  	// 				log.Error("get filter logs: ", err)
   304  	// 			}
   305  	// 			startblock = endblock
   306  	// 			endblock = startblock + filterQueryBlockNums
   307  	// 		}
   308  	// 	}(startblock, endblock, i, &wg)
   309  	// }
   310  	// wg.Wait()
   311  }
   312  
   313  func (s *UniswapHistoryScraper) fetchSwaps(startblock uint64, endblock uint64) error {
   314  	log.Infof("get swaps from block %d to block %d.", startblock, endblock)
   315  	hashSwap := common.HexToHash("0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822")
   316  	topics := make([][]common.Hash, 1)
   317  	topics[0] = append(topics[0], hashSwap)
   318  
   319  	config := ethereum.FilterQuery{
   320  		Addresses: s.pairAddresses,
   321  		Topics:    topics,
   322  		FromBlock: new(big.Int).SetUint64(startblock),
   323  		ToBlock:   new(big.Int).SetUint64(endblock),
   324  	}
   325  
   326  	t := time.Now()
   327  	logs, err := s.RestClient.FilterLogs(context.Background(), config)
   328  	if err != nil {
   329  		return err
   330  	}
   331  	log.Info("time passed for filter logs: ", time.Since(t))
   332  	for _, logg := range logs {
   333  
   334  		pairFilterer, err := uniswap.NewUniswapV2PairFilterer(common.Address{}, s.RestClient)
   335  		if err != nil {
   336  			log.Error(err)
   337  		}
   338  
   339  		blockdata, err := s.RestClient.BlockByNumber(context.Background(), big.NewInt(int64(logg.BlockNumber)))
   340  		if err != nil {
   341  			log.Info("get block by number: ", err)
   342  		}
   343  
   344  		// blockdata, err := ethhelper.GetBlockData(int64(logg.BlockNumber), s.db, s.RestClient)
   345  		// if err != nil {
   346  		// 	return err
   347  		// }
   348  
   349  		swap, err := pairFilterer.ParseSwap(logg)
   350  		if err != nil {
   351  			log.Error(err)
   352  		}
   353  		swp := s.normalizeUniswapSwapHistory(*swap, logg.Address)
   354  
   355  		price, volume := getSwapData(swp)
   356  		token0 := dia.Asset{
   357  			Address:    swp.Pair.Token0.Address.Hex(),
   358  			Symbol:     swp.Pair.Token0.Symbol,
   359  			Name:       swp.Pair.Token0.Name,
   360  			Decimals:   swp.Pair.Token0.Decimals,
   361  			Blockchain: Exchanges[s.exchangeName].BlockChain.Name,
   362  		}
   363  		token1 := dia.Asset{
   364  			Address:    swp.Pair.Token1.Address.Hex(),
   365  			Symbol:     swp.Pair.Token1.Symbol,
   366  			Name:       swp.Pair.Token1.Name,
   367  			Decimals:   swp.Pair.Token1.Decimals,
   368  			Blockchain: Exchanges[s.exchangeName].BlockChain.Name,
   369  		}
   370  
   371  		timestamp := time.Unix(int64(blockdata.Time()), 0)
   372  
   373  		// var timestamp time.Time
   374  		// switch blockdata.Data["Time"].(type) {
   375  		// case float64:
   376  		// 	timestamp = time.Unix(int64(blockdata.Data["Time"].(float64)), 0)
   377  		// case uint64:
   378  		// 	timestamp = time.Unix(int64(blockdata.Data["Time"].(uint64)), 0)
   379  		// }
   380  		t := &dia.Trade{
   381  			Symbol:     swp.Pair.Token0.Symbol,
   382  			Pair:       swp.Pair.ForeignName,
   383  			Price:      price,
   384  			Volume:     volume,
   385  			BaseToken:  token1,
   386  			QuoteToken: token0,
   387  			Time:       timestamp,
   388  			// Time: time.Now(),
   389  			// Time:           time.Unix(int64(blockdata.Time()), 0),
   390  			ForeignTradeID: logg.TxHash.Hex(),
   391  			Source:         s.exchangeName,
   392  			VerifiedPair:   true,
   393  		}
   394  		// If we need quotation of a base token, reverse pair
   395  		if utils.Contains(reverseBasetokens, swp.Pair.Token1.Address.Hex()) {
   396  			tSwapped, err := dia.SwapTrade(*t)
   397  			if err == nil {
   398  				t = &tSwapped
   399  			}
   400  		}
   401  		if price > 0 {
   402  			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)
   403  			s.chanTrades <- t
   404  		}
   405  		if price == 0 {
   406  			log.Info("Got zero trade: ", t)
   407  		}
   408  
   409  	}
   410  
   411  	log.Info("number of swaps: ", len(logs))
   412  	return nil
   413  
   414  }
   415  
   416  // normalizeUniswapSwap takes a swap as returned by the swap contract's channel and converts it to a UniswapSwap type
   417  func (s *UniswapHistoryScraper) normalizeUniswapSwapHistory(swap uniswap.UniswapV2PairSwap, pairAddress common.Address) (normalizedSwap UniswapSwap) {
   418  
   419  	pair := s.pairmap[pairAddress]
   420  
   421  	decimals0 := int(pair.Token0.Decimals)
   422  	decimals1 := int(pair.Token1.Decimals)
   423  	amount0In, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount0In), new(big.Float).SetFloat64(math.Pow10(decimals0))).Float64()
   424  	amount0Out, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount0Out), new(big.Float).SetFloat64(math.Pow10(decimals0))).Float64()
   425  	amount1In, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount1In), new(big.Float).SetFloat64(math.Pow10(decimals1))).Float64()
   426  	amount1Out, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount1Out), new(big.Float).SetFloat64(math.Pow10(decimals1))).Float64()
   427  
   428  	normalizedSwap = UniswapSwap{
   429  		ID:         swap.Raw.TxHash.Hex(),
   430  		Pair:       pair,
   431  		Amount0In:  amount0In,
   432  		Amount0Out: amount0Out,
   433  		Amount1In:  amount1In,
   434  		Amount1Out: amount1Out,
   435  	}
   436  	return
   437  }
   438  
   439  // FetchAvailablePairs returns a list with all available trade pairs as dia.ExchangePair for the pairDiscorvery service
   440  func (s *UniswapHistoryScraper) FetchAvailablePairs() (pairs []dia.ExchangePair, err error) {
   441  	time.Sleep(100 * time.Millisecond)
   442  	uniPairs, err := s.GetAllPairs()
   443  	if err != nil {
   444  		return
   445  	}
   446  	for _, pair := range uniPairs {
   447  		if !pair.pairHealthCheck() {
   448  			continue
   449  		}
   450  		quotetoken := dia.Asset{
   451  			Symbol:     pair.Token0.Symbol,
   452  			Name:       pair.Token0.Name,
   453  			Address:    pair.Token0.Address.Hex(),
   454  			Decimals:   pair.Token0.Decimals,
   455  			Blockchain: dia.ETHEREUM,
   456  		}
   457  		basetoken := dia.Asset{
   458  			Symbol:     pair.Token1.Symbol,
   459  			Name:       pair.Token1.Name,
   460  			Address:    pair.Token1.Address.Hex(),
   461  			Decimals:   pair.Token1.Decimals,
   462  			Blockchain: dia.ETHEREUM,
   463  		}
   464  		pairToNormalise := dia.ExchangePair{
   465  			Symbol:         pair.Token0.Symbol,
   466  			ForeignName:    pair.ForeignName,
   467  			Exchange:       "UniswapV2",
   468  			Verified:       true,
   469  			UnderlyingPair: dia.Pair{BaseToken: basetoken, QuoteToken: quotetoken},
   470  		}
   471  		normalizedPair, _ := s.NormalizePair(pairToNormalise)
   472  		pairs = append(pairs, normalizedPair)
   473  	}
   474  
   475  	return
   476  }
   477  
   478  // FillSymbolData is not used by DEX scrapers.
   479  func (s *UniswapHistoryScraper) FillSymbolData(symbol string) (dia.Asset, error) {
   480  	return dia.Asset{}, nil
   481  }
   482  
   483  // GetAllPairs is similar to FetchAvailablePairs. But instead of dia.ExchangePairs it returns all pairs as UniswapPairs,
   484  // i.e. including the pair's address
   485  func (s *UniswapHistoryScraper) GetAllPairs() ([]UniswapPair, error) {
   486  	time.Sleep(20 * time.Millisecond)
   487  	connection := s.RestClient
   488  	var contract *uniswap.IUniswapV2FactoryCaller
   489  	contract, err := uniswap.NewIUniswapV2FactoryCaller(common.HexToAddress(exchangeFactoryContractAddress), connection)
   490  	if err != nil {
   491  		log.Error(err)
   492  	}
   493  
   494  	numPairs, err := contract.AllPairsLength(&bind.CallOpts{})
   495  	if err != nil {
   496  		return []UniswapPair{}, err
   497  	}
   498  	wg := sync.WaitGroup{}
   499  	defer wg.Wait()
   500  	pairs := make([]UniswapPair, int(numPairs.Int64()))
   501  	for i := 0; i < int(numPairs.Int64()); i++ {
   502  		wg.Add(1)
   503  		go func(index int) {
   504  			defer wg.Done()
   505  			uniPair, err := s.GetPairByID(int64(index))
   506  			if err != nil {
   507  				log.Error("error retrieving pair by ID: ", err)
   508  				return
   509  			}
   510  			pairs[index] = uniPair
   511  		}(i)
   512  	}
   513  	return pairs, nil
   514  }
   515  
   516  func (up *UniswapHistoryScraper) NormalizePair(pair dia.ExchangePair) (dia.ExchangePair, error) {
   517  	return pair, nil
   518  }
   519  
   520  // GetPairByID returns the UniswapPair with the integer id @num
   521  func (s *UniswapHistoryScraper) GetPairByID(num int64) (UniswapPair, error) {
   522  	log.Info("Get pair ID: ", num)
   523  	var contract *uniswap.IUniswapV2FactoryCaller
   524  	contract, err := uniswap.NewIUniswapV2FactoryCaller(common.HexToAddress(exchangeFactoryContractAddress), s.RestClient)
   525  	if err != nil {
   526  		log.Error(err)
   527  		return UniswapPair{}, err
   528  	}
   529  	numToken := big.NewInt(num)
   530  	pairAddress, err := contract.AllPairs(&bind.CallOpts{}, numToken)
   531  	if err != nil {
   532  		log.Error(err)
   533  		return UniswapPair{}, err
   534  	}
   535  
   536  	pair, err := s.GetPairByAddress(pairAddress)
   537  	if err != nil {
   538  		log.Error(err)
   539  		return UniswapPair{}, err
   540  	}
   541  	return pair, err
   542  }
   543  
   544  // GetPairByAddress returns the UniswapPair with pair address @pairAddress
   545  func (s *UniswapHistoryScraper) GetPairByAddress(pairAddress common.Address) (pair UniswapPair, err error) {
   546  	connection := s.RestClient
   547  	var pairContract *uniswap.IUniswapV2PairCaller
   548  	pairContract, err = uniswap.NewIUniswapV2PairCaller(pairAddress, connection)
   549  	if err != nil {
   550  		log.Error(err)
   551  		return UniswapPair{}, err
   552  	}
   553  
   554  	// Getting tokens from pair ---------------------
   555  	address0, _ := pairContract.Token0(&bind.CallOpts{})
   556  	address1, _ := pairContract.Token1(&bind.CallOpts{})
   557  	var token0Contract *uniswap.IERC20Caller
   558  	var token1Contract *uniswap.IERC20Caller
   559  	token0Contract, err = uniswap.NewIERC20Caller(address0, connection)
   560  	if err != nil {
   561  		log.Error(err)
   562  	}
   563  	token1Contract, err = uniswap.NewIERC20Caller(address1, connection)
   564  	if err != nil {
   565  		log.Error(err)
   566  	}
   567  	symbol0, err := token0Contract.Symbol(&bind.CallOpts{})
   568  	if err != nil {
   569  		log.Error(err)
   570  	}
   571  	symbol1, err := token1Contract.Symbol(&bind.CallOpts{})
   572  	if err != nil {
   573  		log.Error(err)
   574  	}
   575  	decimals0, err := s.GetDecimals(address0)
   576  	if err != nil {
   577  		log.Error(err)
   578  		return UniswapPair{}, err
   579  	}
   580  	decimals1, err := s.GetDecimals(address1)
   581  	if err != nil {
   582  		log.Error(err)
   583  		return UniswapPair{}, err
   584  	}
   585  
   586  	name0, err := s.GetName(address0)
   587  	if err != nil {
   588  		log.Error(err)
   589  		return UniswapPair{}, err
   590  	}
   591  	name1, err := s.GetName(address1)
   592  	if err != nil {
   593  		log.Error(err)
   594  		return UniswapPair{}, err
   595  	}
   596  	token0 := UniswapToken{
   597  		Address:  address0,
   598  		Symbol:   symbol0,
   599  		Decimals: decimals0,
   600  		Name:     name0,
   601  	}
   602  	token1 := UniswapToken{
   603  		Address:  address1,
   604  		Symbol:   symbol1,
   605  		Decimals: decimals1,
   606  		Name:     name1,
   607  	}
   608  	foreignName := symbol0 + "-" + symbol1
   609  	pair = UniswapPair{
   610  		ForeignName: foreignName,
   611  		Address:     pairAddress,
   612  		Token0:      token0,
   613  		Token1:      token1,
   614  	}
   615  	return pair, nil
   616  }
   617  
   618  // GetDecimals returns the decimals of the token with address @tokenAddress
   619  func (s *UniswapHistoryScraper) GetDecimals(tokenAddress common.Address) (decimals uint8, err error) {
   620  
   621  	var contract *uniswap.IERC20Caller
   622  	contract, err = uniswap.NewIERC20Caller(tokenAddress, s.RestClient)
   623  	if err != nil {
   624  		log.Error(err)
   625  		return
   626  	}
   627  	decimals, err = contract.Decimals(&bind.CallOpts{})
   628  
   629  	return
   630  }
   631  
   632  func (s *UniswapHistoryScraper) GetName(tokenAddress common.Address) (name string, err error) {
   633  
   634  	var contract *uniswap.IERC20Caller
   635  	contract, err = uniswap.NewIERC20Caller(tokenAddress, s.RestClient)
   636  	if err != nil {
   637  		log.Error(err)
   638  		return
   639  	}
   640  	name, err = contract.Name(&bind.CallOpts{})
   641  
   642  	return
   643  }
   644  
   645  // getNumPairs returns the number of available pairs on Uniswap
   646  func (s *UniswapHistoryScraper) getNumPairs() (int, error) {
   647  
   648  	var contract *uniswap.IUniswapV2FactoryCaller
   649  	contract, err := uniswap.NewIUniswapV2FactoryCaller(common.HexToAddress(exchangeFactoryContractAddress), s.RestClient)
   650  	if err != nil {
   651  		log.Error(err)
   652  	}
   653  
   654  	// Getting pairs ---------------
   655  	numPairs, err := contract.AllPairsLength(&bind.CallOpts{})
   656  	return int(numPairs.Int64()), err
   657  }
   658  
   659  // Close closes any existing API connections, as well as channels of
   660  // PairScrapers from calls to ScrapePair
   661  func (s *UniswapHistoryScraper) Close() error {
   662  	if s.closed {
   663  		return errors.New("UniswapScraper: Already closed")
   664  	}
   665  	s.WsClient.Close()
   666  	s.RestClient.Close()
   667  	close(s.shutdown)
   668  	<-s.shutdownDone
   669  	s.errorLock.RLock()
   670  	defer s.errorLock.RUnlock()
   671  	return s.error
   672  }
   673  
   674  // ScrapePair returns a PairScraper that can be used to get trades for a single pair from
   675  // this APIScraper
   676  func (s *UniswapHistoryScraper) ScrapePair(pair dia.ExchangePair) (PairScraper, error) {
   677  	s.errorLock.RLock()
   678  	defer s.errorLock.RUnlock()
   679  	if s.error != nil {
   680  		return nil, s.error
   681  	}
   682  	if s.closed {
   683  		return nil, errors.New("UniswapScraper: Call ScrapePair on closed scraper")
   684  	}
   685  	ps := &UniswapHistoryPairScraper{
   686  		parent: s,
   687  		pair:   pair,
   688  	}
   689  	s.pairScrapers[pair.ForeignName] = ps
   690  	return ps, nil
   691  }
   692  
   693  // UniswapPairScraper implements PairScraper for Uniswap
   694  type UniswapHistoryPairScraper struct {
   695  	parent *UniswapHistoryScraper
   696  	pair   dia.ExchangePair
   697  	closed bool
   698  }
   699  
   700  // Close stops listening for trades of the pair associated with s
   701  func (ps *UniswapHistoryPairScraper) Close() error {
   702  	ps.closed = true
   703  	return nil
   704  }
   705  
   706  // Channel returns a channel that can be used to receive trades
   707  func (ps *UniswapHistoryScraper) Channel() chan *dia.Trade {
   708  	return ps.chanTrades
   709  }
   710  
   711  // Error returns an error when the channel Channel() is closed
   712  // and nil otherwise
   713  func (ps *UniswapHistoryPairScraper) Error() error {
   714  	s := ps.parent
   715  	s.errorLock.RLock()
   716  	defer s.errorLock.RUnlock()
   717  	return s.error
   718  }
   719  
   720  // Pair returns the pair this scraper is subscribed to
   721  func (ps *UniswapHistoryPairScraper) Pair() dia.ExchangePair {
   722  	return ps.pair
   723  }