public StakeService( IOptions <AppSettings> appSettings, IEtherClient etherClient, IGraphClient graphClient, IEthplorerClient ethplorerClient, IFundService fundService, ICurrencyConverter currencyConverter, IStakingPowerRepository stakingPowerRepository, ITransactionRepository transactionRepository, IOperationRepository operationRepository, IHttpContextAccessor httpContextAccessor, IScopedCancellationToken scopedCancellationToken) : base(appSettings, currencyConverter, transactionRepository, operationRepository, httpContextAccessor, scopedCancellationToken) { this.etherClient = etherClient; this.graphClient = graphClient; this.ethplorerClient = ethplorerClient; this.fundService = fundService; this.stakingPowerRepository = stakingPowerRepository; }
private async Task SyncStakingPowerAsync( IFundService fundService, IStakeService stakeService, IStakingPowerRepository stakingRepository, IStakeSettings stake, DateTimeOffset startDate, DateTimeOffset endDate, CancellationToken cancellationToken) { var currentDate = GetHourlyDate(startDate.UtcDateTime).UtcDateTime; var lastStakingPower = await stakingRepository.GetStakingPowerAsync(stake.ContractAddress, currentDate.AddHours(-1)); var stakingEvents = await stakeService.ListStakeEventsAsync(stake.Symbol, startDate.UtcDateTime, endDate.UtcDateTime) .ToListAsync(cancellationToken); var hourlyGroups = stakingEvents .OrderBy(x => x.ConfirmedAt) .GroupBy(x => GetHourlyDate(x.ConfirmedAt)) .ToList(); do { try { var fundPowers = new List <DataStakingPowerFund>(); var hourlyEvents = hourlyGroups.SingleOrDefault(g => g.Key == currentDate) ?? Enumerable.Empty <IStakeEvent>(); foreach (var symbol in stake.FundMultipliers.Keys) { var fund = AppSettings.Funds .Cast <IFundSettings>() .Single(x => x.Symbol == symbol); var events = lastStakingPower?.Breakdown.SingleOrDefault(x => x.ContractAddress.Equals(fund.ContractAddress.Address, StringComparison.OrdinalIgnoreCase)) ?.Events.ToList() ?? new List <DataStakingEvent>(); events.AddRange(hourlyEvents .Where(e => e.ContractAddress == fund.ContractAddress && e.Type == StakeEventType.Lockup) .Select(e => new DataStakingEvent() { UserAddress = e.UserAddress, StakedAt = e.ConfirmedAt, Quantity = e.Change, ExpiresAt = e.Lock.ExpiresAt, TimeModifier = stake.TimeMultipliers .SingleOrDefault(tm => tm.RangeMin <= e.Lock.Duration.Days && tm.RangeMax >= e.Lock.Duration.Days) ?.Multiplier ?? 1 })); foreach (var releaseEvent in hourlyEvents .Where(e => e.ContractAddress == fund.ContractAddress && e.Type != StakeEventType.Lockup)) { var approximateQuantity = releaseEvent.Release.Quantity + (releaseEvent.Release.FeeQuantity ?? decimal.Zero); var userStakes = events .Where(e => e.UserAddress.Equals(releaseEvent.UserAddress.Address, StringComparison.OrdinalIgnoreCase)) .ToList(); var lockUp = userStakes.Count > 0 ? userStakes.Count == 1 ? userStakes.Single() : userStakes .OrderBy(x => x.StakedAt) .FirstOrDefault(e => Math.Abs(e.Quantity - approximateQuantity) <= Precision) : null; if (lockUp != null) { events.Remove(lockUp); } } if (events.Any()) { var prices = await fundService.ListPerformanceAsync( symbol, PriceMode.Raw, currentDate.Date.AddDays(-1), currentDate.Date.AddDays(2), CurrencyCode.USD) .ToListAsync(cancellationToken); var closestPrice = prices .OrderBy(i => Math.Abs( new DateTimeOffset(i.Date, TimeSpan.Zero).ToUnixTimeSeconds() - new DateTimeOffset(currentDate, TimeSpan.Zero).ToUnixTimeSeconds())) .FirstOrDefault() ?? throw new PermanentException($"No Price data could be found for date {currentDate}"); fundPowers.Add(new DataStakingPowerFund() { ContractAddress = fund.ContractAddress, FundModifier = stake.FundMultipliers[symbol], PricePerToken = closestPrice.NetAssetValuePerToken, Events = events }); } } lastStakingPower = new DataStakingPower() { Address = stake.ContractAddress, Date = currentDate.AddHours(1), Power = fundPowers.Sum(fp => fp.PricePerToken * fp.Events.Sum(fpe => fpe.Quantity * fpe.TimeModifier * fp.FundModifier)), Breakdown = fundPowers, Summary = fundPowers .Select(fp => new DataStakingPowerSummary() { ContractAddress = fp.ContractAddress, Power = fp.PricePerToken * fp.Events.Sum(fpe => fpe.Quantity * fpe.TimeModifier * fp.FundModifier) }) .ToList() }; await stakingRepository.UploadItemsAsync(lastStakingPower); } finally { currentDate = currentDate.AddHours(1); } }while (!cancellationToken.IsCancellationRequested && currentDate < endDate.Round()); }