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 }