github.com/diadata-org/diadata@v1.4.593/internal/pkg/tradesBlockService/tradesBlockService.go (about) 1 package tradesBlockService 2 3 import ( 4 "errors" 5 "math" 6 "sort" 7 "strconv" 8 "sync" 9 "time" 10 11 "github.com/cnf/structhash" 12 "github.com/diadata-org/diadata/pkg/dia" 13 scrapers "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers" 14 models "github.com/diadata-org/diadata/pkg/model" 15 "github.com/diadata-org/diadata/pkg/utils" 16 "github.com/ethereum/go-ethereum/common" 17 "github.com/sirupsen/logrus" 18 ) 19 20 type nothing struct{} 21 22 func init() { 23 log = logrus.New() 24 var err error 25 batchTimeSeconds, err = strconv.Atoi(utils.Getenv("BATCH_TIME_SECONDS", "30")) 26 if err != nil { 27 log.Error("parse BATCH_TIME_SECONDS: ", err) 28 } 29 volumeThreshold, err = strconv.ParseFloat(utils.Getenv("VOLUME_THRESHOLD", "100000"), 64) 30 if err != nil { 31 log.Error("parse env var VOLUME_THRESHOLD: ", err) 32 } 33 blueChipThreshold, err = strconv.ParseFloat(utils.Getenv("BLUECHIP_THRESHOLD", "50000000"), 64) 34 if err != nil { 35 log.Error("parse env var BLUECHIP_THRESHOLD: ", err) 36 } 37 smallX, err = strconv.ParseFloat(utils.Getenv("SMALL_X", "10"), 64) 38 if err != nil { 39 log.Error("parse env var SMALL_X: ", err) 40 } 41 normalX, err = strconv.ParseFloat(utils.Getenv("NORMAL_X", "10"), 64) 42 if err != nil { 43 log.Error("parse env var NORMAL_X: ", err) 44 } 45 tradeVolumeThresholdExponent, err := strconv.ParseFloat(utils.Getenv("TRADE_VOLUME_THRESHOLD_EXPONENT", ""), 64) 46 if err != nil { 47 log.Error("Parse TRADE_VOLUME_THRESHOLD_EXPONENT: ", err) 48 } 49 tradeVolumeThreshold = math.Pow(10, -tradeVolumeThresholdExponent) 50 tradeVolumeThresholdUSDExponent, err := strconv.ParseFloat(utils.Getenv("TRADE_VOLUME_THRESHOLD_USD_EXPONENT", ""), 64) 51 if err != nil { 52 log.Error("Parse TRADE_VOLUME_THRESHOLD_USD_EXPONENT: ", err) 53 } 54 tradeVolumeThresholdUSD = math.Pow(10, -tradeVolumeThresholdUSDExponent) 55 } 56 57 var ( 58 stablecoins = map[string]interface{}{ 59 "USDT": "", 60 "TUSD": "", 61 // "DAI": "", 62 // "USDC": "", 63 "PAX": "", 64 "BUSD": "", 65 } 66 67 // These should be loaded from postgres once we have a list. 68 USDT = dia.Asset{Address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", Blockchain: dia.ETHEREUM} 69 USDT_BNB_CHAIN = dia.Asset{Address: "0x55d398326f99059fF775485246999027B3197955", Blockchain: dia.BINANCESMARTCHAIN} 70 USDC = dia.Asset{Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", Blockchain: dia.ETHEREUM} 71 BUSD = dia.Asset{Address: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", Blockchain: dia.BINANCESMARTCHAIN} 72 DAI = dia.Asset{Address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", Blockchain: dia.ETHEREUM} 73 TUSD = dia.Asset{Address: "0x0000000000085d4780B73119b644AE5ecd22b376", Blockchain: dia.ETHEREUM} 74 stablecoinAssets = map[string]interface{}{ 75 USDT.Identifier(): "", 76 USDC.Identifier(): "", 77 DAI.Identifier(): "", 78 TUSD.Identifier(): "", 79 } 80 81 tol = float64(0.04) 82 log *logrus.Logger 83 batchTimeSeconds int 84 tradeVolumeThreshold float64 85 tradeVolumeThresholdUSD float64 86 volumeUpdateSeconds = 60 * 10 87 volumeThreshold float64 88 blueChipThreshold float64 89 smallX float64 90 normalX float64 91 checkTradesDuplicate = make(map[string]struct{}) 92 ) 93 94 type TradesBlockService struct { 95 shutdown chan nothing 96 shutdownDone chan nothing 97 chanTrades chan *dia.Trade 98 chanTradesBlock chan *dia.TradesBlock 99 errorLock sync.RWMutex 100 error error 101 closed bool 102 started bool 103 BlockDuration int64 104 currentBlock *dia.TradesBlock 105 priceCache map[string]float64 106 volumeCache map[string]float64 107 datastore models.Datastore 108 relDB models.RelDatastore 109 historical bool 110 writeMeasurement string 111 batchTicker *time.Ticker 112 volumeTicker *time.Ticker 113 } 114 115 func NewTradesBlockService(datastore models.Datastore, relDB models.RelDatastore, blockDuration int64, historical bool) *TradesBlockService { 116 s := &TradesBlockService{ 117 shutdown: make(chan nothing), 118 shutdownDone: make(chan nothing), 119 chanTrades: make(chan *dia.Trade), 120 chanTradesBlock: make(chan *dia.TradesBlock), 121 error: nil, 122 started: false, 123 currentBlock: nil, 124 BlockDuration: blockDuration, 125 priceCache: make(map[string]float64), 126 volumeCache: make(map[string]float64), 127 datastore: datastore, 128 relDB: relDB, 129 historical: historical, 130 batchTicker: time.NewTicker(time.Duration(batchTimeSeconds) * time.Second), 131 volumeTicker: time.NewTicker(time.Duration(volumeUpdateSeconds) * time.Second), 132 } 133 if historical { 134 s.writeMeasurement = utils.Getenv("INFLUX_MEASUREMENT_WRITE", "tradesTmp") 135 } 136 log.Info("write measurement: ", s.writeMeasurement) 137 log.Info("historical: ", s.historical) 138 log.Info("batch ticker time: ", batchTimeSeconds) 139 log.Info("volume threshold: ", volumeThreshold) 140 log.Info("bluechip threshold: ", blueChipThreshold) 141 log.Info("smallX: ", smallX) 142 log.Info("normalX: ", normalX) 143 log.Info("tradeVolumeThreshold: ", tradeVolumeThreshold) 144 log.Info("tradeVolumeThresholdUSD: ", tradeVolumeThresholdUSD) 145 146 s.volumeCache = s.loadVolumes() 147 log.Info("...done loading volumes.") 148 149 go s.mainLoop() 150 go s.loadVolumesLoop() 151 return s 152 } 153 154 // runs in a goroutine until s is closed 155 func (s *TradesBlockService) mainLoop() { 156 var ( 157 acceptCountDEX int 158 // acceptCountSwapDEX int 159 totalCount int 160 logTicker = *time.NewTicker(120 * time.Second) 161 ) 162 for { 163 select { 164 case <-s.shutdown: 165 log.Println("TradesBlockService shutting down") 166 s.cleanup(nil) 167 return 168 case t := <-s.chanTrades: 169 170 // Only take into account original order for CEX trade. 171 if scrapers.Exchanges[(*t).Source].Centralized { 172 s.process(*t) 173 } else { 174 175 // tSwapped, err := dia.SwapTrade(*t) 176 // if err != nil { 177 // log.Error("swap trade: ", err) 178 // } 179 180 // Collect booleans for stats. 181 tradeOk := s.checkTrade(*t) 182 // swapppedTradeOk := s.checkTrade(tSwapped) 183 if tradeOk { 184 acceptCountDEX++ 185 } 186 // if swapppedTradeOk { 187 // acceptCountSwapDEX++ 188 // } 189 // if tradeOk || swapppedTradeOk { 190 // acceptCount++ 191 // } 192 totalCount++ 193 194 // Process (possibly) both trades. 195 if tradeOk { 196 s.process(*t) 197 } 198 // s.process(tSwapped) 199 } 200 201 case <-s.batchTicker.C: 202 err := s.datastore.Flush() 203 if err != nil { 204 log.Error("flush influx batch: ", err) 205 } 206 case <-logTicker.C: 207 log.Info("accepted trades DEX: ", acceptCountDEX) 208 // log.Info("accepted swapped trades DEX: ", acceptCountSwapDEX) 209 log.Info("discarded trades: ", totalCount-acceptCountDEX) 210 acceptCountDEX = 0 211 // acceptCountSwapDEX = 0 212 totalCount = 0 213 } 214 } 215 } 216 217 // checkTrade determines whether a (DEX-)trade should be taken into account for price determination. 218 func (s *TradesBlockService) checkTrade(t dia.Trade) bool { 219 220 // Discard (very) low volume trade. 221 if math.Abs(t.Volume) < tradeVolumeThreshold { 222 log.Info("low volume trade: ", t) 223 return false 224 } 225 226 // Replace basetoken with bridged asset for pricing if necessary. 227 // The basetoken in the stored trade will remain unchanged. 228 basetoken := buildBridge(t) 229 230 // Allow trade where basetoken is stablecoin. 231 if _, ok := stablecoinAssets[basetoken.Identifier()]; ok { 232 return true 233 } 234 235 // Only take into account stablecoin trade if basetoken is stable coin as well. 236 if _, ok := stablecoinAssets[t.QuoteToken.Identifier()]; ok { 237 if _, ok := stablecoinAssets[basetoken.Identifier()]; !ok { 238 return false 239 } 240 } 241 242 if baseVolume, ok := s.volumeCache[basetoken.Identifier()]; ok { 243 if baseVolume > blueChipThreshold { 244 return true 245 } 246 if quoteVolume, ok := s.volumeCache[t.QuoteToken.Identifier()]; ok { 247 if baseVolume < volumeThreshold { 248 // For small volume basetoken, quotetoken must be a small volume asset too. 249 return quoteVolume < smallX*baseVolume 250 } 251 // Discard trade if base volume is too small compared to quote volume. 252 return quoteVolume < normalX*baseVolume 253 } 254 // Base asset has enough volume or quotetoken has no volume yet. 255 return true 256 } 257 return false 258 } 259 260 func (s *TradesBlockService) process(t dia.Trade) { 261 262 var ( 263 verifiedTrade bool 264 tradeOk bool 265 ) 266 267 if scrapers.Exchanges[t.Source].Centralized { 268 tradeOk = true 269 } else { 270 tradeOk = s.checkTrade(t) 271 } 272 273 // Price estimation can only be done for verified pairs. 274 // Trades with unverified pairs are still saved, but not sent to the filtersBlockService. 275 if t.VerifiedPair && tradeOk { 276 if t.BaseToken.Address == "840" && t.BaseToken.Blockchain == dia.FIAT { 277 // All prices are measured in US-Dollar, so just price for base token == USD 278 t.EstimatedUSDPrice = t.Price 279 verifiedTrade = true 280 } else { 281 var ( 282 quotation *models.AssetQuotation 283 price float64 284 ok bool 285 err error 286 ) 287 288 // Bridge basetoken if necessary. 289 basetoken := buildBridge(t) 290 291 // Get latest price from cache. 292 if _, ok = s.priceCache[basetoken.Identifier()]; ok { 293 price = s.priceCache[basetoken.Identifier()] 294 } else { 295 quotation, err = s.datastore.GetAssetQuotationCache(basetoken) 296 price = quotation.Price 297 s.priceCache[basetoken.Identifier()] = price 298 // log.Infof("quotation for %s from redis cache: %v", basetoken.Symbol, price) 299 } 300 301 if err != nil { 302 log.Errorf("Can't find quotation for base token in trade %s: %v.\n Basetoken address -- blockchain: %s --- %s", 303 t.Pair, 304 err, 305 t.BaseToken.Address, 306 t.BaseToken.Blockchain, 307 ) 308 } else { 309 if price > 0.0 { 310 t.EstimatedUSDPrice = t.Price * price 311 if t.VolumeUSD() > tradeVolumeThresholdUSD { 312 verifiedTrade = true 313 } else { 314 log.Warn("low $ volume on trade: ", t) 315 } 316 } 317 } 318 } 319 } 320 321 // // If estimated price for stablecoin diverges too much ignore trade 322 if _, ok := stablecoins[t.Symbol]; ok { 323 if math.Abs(t.EstimatedUSDPrice-1) > tol && t.EstimatedUSDPrice > 0 { 324 log.Errorf("%s on %s. price for %s diverges by %v", t.Pair, t.Source, t.Symbol, math.Abs(t.EstimatedUSDPrice-1)) 325 verifiedTrade = false 326 } 327 } 328 // Comment Philipp: We could make another check here. Store CG and/or CMC quotation in redis cache 329 // and compare with estimatedUSDPrice. If deviation is too large ignore trade. 330 var err error 331 if !s.historical { 332 err = s.datastore.SaveTradeInflux(&t) 333 if err != nil { 334 log.Error(err) 335 } 336 } else { 337 err = s.datastore.SaveTradeInfluxToTable(&t, s.writeMeasurement) 338 if err != nil { 339 log.Error(err) 340 } 341 } 342 343 if s.currentBlock != nil && s.currentBlock.TradesBlockData.BeginTime.After(t.Time) { 344 log.Debugf("ignore trade should be in previous block %v", t) 345 verifiedTrade = false 346 } 347 348 // Only verified trades of verified pairs with nonzero price are added to the tradesBlock 349 if verifiedTrade && t.EstimatedUSDPrice > 0 { 350 if s.currentBlock == nil || s.currentBlock.TradesBlockData.EndTime.Before(t.Time) { 351 if s.currentBlock != nil { 352 s.finaliseCurrentBlock() 353 s.priceCache = make(map[string]float64) 354 } 355 356 b := &dia.TradesBlock{ 357 TradesBlockData: dia.TradesBlockData{ 358 Trades: []dia.Trade{}, 359 EndTime: time.Unix((t.Time.Unix()/s.BlockDuration)*s.BlockDuration+s.BlockDuration, 0), 360 BeginTime: time.Unix((t.Time.Unix()/s.BlockDuration)*s.BlockDuration, 0), 361 }, 362 } 363 if s.currentBlock != nil { 364 log.Info("created new block beginTime:", b.TradesBlockData.BeginTime, "previous block nb trades:", len(s.currentBlock.TradesBlockData.Trades)) 365 } 366 s.currentBlock = b 367 err = s.datastore.Flush() 368 if err != nil { 369 log.Error(err) 370 } 371 } 372 // For centralized exchanges check if trade is not in the block yet 373 // (we have observed ws APIs sending identical trades). 374 if scrapers.Exchanges[t.Source].Centralized { 375 if _, ok := checkTradesDuplicate[t.TradeIdentifierFull()]; !ok { 376 s.currentBlock.TradesBlockData.Trades = append(s.currentBlock.TradesBlockData.Trades, t) 377 checkTradesDuplicate[t.TradeIdentifierFull()] = struct{}{} 378 } else { 379 if scrapers.Exchanges[t.Source].Name != dia.BitforexExchange { 380 log.Warn("duplicate trade within one tradesblock: ", t) 381 } 382 } 383 } else { 384 s.currentBlock.TradesBlockData.Trades = append(s.currentBlock.TradesBlockData.Trades, t) 385 } 386 } else { 387 log.Debugf("ignore trade %v", t) 388 } 389 } 390 391 func (s *TradesBlockService) loadVolumes() map[string]float64 { 392 // Clean asset volumes 393 volumeCache := make(map[string]float64) 394 endtime := time.Now() 395 assets, err := s.relDB.GetAssetsWithVolByBlockchain(endtime.AddDate(0, 0, -7), endtime, "") 396 if err != nil { 397 log.Error("could not load asset with volume: ", err) 398 } 399 for _, asset := range assets { 400 volumeCache[asset.Asset.Identifier()] = asset.Volume 401 } 402 return volumeCache 403 } 404 405 func (s *TradesBlockService) loadVolumesLoop() { 406 for range s.volumeTicker.C { 407 s.volumeCache = s.loadVolumes() 408 } 409 } 410 411 func (s *TradesBlockService) finaliseCurrentBlock() { 412 413 sort.Slice(s.currentBlock.TradesBlockData.Trades, func(i, j int) bool { 414 return s.currentBlock.TradesBlockData.Trades[i].Time.Before(s.currentBlock.TradesBlockData.Trades[j].Time) 415 }) 416 417 hash, err := structhash.Hash(s.currentBlock.TradesBlockData, 1) 418 if err != nil { 419 log.Printf("error on hash") 420 hash = "hashError" 421 } 422 s.currentBlock.BlockHash = hash 423 s.currentBlock.TradesBlockData.TradesNumber = len(s.currentBlock.TradesBlockData.Trades) 424 // Reset duplicate trades identifier. 425 checkTradesDuplicate = make(map[string]struct{}) 426 s.chanTradesBlock <- s.currentBlock 427 } 428 429 func (s *TradesBlockService) ProcessTrade(trade *dia.Trade) { 430 s.chanTrades <- trade 431 } 432 433 func (s *TradesBlockService) Close() error { 434 if s.closed { 435 return errors.New("TradesBlockService: Already closed") 436 } 437 close(s.shutdown) 438 <-s.shutdownDone 439 return s.error 440 } 441 442 // must only be called from mainLoop 443 func (s *TradesBlockService) cleanup(err error) { 444 s.errorLock.Lock() 445 defer s.errorLock.Unlock() 446 if err != nil { 447 s.error = err 448 } 449 s.closed = true 450 close(s.shutdownDone) // signal that shutdown is complete 451 } 452 453 func (s *TradesBlockService) Channel() chan *dia.TradesBlock { 454 return s.chanTradesBlock 455 } 456 457 func buildBridge(t dia.Trade) dia.Asset { 458 459 basetoken := t.BaseToken 460 461 // if basetoken.Blockchain == dia.ETHEREUM && basetoken.Address == "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" { 462 // basetoken = dia.Asset{ 463 // Symbol: "ETH", 464 // Address: "0x0000000000000000000000000000000000000000", 465 // Blockchain: dia.ETHEREUM, 466 // } 467 // } 468 if basetoken.Blockchain == dia.SOLANA && t.Source == dia.OrcaExchange { 469 if basetoken.Address == "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" { 470 basetoken = dia.Asset{ 471 Symbol: "USDC", 472 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 473 Blockchain: dia.ETHEREUM, 474 } 475 } 476 if basetoken.Address == "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" { 477 basetoken = dia.Asset{ 478 Symbol: "USDT", 479 Address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", 480 Blockchain: dia.ETHEREUM, 481 } 482 } 483 } 484 if basetoken.Blockchain == dia.METIS && (t.Source == dia.NetswapExchange || t.Source == dia.TethysExchange || t.Source == dia.HermesExchange) && basetoken.Address == "0xEA32A96608495e54156Ae48931A7c20f0dcc1a21" { 485 basetoken = dia.Asset{ 486 Symbol: "USDC", 487 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 488 Blockchain: dia.ETHEREUM, 489 } 490 } 491 if basetoken.Blockchain == dia.FANTOM && (t.Source == dia.SpookyswapExchange || t.Source == dia.SpiritswapExchange || t.Source == dia.BeetsExchange || t.Source == dia.SushiSwapExchangeFantom) { 492 if basetoken.Address == "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" { 493 basetoken = dia.Asset{ 494 Symbol: "FTM", 495 Address: "0x0000000000000000000000000000000000000000", 496 Blockchain: dia.FANTOM, 497 } 498 } 499 } 500 if basetoken.Blockchain == dia.TELOS && (t.Source == dia.OmniDexExchange) && basetoken.Address == common.HexToAddress("0xd102ce6a4db07d247fcc28f366a623df0938ca9e").Hex() { 501 basetoken = dia.Asset{ 502 Symbol: "TLOS", 503 Address: "0x0000000000000000000000000000000000000000", 504 Blockchain: dia.TELOS, 505 } 506 } 507 if basetoken.Blockchain == dia.EVMOS && t.Source == dia.DiffusionExchange { 508 if basetoken.Address == common.HexToAddress("0xD4949664cD82660AaE99bEdc034a0deA8A0bd517").Hex() { 509 basetoken = dia.Asset{ 510 Symbol: "EVMOS", 511 Address: "0x0000000000000000000000000000000000000000", 512 Blockchain: dia.EVMOS, 513 } 514 } 515 } 516 if t.Source == dia.StellaswapExchange && basetoken.Blockchain == dia.MOONBEAM { 517 if basetoken.Address == common.HexToAddress("0xAcc15dC74880C9944775448304B263D191c6077F").Hex() { 518 basetoken = dia.Asset{ 519 Symbol: "GLMR", 520 Address: "0x0000000000000000000000000000000000000000", 521 Blockchain: dia.MOONBEAM, 522 } 523 } 524 } 525 if t.Source == dia.CurveFIExchangeMoonbeam && basetoken.Blockchain == dia.MOONBEAM { 526 if basetoken.Address == common.HexToAddress("0xFFFFFFfFea09FB06d082fd1275CD48b191cbCD1d").Hex() { 527 basetoken = dia.Asset{ 528 Symbol: "USDT", 529 Address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", 530 Blockchain: dia.ETHEREUM, 531 } 532 } 533 } 534 if (t.Source == dia.UniswapExchangeV3Polygon || t.Source == dia.QuickswapExchange || t.Source == dia.SushiSwapExchangePolygon || t.Source == dia.DfynNetwork) && basetoken.Blockchain == dia.POLYGON { 535 if basetoken.Address == common.HexToAddress("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174").Hex() { 536 basetoken = dia.Asset{ 537 Symbol: "USDC", 538 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 539 Blockchain: dia.ETHEREUM, 540 } 541 } 542 if basetoken.Address == common.HexToAddress("0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270").Hex() { 543 basetoken = dia.Asset{ 544 Symbol: "MATIC", 545 Address: "0x0000000000000000000000000000000000001010", 546 Blockchain: dia.POLYGON, 547 } 548 } 549 } 550 if t.Source == dia.ArthswapExchange && basetoken.Blockchain == dia.ASTAR { 551 if basetoken.Address == common.HexToAddress("0x6a2d262D56735DbA19Dd70682B39F6bE9a931D98").Hex() { 552 basetoken = dia.Asset{ 553 Symbol: "USDC", 554 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 555 Blockchain: dia.ETHEREUM, 556 } 557 } 558 if basetoken.Address == common.HexToAddress("0xAeaaf0e2c81Af264101B9129C00F4440cCF0F720").Hex() { 559 basetoken = dia.Asset{ 560 Symbol: "ASTR", 561 Address: "0x0000000000000000000000000000000000000000", 562 Blockchain: dia.ASTAR, 563 } 564 } 565 } 566 if basetoken.Blockchain == dia.AVALANCHE && (t.Source == dia.TraderJoeExchange || t.Source == dia.PangolinExchange) { 567 if basetoken.Address == common.HexToAddress("0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7").Hex() { 568 basetoken = dia.Asset{ 569 Symbol: "AVAX", 570 Address: "0x0000000000000000000000000000000000000000", 571 Blockchain: dia.AVALANCHE, 572 } 573 } 574 } 575 if basetoken.Blockchain == dia.WANCHAIN && t.Source == dia.WanswapExchange { 576 if basetoken.Address == common.HexToAddress("0xdabD997aE5E4799BE47d6E69D9431615CBa28f48").Hex() { 577 basetoken = dia.Asset{ 578 Symbol: "WAN", 579 Address: "0x0000000000000000000000000000000000000000", 580 Blockchain: dia.WANCHAIN, 581 } 582 } 583 } 584 if basetoken.Blockchain == dia.ARBITRUM && (t.Source == dia.UniswapExchangeV3Arbitrum || t.Source == dia.SushiSwapExchangeArbitrum || t.Source == dia.CamelotExchange || t.Source == dia.TraderJoeExchangeV2_1Arbitrum) { 585 if basetoken.Address == common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1").Hex() { 586 basetoken = dia.Asset{ 587 Symbol: "ETH", 588 Address: "0x0000000000000000000000000000000000000000", 589 Blockchain: dia.ETHEREUM, 590 } 591 } 592 } 593 if basetoken.Blockchain == dia.OSMOSIS && t.Source == dia.OsmosisExchange { 594 if basetoken.Address == "ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858" { 595 basetoken = dia.Asset{ 596 Symbol: "USDC", 597 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 598 Blockchain: dia.ETHEREUM, 599 } 600 } 601 } 602 if basetoken.Blockchain == dia.BIFROST && t.Source == dia.BifrostExchange { 603 if basetoken.Address == "token2-0" { 604 basetoken = dia.Asset{ 605 Symbol: "DOT", 606 Address: "0x0000000000000000000000000000000000000000", 607 Blockchain: dia.POLKADOT, 608 } 609 } 610 if basetoken.Address == "token2-1" { 611 basetoken = dia.Asset{ 612 Symbol: "GLMR", 613 Address: "0x0000000000000000000000000000000000000000", 614 Blockchain: dia.MOONBEAM, 615 } 616 } 617 if basetoken.Address == "token2-3" { 618 basetoken = dia.Asset{ 619 Symbol: "ASTR", 620 Address: "0x0000000000000000000000000000000000000000", 621 Blockchain: dia.ASTAR, 622 } 623 } 624 if basetoken.Address == "token2-4" { 625 basetoken = dia.Asset{ 626 Symbol: "FIL", 627 Address: "0x0000000000000000000000000000000000000000", 628 Blockchain: dia.FILECOIN, 629 } 630 } 631 } 632 633 if basetoken.Blockchain == dia.UNREAL_TESTNET && t.Source == dia.PearlfiExchangeTestnet { 634 if basetoken.Address == "0x0C68a3C11FB3550e50a4ed8403e873D367A8E361" { 635 basetoken = dia.Asset{ 636 Symbol: "WETH", 637 Address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 638 Blockchain: dia.ETHEREUM, 639 } 640 } 641 if basetoken.Address == "0x3F93beBAd7BA4d7A5129eA8159A5829Eacb06497" { 642 basetoken = dia.Asset{ 643 Symbol: "DAI", 644 Address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", 645 Blockchain: dia.ETHEREUM, 646 } 647 } 648 } 649 if basetoken.Blockchain == dia.UNREAL && t.Source == dia.PearlfiExchange { 650 if basetoken.Address == "0x75d0cBF342060b14c2fC756fd6E717dFeb5B1B70" { 651 basetoken = dia.Asset{ 652 Symbol: "DAI", 653 Address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", 654 Blockchain: dia.ETHEREUM, 655 } 656 } 657 if basetoken.Address == "0xc518A88c67CECA8B3f24c4562CB71deeB2AF86B7" { 658 basetoken = dia.Asset{ 659 Symbol: "USDC", 660 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 661 Blockchain: dia.ETHEREUM, 662 } 663 } 664 if basetoken.Address == "0xAEC9e50e3397f9ddC635C6c429C8C7eca418a143" { 665 basetoken = dia.Asset{ 666 Symbol: "USDC", 667 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 668 Blockchain: dia.ETHEREUM, 669 } 670 } 671 } 672 if basetoken.Blockchain == dia.BINANCESMARTCHAIN && t.Source == dia.PanCakeSwapExchangeV3 { 673 if basetoken.Address == "0x55d398326f99059fF775485246999027B3197955" { 674 basetoken = dia.Asset{ 675 Symbol: "USDT", 676 Address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", 677 Blockchain: dia.ETHEREUM, 678 } 679 } 680 } 681 if basetoken.Blockchain == dia.LINEA && (t.Source == dia.NileV1Exchange || t.Source == dia.NileV2Exchange) { 682 if basetoken.Address == "0x176211869cA2b568f2A7D4EE941E073a821EE1ff" { 683 basetoken = dia.Asset{ 684 Symbol: "USDC", 685 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 686 Blockchain: dia.ETHEREUM, 687 } 688 } 689 } 690 if basetoken.Blockchain == dia.OPTIMISM && t.Source == dia.VelodromeExchange { 691 if basetoken.Address == "0x7F5c764cBc14f9669B88837ca1490cCa17c31607" || basetoken.Address == "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85" { 692 basetoken = dia.Asset{ 693 Symbol: "USDC", 694 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 695 Blockchain: dia.ETHEREUM, 696 } 697 } 698 } 699 if basetoken.Blockchain == dia.BASE && (t.Source == dia.UniswapExchangeV3Base || 700 t.Source == dia.UniswapExchangeBase || 701 t.Source == dia.AerodromeSlipstreamExchange || 702 t.Source == dia.AerodromeV1Exchange) { 703 if basetoken.Address == "0x4200000000000000000000000000000000000006" { 704 basetoken = dia.Asset{ 705 Symbol: "WETH", 706 Address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 707 Blockchain: dia.ETHEREUM, 708 } 709 } 710 } 711 if basetoken.Blockchain == dia.HYDRATION && t.Source == dia.HydrationExchange { 712 if basetoken.Address == "18" { 713 basetoken = dia.Asset{ 714 Symbol: "DAI", 715 Address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", 716 Blockchain: dia.ETHEREUM, 717 } 718 } 719 if basetoken.Address == "10" { 720 basetoken = dia.Asset{ 721 Symbol: "USDT", 722 Address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", 723 Blockchain: dia.ETHEREUM, 724 } 725 } 726 } 727 if basetoken.Blockchain == dia.STACKS && (t.Source == dia.BitflowExchange || t.Source == dia.VelarExchange) { 728 if basetoken.Address == "SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-aeusdc" { 729 basetoken = dia.Asset{ 730 Symbol: "USDC", 731 Address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 732 Blockchain: dia.ETHEREUM, 733 } 734 } 735 } 736 if basetoken.Blockchain == dia.SWELLCHAIN && t.Source == dia.VelodromeExchangeSwellchain { 737 if basetoken.Address == "0x4200000000000000000000000000000000000006" { 738 basetoken = dia.Asset{ 739 Symbol: "WETH", 740 Address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 741 Blockchain: dia.ETHEREUM, 742 } 743 } 744 } 745 746 return basetoken 747 }