github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/exchange-scrapers/UniswapV3Scraper.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  	PancakeswapV3Pair "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/pancakeswapv3"
    14  	uniswapcontract "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/uniswap"
    15  	uniswapcontractv3 "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/uniswapv3"
    16  	UniswapV3Pair "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/uniswapv3/uniswapV3Pair"
    17  	models "github.com/diadata-org/diadata/pkg/model"
    18  
    19  	"github.com/diadata-org/diadata/pkg/dia/helpers"
    20  	"github.com/diadata-org/diadata/pkg/utils"
    21  
    22  	"github.com/diadata-org/diadata/pkg/dia"
    23  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/ethclient"
    26  )
    27  
    28  type UniswapV3Swap struct {
    29  	ID        string
    30  	Timestamp int64
    31  	Pair      UniswapPair
    32  	Amount0   float64
    33  	Amount1   float64
    34  }
    35  
    36  type UniswapV3Scraper struct {
    37  	WsClient   *ethclient.Client
    38  	RestClient *ethclient.Client
    39  	relDB      *models.RelDB
    40  	// signaling channels for session initialization and finishing
    41  	//initDone     chan nothing
    42  	run          bool
    43  	shutdown     chan nothing
    44  	shutdownDone chan nothing
    45  	// error handling; to read error or closed, first acquire read lock
    46  	// only cleanup method should hold write lock
    47  	errorLock sync.RWMutex
    48  	error     error
    49  	closed    bool
    50  	// used to keep track of trading pairs that we subscribed to
    51  	pairScrapers map[string]*UniswapPairV3Scraper
    52  	pairRecieved chan *UniswapPair
    53  
    54  	exchangeName           string
    55  	startBlock             uint64
    56  	waitTime               int
    57  	listenByAddress        bool
    58  	chanTrades             chan *dia.Trade
    59  	factoryContractAddress common.Address
    60  }
    61  
    62  var (
    63  	fullPoolsUniswapV3 *[]string
    64  )
    65  
    66  // NewUniswapV3Scraper returns a new UniswapV3Scraper
    67  func NewUniswapV3Scraper(exchange dia.Exchange, scrape bool, relDB *models.RelDB) *UniswapV3Scraper {
    68  	log.Info("NewUniswapScraper ", exchange.Name)
    69  	log.Info("NewUniswapScraper Address ", exchange.Contract)
    70  
    71  	var (
    72  		s               *UniswapV3Scraper
    73  		listenByAddress bool
    74  		err             error
    75  	)
    76  
    77  	listenByAddress, err = strconv.ParseBool(utils.Getenv("LISTEN_BY_ADDRESS", ""))
    78  	if err != nil {
    79  		log.Fatal("parse LISTEN_BY_ADDRESS: ", err)
    80  	}
    81  
    82  	switch exchange.Name {
    83  	case dia.UniswapExchangeV3:
    84  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(12369621))
    85  	case dia.UniswapExchangeV3Polygon:
    86  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(22757913))
    87  	case dia.UniswapExchangeV3Arbitrum:
    88  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(165))
    89  	case dia.UniswapExchangeV3Base:
    90  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(1371680))
    91  	case dia.UniswapExchangeV3Celo:
    92  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(13916355))
    93  	case dia.PanCakeSwapExchangeV3:
    94  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(26956207))
    95  	case dia.CamelotExchangeV3:
    96  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(101163738))
    97  	case dia.ThenaV3Exchange:
    98  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(26030310))
    99  	case dia.PearlfiExchangeTestnet:
   100  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(2890))
   101  	case dia.PearlfiExchange:
   102  		// TO DO: add init block number
   103  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(0))
   104  	case dia.RamsesV2Exchange:
   105  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "200", uint64(90593047))
   106  	case dia.NileV2Exchange:
   107  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "2000", uint64(1768866))
   108  	case dia.AerodromeSlipstreamExchange:
   109  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "2000", uint64(0))
   110  	case dia.VelodromeSlipstreamExchange:
   111  		s = makeUniswapV3Scraper(exchange, listenByAddress, "", "", "2000", uint64(0))
   112  	}
   113  
   114  	s.relDB = relDB
   115  
   116  	// Only include pools with (minimum) liquidity bigger than given env var.
   117  	liquidityThreshold, err := strconv.ParseFloat(utils.Getenv("LIQUIDITY_THRESHOLD", "0"), 64)
   118  	if err != nil {
   119  		liquidityThreshold = float64(0)
   120  		log.Warnf("parse liquidity threshold:  %v. Set to default %v", err, liquidityThreshold)
   121  	}
   122  	// Only include pools with (minimum) liquidity USD value bigger than given env var.
   123  	liquidityThresholdUSD, err := strconv.ParseFloat(utils.Getenv("LIQUIDITY_THRESHOLD_USD", "0"), 64)
   124  	if err != nil {
   125  		liquidityThresholdUSD = float64(0)
   126  		log.Warnf("parse liquidity threshold:  %v. Set to default %v", err, liquidityThresholdUSD)
   127  	}
   128  
   129  	poolMap, err = s.makeUniV3PoolMap(liquidityThreshold, liquidityThresholdUSD)
   130  	if err != nil {
   131  		log.Fatal("build poolMap: ", err)
   132  	}
   133  
   134  	pingNodeInterval, err := strconv.ParseInt(utils.Getenv("PING_SERVER", "0"), 10, 64)
   135  	if err != nil {
   136  		log.Error("parse PING_SERVER: ", err)
   137  	}
   138  	if pingNodeInterval > 0 {
   139  		s.pingNode(pingNodeInterval)
   140  	}
   141  
   142  	if scrape {
   143  		go s.mainLoop()
   144  	}
   145  	return s
   146  }
   147  
   148  // makeUniswapV3Scraper returns a uniswap scraper as used in NewUniswapV3Scraper.
   149  func makeUniswapV3Scraper(exchange dia.Exchange, listenByAddress bool, restDial string, wsDial string, waitMilliseconds string, startBlock uint64) *UniswapV3Scraper {
   150  	var restClient, wsClient *ethclient.Client
   151  	var err error
   152  	var s *UniswapV3Scraper
   153  
   154  	log.Infof("Init rest and ws client for %s.", exchange.BlockChain.Name)
   155  	restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", restDial))
   156  	if err != nil {
   157  		log.Fatal("init rest client: ", err)
   158  	}
   159  	wsClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_WS", wsDial))
   160  	if err != nil {
   161  		log.Fatal("init ws client: ", err)
   162  	}
   163  
   164  	var waitTime int
   165  	waitTimeString := utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_WAIT_TIME", waitMilliseconds)
   166  	waitTime, err = strconv.Atoi(waitTimeString)
   167  	if err != nil {
   168  		log.Error("could not parse wait time: ", err)
   169  		waitTime = 500
   170  	}
   171  
   172  	s = &UniswapV3Scraper{
   173  		WsClient:               wsClient,
   174  		RestClient:             restClient,
   175  		shutdown:               make(chan nothing),
   176  		shutdownDone:           make(chan nothing),
   177  		pairScrapers:           make(map[string]*UniswapPairV3Scraper),
   178  		exchangeName:           exchange.Name,
   179  		pairRecieved:           make(chan *UniswapPair),
   180  		error:                  nil,
   181  		chanTrades:             make(chan *dia.Trade),
   182  		waitTime:               waitTime,
   183  		listenByAddress:        listenByAddress,
   184  		startBlock:             startBlock,
   185  		factoryContractAddress: common.HexToAddress(exchange.Contract),
   186  	}
   187  	return s
   188  }
   189  
   190  // runs in a goroutine until s is closed
   191  func (s *UniswapV3Scraper) mainLoop() {
   192  
   193  	var err error
   194  	reverseBasetokens, err = getReverseTokensFromConfig("uniswapv3/reverse_tokens/" + s.exchangeName + "Basetoken")
   195  	if err != nil {
   196  		log.Error("error getting basetokens for which pairs should be reversed: ", err)
   197  	}
   198  	log.Infof("reverse the following basetokens on %s: %v", s.exchangeName, reverseBasetokens)
   199  	reverseQuotetokens, err = getReverseTokensFromConfig("uniswapv3/reverse_tokens/" + s.exchangeName + "Quotetoken")
   200  	if err != nil {
   201  		log.Error("error getting quotetokens for which pairs should be reversed: ", err)
   202  	}
   203  	log.Infof("reverse the following quotetokens on %s: %v", s.exchangeName, reverseQuotetokens)
   204  	fullPoolsUniswapV3, err = getReverseTokensFromConfig("uniswapv3/fullPools/" + s.exchangeName + "FullPools")
   205  	if err != nil {
   206  		log.Error("error getting fullPools for which pairs should be reversed: ", err)
   207  	}
   208  	log.Infof("Take into account both directions of a trade on the following pools: %v", fullPoolsUniswapV3)
   209  
   210  	time.Sleep(4 * time.Second)
   211  	s.run = true
   212  
   213  	go func() {
   214  		pools := s.feedPoolsToSubscriptions()
   215  		log.Info("Found ", len(pools), " pairs")
   216  		log.Info("Found ", len(s.pairScrapers), " pairScrapers")
   217  	}()
   218  
   219  	if len(s.pairScrapers) == 0 {
   220  		s.error = errors.New("uniswap: No pairs to scrape provided")
   221  		log.Error(s.error.Error())
   222  	}
   223  	count := 0
   224  	for {
   225  		pool := <-s.pairRecieved
   226  		log.Infoln("Subscribing for pair", pool)
   227  
   228  		if len(pool.Token0.Symbol) < 2 || len(pool.Token1.Symbol) < 2 {
   229  			log.Info("skip pair: ", pool.ForeignName)
   230  			continue
   231  		}
   232  		if helpers.AddressIsBlacklisted(pool.Token0.Address) || helpers.AddressIsBlacklisted(pool.Token1.Address) {
   233  			log.Info("skip pair ", pool.ForeignName, ", address is blacklisted")
   234  			continue
   235  		}
   236  		if helpers.PoolIsBlacklisted(pool.Address) {
   237  			log.Info("skip blacklisted pool ", pool.Address)
   238  			continue
   239  		}
   240  		log.Infof("%v found pair scraper for: %s with address %s", count, pool.ForeignName, pool.Address.Hex())
   241  		count++
   242  
   243  		if s.exchangeName == dia.PanCakeSwapExchangeV3 {
   244  
   245  			sink, err := s.GetPancakeSwapsChannel(pool.Address)
   246  			if err != nil {
   247  				log.Error("error fetching swaps channel: ", err)
   248  			}
   249  
   250  			go func() {
   251  				for {
   252  					rawSwap, ok := <-sink
   253  					if ok {
   254  						swap := s.normalizeUniswapSwap(*rawSwap)
   255  						s.sendTrade(swap, pool)
   256  					}
   257  				}
   258  			}()
   259  
   260  		} else {
   261  
   262  			sink, err := s.GetSwapsChannel(pool.Address)
   263  			if err != nil {
   264  				log.Error("error fetching swaps channel: ", err)
   265  			}
   266  
   267  			go func() {
   268  				for {
   269  					rawSwap, ok := <-sink
   270  					if ok {
   271  						swap := s.normalizeUniswapSwap(*rawSwap)
   272  						s.sendTrade(swap, pool)
   273  					}
   274  				}
   275  			}()
   276  
   277  		}
   278  
   279  	}
   280  }
   281  
   282  func (s *UniswapV3Scraper) sendTrade(swap UniswapV3Swap, pool *UniswapPair) {
   283  	price, volume := s.getSwapData(swap)
   284  	token0 := dia.Asset{
   285  		Address:    pool.Token0.Address.Hex(),
   286  		Symbol:     pool.Token0.Symbol,
   287  		Name:       pool.Token0.Name,
   288  		Decimals:   pool.Token0.Decimals,
   289  		Blockchain: Exchanges[s.exchangeName].BlockChain.Name,
   290  	}
   291  	token1 := dia.Asset{
   292  		Address:    pool.Token1.Address.Hex(),
   293  		Symbol:     pool.Token1.Symbol,
   294  		Name:       pool.Token1.Name,
   295  		Decimals:   pool.Token1.Decimals,
   296  		Blockchain: Exchanges[s.exchangeName].BlockChain.Name,
   297  	}
   298  
   299  	t := &dia.Trade{
   300  		Symbol:         pool.Token0.Symbol,
   301  		Pair:           pool.ForeignName,
   302  		Price:          price,
   303  		Volume:         volume,
   304  		BaseToken:      token1,
   305  		QuoteToken:     token0,
   306  		Time:           time.Unix(swap.Timestamp, 0),
   307  		ForeignTradeID: swap.ID,
   308  		PoolAddress:    pool.Address.Hex(),
   309  		Source:         s.exchangeName,
   310  		VerifiedPair:   true,
   311  	}
   312  
   313  	switch {
   314  	case utils.Contains(reverseBasetokens, pool.Token1.Address.Hex()):
   315  		// If we need quotation of a base token, reverse pair
   316  		tSwapped, err := dia.SwapTrade(*t)
   317  		if err == nil {
   318  			t = &tSwapped
   319  		}
   320  	case utils.Contains(reverseQuotetokens, pool.Token0.Address.Hex()):
   321  		// If we need quotation of a base token, reverse pair
   322  		tSwapped, err := dia.SwapTrade(*t)
   323  		if err == nil {
   324  			t = &tSwapped
   325  		}
   326  	}
   327  
   328  	if utils.Contains(fullPoolsUniswapV3, pool.Address.Hex()) {
   329  		tSwapped, err := dia.SwapTrade(*t)
   330  		if err == nil {
   331  			if tSwapped.Price > 0 {
   332  				s.chanTrades <- &tSwapped
   333  			}
   334  		}
   335  	}
   336  
   337  	if price > 0 {
   338  		log.Infof("Got trade on pool %s: %v", pool.Address.Hex(), t)
   339  		s.chanTrades <- t
   340  	}
   341  }
   342  
   343  // GetSwapsChannel returns a channel for swaps of the pair with address @pairAddress
   344  func (s *UniswapV3Scraper) GetSwapsChannel(pairAddress common.Address) (chan *UniswapV3Pair.UniswapV3PairSwap, error) {
   345  	sink := make(chan *UniswapV3Pair.UniswapV3PairSwap)
   346  	var pairFiltererContract *UniswapV3Pair.UniswapV3PairFilterer
   347  
   348  	pairFiltererContract, err := UniswapV3Pair.NewUniswapV3PairFilterer(pairAddress, s.WsClient)
   349  	if err != nil {
   350  		log.Fatal(err)
   351  	}
   352  
   353  	_, err = pairFiltererContract.WatchSwap(&bind.WatchOpts{}, sink, []common.Address{}, []common.Address{})
   354  	if err != nil {
   355  		log.Error("error in get swaps channel: ", err)
   356  	}
   357  
   358  	return sink, nil
   359  
   360  }
   361  
   362  func (s *UniswapV3Scraper) GetPancakeSwapsChannel(pairAddress common.Address) (chan *PancakeswapV3Pair.Pancakev3pairSwap, error) {
   363  	sink := make(chan *PancakeswapV3Pair.Pancakev3pairSwap)
   364  	var pairFiltererContract *PancakeswapV3Pair.Pancakev3pairFilterer
   365  
   366  	pairFiltererContract, err := PancakeswapV3Pair.NewPancakev3pairFilterer(pairAddress, s.WsClient)
   367  	if err != nil {
   368  		log.Fatal(err)
   369  	}
   370  
   371  	_, err = pairFiltererContract.WatchSwap(&bind.WatchOpts{}, sink, []common.Address{}, []common.Address{})
   372  	if err != nil {
   373  		log.Error("error in get swaps channel: ", err)
   374  	}
   375  	return sink, nil
   376  }
   377  
   378  func (s *UniswapV3Scraper) getSwapData(swap UniswapV3Swap) (price float64, volume float64) {
   379  	volume = swap.Amount0
   380  	price = math.Abs(swap.Amount1 / swap.Amount0)
   381  	return
   382  }
   383  
   384  // normalizeUniswapSwap takes a swap as returned by the swap contract's channel and converts it to a UniswapSwap type
   385  func (s *UniswapV3Scraper) normalizeUniswapSwap(swapI interface{}) (normalizedSwap UniswapV3Swap) {
   386  	switch swap := swapI.(type) {
   387  	case UniswapV3Pair.UniswapV3PairSwap:
   388  		pair := poolMap[swap.Raw.Address.Hex()]
   389  		decimals0 := int(pair.Token0.Decimals)
   390  		decimals1 := int(pair.Token1.Decimals)
   391  		amount0, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount0), new(big.Float).SetFloat64(math.Pow10(decimals0))).Float64()
   392  		amount1, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount1), new(big.Float).SetFloat64(math.Pow10(decimals1))).Float64()
   393  
   394  		normalizedSwap = UniswapV3Swap{
   395  			ID:        swap.Raw.TxHash.Hex(),
   396  			Timestamp: time.Now().Unix(),
   397  			Pair:      pair,
   398  			Amount0:   amount0,
   399  			Amount1:   amount1,
   400  		}
   401  	case PancakeswapV3Pair.Pancakev3pairSwap:
   402  		pair := poolMap[swap.Raw.Address.Hex()]
   403  		decimals0 := int(pair.Token0.Decimals)
   404  		decimals1 := int(pair.Token1.Decimals)
   405  		amount0, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount0), new(big.Float).SetFloat64(math.Pow10(decimals0))).Float64()
   406  		amount1, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(swap.Amount1), new(big.Float).SetFloat64(math.Pow10(decimals1))).Float64()
   407  
   408  		normalizedSwap = UniswapV3Swap{
   409  			ID:        swap.Raw.TxHash.Hex(),
   410  			Timestamp: time.Now().Unix(),
   411  			Pair:      pair,
   412  			Amount0:   amount0,
   413  			Amount1:   amount1,
   414  		}
   415  	}
   416  
   417  	return
   418  }
   419  
   420  // FetchAvailablePairs returns a list with all available trade pairs as dia.Pair for the pairDiscorvery service
   421  func (s *UniswapV3Scraper) FetchAvailablePairs() (pairs []dia.ExchangePair, err error) {
   422  	return
   423  }
   424  
   425  func (s *UniswapV3Scraper) FillSymbolData(symbol string) (dia.Asset, error) {
   426  	return dia.Asset{Symbol: symbol}, nil
   427  }
   428  
   429  func (s *UniswapV3Scraper) NormalizePair(pair dia.ExchangePair) (dia.ExchangePair, error) {
   430  	return pair, nil
   431  }
   432  
   433  // GetPairByID returns the UniswapPair with the integer id @num
   434  func (s *UniswapV3Scraper) GetPairData(poolEvent *uniswapcontractv3.UniswapV3PoolCreated) (UniswapPair, error) {
   435  	pair, err := s.GetPairByTokenAddress(poolEvent.Token0, poolEvent.Token1, poolEvent.Pool)
   436  	if err != nil {
   437  		log.Error("GetPairData", err)
   438  		return UniswapPair{}, err
   439  	}
   440  	return pair, err
   441  }
   442  
   443  func (s *UniswapV3Scraper) GetPairByTokenAddress(address0 common.Address, address1 common.Address, pairAddress common.Address) (pair UniswapPair, err error) {
   444  	connection := s.RestClient
   445  
   446  	var token0Contract *uniswapcontract.IERC20Caller
   447  	var token1Contract *uniswapcontract.IERC20Caller
   448  	token0Contract, err = uniswapcontract.NewIERC20Caller(address0, connection)
   449  	if err != nil {
   450  		log.Error(err)
   451  	}
   452  	token1Contract, err = uniswapcontract.NewIERC20Caller(address1, connection)
   453  	if err != nil {
   454  		log.Error(err)
   455  	}
   456  	symbol0, err := token0Contract.Symbol(&bind.CallOpts{})
   457  	if err != nil {
   458  		log.Error(err)
   459  	}
   460  	symbol1, err := token1Contract.Symbol(&bind.CallOpts{})
   461  	if err != nil {
   462  		log.Error(err)
   463  	}
   464  	decimals0, err := s.GetDecimals(address0)
   465  	if err != nil {
   466  		log.Error(err)
   467  		return UniswapPair{}, err
   468  	}
   469  	decimals1, err := s.GetDecimals(address1)
   470  	if err != nil {
   471  		log.Error(err)
   472  		return UniswapPair{}, err
   473  	}
   474  	token0 := UniswapToken{
   475  		Address:  address0,
   476  		Symbol:   symbol0,
   477  		Decimals: decimals0,
   478  	}
   479  	token1 := UniswapToken{
   480  		Address:  address1,
   481  		Symbol:   symbol1,
   482  		Decimals: decimals1,
   483  	}
   484  	foreignName := symbol0 + "-" + symbol1
   485  	pair = UniswapPair{
   486  		ForeignName: foreignName,
   487  		Address:     pairAddress,
   488  		Token0:      token0,
   489  		Token1:      token1,
   490  	}
   491  	return pair, nil
   492  }
   493  
   494  // GetDecimals returns the decimals of the token with address @tokenAddress
   495  func (s *UniswapV3Scraper) GetDecimals(tokenAddress common.Address) (decimals uint8, err error) {
   496  
   497  	var contract *uniswapcontract.IERC20Caller
   498  	contract, err = uniswapcontract.NewIERC20Caller(tokenAddress, s.RestClient)
   499  	if err != nil {
   500  		log.Error(err)
   501  		return
   502  	}
   503  	decimals, err = contract.Decimals(&bind.CallOpts{})
   504  
   505  	return
   506  }
   507  
   508  func (s *UniswapV3Scraper) feedPoolsToSubscriptions() (pairs []UniswapPair) {
   509  	for i := range poolMap {
   510  		up := poolMap[i]
   511  		pairs = append(pairs, up)
   512  		s.pairRecieved <- &up
   513  	}
   514  	return
   515  }
   516  
   517  func asset2UniAsset(asset dia.Asset) UniswapToken {
   518  	return UniswapToken{
   519  		Address:  common.HexToAddress(asset.Address),
   520  		Decimals: asset.Decimals,
   521  		Symbol:   asset.Symbol,
   522  		Name:     asset.Name,
   523  	}
   524  }
   525  
   526  // Close closes any existing API connections, as well as channels of
   527  // PairScrapers from calls to ScrapePair
   528  func (s *UniswapV3Scraper) Close() error {
   529  
   530  	if s.closed {
   531  		return errors.New("UniswapScraper: Already closed")
   532  	}
   533  	s.WsClient.Close()
   534  	s.RestClient.Close()
   535  	close(s.shutdown)
   536  	<-s.shutdownDone
   537  	s.errorLock.RLock()
   538  	defer s.errorLock.RUnlock()
   539  	return s.error
   540  }
   541  
   542  // ScrapePair returns a PairScraper that can be used to get trades for a single pair from
   543  // this APIScraper
   544  func (s *UniswapV3Scraper) ScrapePair(pair dia.ExchangePair) (PairScraper, error) {
   545  
   546  	s.errorLock.RLock()
   547  	defer s.errorLock.RUnlock()
   548  	if s.error != nil {
   549  		return nil, s.error
   550  	}
   551  	if s.closed {
   552  		return nil, errors.New("UniswapScraper: Call ScrapePair on closed scraper")
   553  	}
   554  	ps := &UniswapPairV3Scraper{
   555  		parent: s,
   556  		pair:   pair,
   557  	}
   558  	s.pairScrapers[pair.ForeignName] = ps
   559  	return ps, nil
   560  }
   561  
   562  func (s *UniswapV3Scraper) pingNode(pingNodeInterval int64) {
   563  	ticker := time.NewTicker(time.Duration(pingNodeInterval) * time.Second)
   564  	go func() {
   565  		for range ticker.C {
   566  			blockNumber, err := s.WsClient.BlockNumber(context.Background())
   567  			if err != nil {
   568  				log.Error("pingNode: ", err)
   569  			} else {
   570  				log.Infof("%v -- blockNumber: %d", time.Now(), blockNumber)
   571  			}
   572  		}
   573  	}()
   574  }
   575  
   576  // UniswapPairScraper implements PairScraper for Uniswap
   577  type UniswapPairV3Scraper struct {
   578  	parent *UniswapV3Scraper
   579  	pair   dia.ExchangePair
   580  	//closed bool
   581  }
   582  
   583  // Close stops listening for trades of the pair associated with s
   584  func (ps *UniswapPairV3Scraper) Close() error {
   585  	return nil
   586  }
   587  
   588  // Channel returns a channel that can be used to receive trades
   589  func (s *UniswapV3Scraper) Channel() chan *dia.Trade {
   590  	return s.chanTrades
   591  }
   592  
   593  // Error returns an error when the channel Channel() is closed
   594  // and nil otherwise
   595  func (ps *UniswapPairV3Scraper) Error() error {
   596  	s := ps.parent
   597  	s.errorLock.RLock()
   598  	defer s.errorLock.RUnlock()
   599  	return s.error
   600  }
   601  
   602  // Pair returns the pair this scraper is subscribed to
   603  func (ps *UniswapPairV3Scraper) Pair() dia.ExchangePair {
   604  	return ps.pair
   605  }
   606  
   607  // makeUniPoolMap returns a map with pool addresses as keys and the underlying UniswapPair as values.
   608  func (s *UniswapV3Scraper) makeUniV3PoolMap(liquiThreshold float64, liquidityThresholdUSD float64) (map[string]UniswapPair, error) {
   609  	pm := make(map[string]UniswapPair)
   610  	var pools []dia.Pool
   611  	var err error
   612  
   613  	if s.listenByAddress {
   614  		// Only load pool info for addresses from json file.
   615  		poolAddresses, errAddr := getAddressesFromConfig("uniswapv3/subscribe_pools/" + s.exchangeName)
   616  		if errAddr != nil {
   617  			log.Error("fetch pool addresses from config file: ", errAddr)
   618  		}
   619  		for _, address := range poolAddresses {
   620  			pool, errPool := s.relDB.GetPoolByAddress(Exchanges[s.exchangeName].BlockChain.Name, address.Hex())
   621  			if errPool != nil {
   622  				log.Fatalf("Get pool with address %s: %v", address.Hex(), errPool)
   623  			}
   624  			pools = append(pools, pool)
   625  		}
   626  	} else {
   627  		// Load all pools above liqui threshold.
   628  		pools, err = s.relDB.GetAllPoolsExchange(s.exchangeName, liquiThreshold)
   629  		if err != nil {
   630  			return pm, err
   631  		}
   632  	}
   633  
   634  	log.Info("Found ", len(pools), " pools.")
   635  	log.Info("make pool map...")
   636  	lowerBoundCount := 0
   637  	for _, pool := range pools {
   638  		if len(pool.Assetvolumes) != 2 {
   639  			continue
   640  		}
   641  		liquidity, lowerBound := pool.GetPoolLiquidityUSD()
   642  		// Discard pool if complete USD liquidity is below threshold.
   643  		if !lowerBound && liquidity < liquidityThresholdUSD {
   644  			continue
   645  		}
   646  		if lowerBound {
   647  			lowerBoundCount++
   648  		}
   649  
   650  		up := UniswapPair{
   651  			Address: common.HexToAddress(pool.Address),
   652  		}
   653  		if pool.Assetvolumes[0].Index == 0 {
   654  			up.Token0 = asset2UniAsset(pool.Assetvolumes[0].Asset)
   655  			up.Token1 = asset2UniAsset(pool.Assetvolumes[1].Asset)
   656  		} else {
   657  			up.Token0 = asset2UniAsset(pool.Assetvolumes[1].Asset)
   658  			up.Token1 = asset2UniAsset(pool.Assetvolumes[0].Asset)
   659  		}
   660  		up.ForeignName = up.Token0.Symbol + "-" + up.Token1.Symbol
   661  		pm[pool.Address] = up
   662  	}
   663  
   664  	log.Infof("found %v subscribable pools.", len(pm))
   665  	log.Infof("%v pools with lowerBound=true.", lowerBoundCount)
   666  	return pm, err
   667  }