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 }