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 }