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  }