From 8facf10b408018e637048de6771adc02e6dc38f2 Mon Sep 17 00:00:00 2001 From: mixhsnhd Date: Thu, 2 Jan 2025 15:48:44 +0800 Subject: [PATCH] feat: activities token transfer from token indexer --- .../Provider/Dto/Indexer/BaseInput.cs | 36 ++++++ .../Indexer/IndexerTokenTransferListDto.cs | 21 ++++ .../Provider/Dto/Indexer/SortField.cs | 20 +++ .../Dto/Indexer/TokenTransferInput.cs | 43 +++++++ .../Provider/IGraphQLProvider.cs | 9 ++ .../UserActivity/Dto/ActivityBase.cs | 2 +- .../Request/GetActivitiesRequestDto.cs | 2 +- .../EoaServerApplicationModule.cs | 1 + .../Options/GraphQLOptions.cs | 13 ++ .../Provider/GraphQLProvider.cs | 78 ++++++++++++ .../UserActivity/UserActivityAppService.cs | 117 +++++++++--------- src/EoaServer.Domain/DateTimeHelper.cs | 23 ++++ src/EoaServer.HttpApi.Host/appsettings.json | 12 +- 13 files changed, 315 insertions(+), 62 deletions(-) create mode 100644 src/EoaServer.Application.Contracts/Provider/Dto/Indexer/BaseInput.cs create mode 100644 src/EoaServer.Application.Contracts/Provider/Dto/Indexer/IndexerTokenTransferListDto.cs create mode 100644 src/EoaServer.Application.Contracts/Provider/Dto/Indexer/SortField.cs create mode 100644 src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TokenTransferInput.cs create mode 100644 src/EoaServer.Application.Contracts/Provider/IGraphQLProvider.cs create mode 100644 src/EoaServer.Application/Options/GraphQLOptions.cs create mode 100644 src/EoaServer.Application/Provider/GraphQLProvider.cs create mode 100644 src/EoaServer.Domain/DateTimeHelper.cs diff --git a/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/BaseInput.cs b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/BaseInput.cs new file mode 100644 index 0000000..f2daf44 --- /dev/null +++ b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/BaseInput.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; +using MongoDB.Driver; + +namespace EoaServer.Provider.Dto.Indexer; + +public class BaseInput : OrderInfo +{ + public string ChainId { get; set; } = ""; + public long SkipCount { get; set; } + public long MaxResultCount { get; set; } = 10; + public List OrderInfos { get; set; } + public List SearchAfter { get; set; } + + public void OfOrderInfos(params (SortField sortField, SortDirection sortDirection)[] orderInfos) + { + OrderInfos = BuildOrderInfos(orderInfos); + } +} + +public class OrderInfo +{ + public string OrderBy { get; set; } + + public string Sort { get; set; } + + public static List BuildOrderInfos( + params (SortField sortField, SortDirection sortDirection)[] orderInfos) + { + return orderInfos.Select(info => new OrderInfo + { + OrderBy = info.sortField.ToString(), + Sort = info.sortDirection.ToString() + }).ToList(); + } +} \ No newline at end of file diff --git a/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/IndexerTokenTransferListDto.cs b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/IndexerTokenTransferListDto.cs new file mode 100644 index 0000000..c6dc764 --- /dev/null +++ b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/IndexerTokenTransferListDto.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using EoaServer.Token.Dto; + +namespace EoaServer.Provider.Dto.Indexer; + +public class IndexerTokenTransfersDto +{ + public IndexerTokenTransferListDto TransferInfo { get; set; } +} + +public class IndexerTokenTransferListDto +{ + public long TotalCount { get; set; } + public List Items { get; set; } = new(); +} + +public class IndexerTransferInfoDto +{ + public string TransactionId { get; set; } + public MetadataDto Metadata { get; set; } +} \ No newline at end of file diff --git a/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/SortField.cs b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/SortField.cs new file mode 100644 index 0000000..55561ac --- /dev/null +++ b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/SortField.cs @@ -0,0 +1,20 @@ +namespace EoaServer.Provider.Dto.Indexer; + +public enum SortField +{ + Id, + BlockTime, + BlockHeight, + HolderCount, + TransferCount, + Symbol, + FormatAmount, + Address, + TransactionId +} + +public enum SortDirection +{ + Asc, + Desc +} \ No newline at end of file diff --git a/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TokenTransferInput.cs b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TokenTransferInput.cs new file mode 100644 index 0000000..5c58874 --- /dev/null +++ b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TokenTransferInput.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using EoaServer.Token.Dto; +using MongoDB.Driver; + +namespace EoaServer.Provider.Dto.Indexer; + +public class TokenTransferInput : BaseInput + +{ + public string Symbol { get; set; } = ""; + public string Search { get; set; } = ""; + public string CollectionSymbol { get; set; } = ""; + + public string Address { get; set; } = ""; + + public List Types { get; set; } = new() { SymbolType.Token }; + + public string FuzzySearch { get; set; } = ""; + + public DateTime? BeginBlockTime { get; set; } + + public void SetDefaultSort() + { + if (!OrderBy.IsNullOrEmpty() || !OrderInfos.IsNullOrEmpty()) + { + return; + } + + OfOrderInfos((SortField.BlockTime, SortDirection.Desc), (SortField.TransactionId, SortDirection.Desc)); + } + + + public void SetBlockTimeSort() + { + if (!OrderBy.IsNullOrEmpty() || !OrderInfos.IsNullOrEmpty()) + { + return; + } + + OfOrderInfos((SortField.BlockTime, SortDirection.Desc), (SortField.TransactionId, SortDirection.Desc)); + } +} \ No newline at end of file diff --git a/src/EoaServer.Application.Contracts/Provider/IGraphQLProvider.cs b/src/EoaServer.Application.Contracts/Provider/IGraphQLProvider.cs new file mode 100644 index 0000000..0c6b06c --- /dev/null +++ b/src/EoaServer.Application.Contracts/Provider/IGraphQLProvider.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using EoaServer.Provider.Dto.Indexer; + +namespace EoaServer.Provider; + +public interface IGraphQLProvider +{ + public Task GetTokenTransferInfoAsync(TokenTransferInput input); +} \ No newline at end of file diff --git a/src/EoaServer.Application.Contracts/UserActivity/Dto/ActivityBase.cs b/src/EoaServer.Application.Contracts/UserActivity/Dto/ActivityBase.cs index 7e63aae..f489264 100644 --- a/src/EoaServer.Application.Contracts/UserActivity/Dto/ActivityBase.cs +++ b/src/EoaServer.Application.Contracts/UserActivity/Dto/ActivityBase.cs @@ -25,7 +25,7 @@ public class ActivityBase public string ToChainId { get; set; } public string ToChainIdUpdated { get; set; } public string ToChainIcon { get; set; } - public List TransactionFees { get; set; } + public List TransactionFees { get; set; } = new(); public string PriceInUsd { get; set; } public bool IsDelegated { get; set; } public bool IsSystem { get; set; } diff --git a/src/EoaServer.Application.Contracts/UserActivity/Request/GetActivitiesRequestDto.cs b/src/EoaServer.Application.Contracts/UserActivity/Request/GetActivitiesRequestDto.cs index ba3e73a..ebcf729 100644 --- a/src/EoaServer.Application.Contracts/UserActivity/Request/GetActivitiesRequestDto.cs +++ b/src/EoaServer.Application.Contracts/UserActivity/Request/GetActivitiesRequestDto.cs @@ -12,7 +12,7 @@ public class GetActivitiesRequestDto : PagedResultRequestDto public List AddressInfos { get; set; } public List TransactionTypes { get; set; } public string ChainId { get; set; } - public string Symbol { get; set; } + public string Symbol { get; set; } = ""; public int Width { get; set; } diff --git a/src/EoaServer.Application/EoaServerApplicationModule.cs b/src/EoaServer.Application/EoaServerApplicationModule.cs index 4ddecae..307b1ca 100644 --- a/src/EoaServer.Application/EoaServerApplicationModule.cs +++ b/src/EoaServer.Application/EoaServerApplicationModule.cs @@ -53,5 +53,6 @@ public override void ConfigureServices(ServiceConfigurationContext context) Configure(configuration.GetSection("Deposit")); Configure(configuration.GetSection("CoinGecko")); Configure(configuration.GetSection("AWSThumbnail")); + Configure(configuration.GetSection("GraphQLOptions")); } } \ No newline at end of file diff --git a/src/EoaServer.Application/Options/GraphQLOptions.cs b/src/EoaServer.Application/Options/GraphQLOptions.cs new file mode 100644 index 0000000..fbb0881 --- /dev/null +++ b/src/EoaServer.Application/Options/GraphQLOptions.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace EoaServer.Options; + +public class GraphQLOptions +{ + public Dictionary IndexerOptions { get; set; } +} + +public class IndexerOption +{ + public string BaseUrl { get; set; } +} \ No newline at end of file diff --git a/src/EoaServer.Application/Provider/GraphQLProvider.cs b/src/EoaServer.Application/Provider/GraphQLProvider.cs new file mode 100644 index 0000000..841065e --- /dev/null +++ b/src/EoaServer.Application/Provider/GraphQLProvider.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using EoaServer.Options; +using EoaServer.Provider.Dto.Indexer; +using EoaServer.Token; +using EoaServer.Token.Dto; +using GraphQL; +using GraphQL.Client.Http; +using GraphQL.Client.Serializer.Newtonsoft; +using Microsoft.Extensions.Options; +using Orleans; +using Serilog; +using Volo.Abp.DependencyInjection; + +namespace EoaServer.Provider; + +public class GraphQLProvider : IGraphQLProvider, ISingletonDependency +{ + private readonly GraphQLOptions _graphQLOptions; + private readonly GraphQLHttpClient _blockChainIndexerClient; + private readonly GraphQLHttpClient _tokenIndexerClient; + private readonly IClusterClient _clusterClient; + private readonly ILogger _logger; + private readonly ITokenAppService _tokenAppService; + + public const string TokenIndexer = "TokenIndexer"; + public const string BlockChainIndexer = "BlockChainIndexer"; + + public GraphQLProvider(IClusterClient clusterClient, + ITokenAppService tokenAppService, + IOptionsSnapshot graphQLOptions) + { + _logger = Log.ForContext(); + _clusterClient = clusterClient; + _graphQLOptions = graphQLOptions.Value; + _blockChainIndexerClient = new GraphQLHttpClient(_graphQLOptions.IndexerOptions[BlockChainIndexer].BaseUrl, new NewtonsoftJsonSerializer()); + _tokenIndexerClient = new GraphQLHttpClient(_graphQLOptions.IndexerOptions[TokenIndexer].BaseUrl, new NewtonsoftJsonSerializer()); + _tokenAppService = tokenAppService; + } + + public async Task GetTokenTransferInfoAsync(TokenTransferInput input) + { + input.SetDefaultSort(); + var indexerResult = await _tokenIndexerClient.SendQueryAsync(new GraphQLRequest + { + Query = + @"query($chainId:String!,$symbol:String!,$address:String,$collectionSymbol:String, + $search:String,$skipCount:Int!,$maxResultCount:Int!,$types:[SymbolType!],$beginBlockTime:DateTime, + $fuzzySearch:String,$sort:String,$orderBy:String,$searchAfter:[String],$orderInfos:[OrderInfo]){ + transferInfo(input: {chainId:$chainId,symbol:$symbol,collectionSymbol:$collectionSymbol,address:$address,types:$types,beginBlockTime:$beginBlockTime,search:$search, + skipCount:$skipCount,maxResultCount:$maxResultCount,fuzzySearch:$fuzzySearch,sort:$sort,orderBy:$orderBy,searchAfter:$searchAfter,orderInfos:$orderInfos}){ + totalCount, + items{ + transactionId + metadata { + chainId + block { + blockHash + blockHeight + blockTime + } + } + } + } + }", + Variables = new + { + chainId = input.ChainId, symbol = input.Symbol, address = input.Address, search = input.Search, + skipCount = input.SkipCount, maxResultCount = input.MaxResultCount, + collectionSymbol = input.CollectionSymbol, + sort = input.Sort, fuzzySearch = input.FuzzySearch, + orderInfos = input.OrderInfos, searchAfter = input.SearchAfter, beginBlockTime = input.BeginBlockTime + } + }); + return indexerResult == null || indexerResult.Data == null ? + new IndexerTokenTransferListDto() : indexerResult.Data.TransferInfo; + } +} \ No newline at end of file diff --git a/src/EoaServer.Application/UserActivity/UserActivityAppService.cs b/src/EoaServer.Application/UserActivity/UserActivityAppService.cs index 1b7c416..67fa768 100644 --- a/src/EoaServer.Application/UserActivity/UserActivityAppService.cs +++ b/src/EoaServer.Application/UserActivity/UserActivityAppService.cs @@ -6,6 +6,8 @@ using EoaServer.Common; using EoaServer.Commons; using EoaServer.Options; +using EoaServer.Provider; +using EoaServer.Provider.Dto.Indexer; using EoaServer.Token; using EoaServer.Token.Dto; using EoaServer.UserActivity.Dto; @@ -13,6 +15,7 @@ using EoaServer.UserAssets; using EoaServer.UserAssets.Dtos; using EoaServer.UserAssets.Provider; +using Google.Protobuf.WellKnownTypes; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; @@ -36,14 +39,16 @@ public class UserActivityAppService : EoaServerBaseService, IUserActivityAppServ private readonly ITokenInfoProvider _tokenInfoProvider; private readonly IAElfScanDataProvider _aelfScanDataProvider; private readonly IImageProcessProvider _imageProcessProvider; - + private readonly IGraphQLProvider _graphqlProvider; + public UserActivityAppService(ILogger logger, IOptionsSnapshot activityOptions, IOptionsSnapshot tokenSpenderOptions, IOptionsSnapshot chainOptions, ITokenInfoProvider tokenInfoProvider, IImageProcessProvider imageProcessProvider, - IAElfScanDataProvider aelfScanDataProvider) + IAElfScanDataProvider aelfScanDataProvider, + IGraphQLProvider graphqlProvider) { _logger = logger; _activityOptions = activityOptions.Value; @@ -52,6 +57,7 @@ public UserActivityAppService(ILogger logger, _tokenInfoProvider = tokenInfoProvider; _aelfScanDataProvider = aelfScanDataProvider; _imageProcessProvider = imageProcessProvider; + _graphqlProvider = graphqlProvider; } public async Task GetActivityAsync(GetActivityRequestDto request) @@ -63,43 +69,43 @@ public async Task GetActivityAsync(GetActivityRequestDto request _logger.LogError($"Get TransactionDetailResponseDto failed, chainId: {request.ChainId}, transactionId: {request.TransactionId}"); return null; } - - var tokenMap = await GetTokenMapAsync(txnDto.List); + var tokens = new HashSet( + txnDto.List + .SelectMany(txn => txn.TokenTransferreds + .Select(transfer => transfer.Symbol) + .Concat(txn.NftsTransferreds + .Select(transfer => transfer.Symbol))) + ); + var tokenMap = await GetTokenMapAsync(tokens); return await ConvertDtoAsync(request.ChainId, txnDto.List[0], tokenMap, 0, 0, request.AddressInfos[0].Address); } public async Task GetActivitiesAsync(GetActivitiesRequestDto request) { var address = request.AddressInfos[0].Address; - var chainId = request.AddressInfos.Count == 1 ? request.AddressInfos[0].ChainId : null; + var chainId = request.AddressInfos.Count == 1 ? request.AddressInfos[0].ChainId : ""; var txns = new TransactionsResponseDto(); - var tokenTransfers = new GetTransferListResultDto(); - if (string.IsNullOrEmpty(request.Symbol)) + var tokenTransfers = new IndexerTokenTransferListDto(); + + var txnsTask = _aelfScanDataProvider.GetAddressTransactionsAsync(chainId, address, 0, request.SkipCount + request.MaxResultCount); + var tokenTransfersTask = _graphqlProvider.GetTokenTransferInfoAsync(new TokenTransferInput() { - var txnsTask = _aelfScanDataProvider.GetAddressTransactionsAsync(chainId, address, 0, request.SkipCount + request.MaxResultCount); - var tokenTransfersTask = _aelfScanDataProvider.GetAddressTransfersAsync(chainId, address, 0, 0, request.SkipCount + request.MaxResultCount, null); - var nftTransfersTask = _aelfScanDataProvider.GetAddressTransfersAsync(chainId, address, 1, 0, request.SkipCount + request.MaxResultCount, null); + Address = address, + ChainId = chainId, + SkipCount = 0, + MaxResultCount = request.SkipCount + request.MaxResultCount, + Symbol = request.Symbol + }); - await Task.WhenAll(txnsTask, tokenTransfersTask, nftTransfersTask); - - txns = await txnsTask; - tokenTransfers = await tokenTransfersTask; - var nftTransfers = await nftTransfersTask; - - if (tokenTransfers != null && nftTransfers != null) - { - tokenTransfers.List.AddRange(nftTransfers.List); - } - } - else - { - tokenTransfers = await _aelfScanDataProvider.GetAddressTransfersAsync(chainId, address, 0, 0, request.SkipCount + request.MaxResultCount, request.Symbol); - } + await Task.WhenAll(txnsTask, tokenTransfersTask); + txns = await txnsTask; + tokenTransfers = await tokenTransfersTask; + if (tokenTransfers != null) { - foreach (var transfer in tokenTransfers.List) + foreach (var transfer in tokenTransfers.Items) { var txn = txns.Transactions.FirstOrDefault(t => t.TransactionId == transfer.TransactionId); if (txn == null) @@ -107,8 +113,8 @@ public async Task GetActivitiesAsync(GetActivitiesRequestDto r txns.Transactions.Add(new TransactionResponseDto { TransactionId = transfer.TransactionId, - ChainIds = transfer.ChainIds, - Timestamp = transfer.BlockTime + ChainIds = new List(){transfer.Metadata.ChainId}, + Timestamp = DateTimeHelper.ToUnixTimeSeconds(transfer.Metadata.Block.BlockTime) }); } } @@ -127,24 +133,34 @@ public async Task GetActivitiesAsync(GetActivitiesRequestDto r return null; } txnChainMap[txn.TransactionId] = txn.ChainIds[0]; - var txnDetail = await _aelfScanDataProvider.GetTransactionDetailAsync(txn.ChainIds[0], txn.TransactionId); - if (txnDetail == null) - { - _logger.LogError($"Get transaction detail error. ChainId: {txn.ChainIds[0]}, TransactionId: {txn.TransactionId}"); - } - return txnDetail; + return await _aelfScanDataProvider.GetTransactionDetailAsync(txn.ChainIds[0], txn.TransactionId); }).ToList(); - var txnDetails = (await Task.WhenAll(mapTasks)) + var txnDetailMap = (await Task.WhenAll(mapTasks)) .Where(result => result != null) - .SelectMany(result => result.List) - .ToList(); + .SelectMany(result => result.List) + .ToDictionary(t => t.TransactionId, t => t); + + var tokens = new HashSet( + txnDetailMap + .SelectMany(txn => txn.Value.TokenTransferreds + .Select(transfer => transfer.Symbol) + .Concat(txn.Value.NftsTransferreds + .Select(transfer => transfer.Symbol))) + ); - var tokenMap = await GetTokenMapAsync(txnDetails); + var tokenMap = await GetTokenMapAsync(tokens); var activityDtos = new List(); - foreach (var txnDetail in txnDetails) + foreach (var txn in txns.Transactions) { - activityDtos.Add(await ConvertDtoAsync(txnChainMap[txnDetail.TransactionId], txnDetail, tokenMap, request.Width, request.Height, request.AddressInfos[0].Address)); + if (txnDetailMap.ContainsKey(txn.TransactionId) && txnDetailMap[txn.TransactionId] != null) + { + activityDtos.Add(await ConvertDtoAsync(txn.ChainIds[0], txnDetailMap[txn.TransactionId], tokenMap, request.Width, request.Height, request.AddressInfos[0].Address)); + } + else + { + _logger.LogError($"Get transaction detail error. ChainId: {txn.ChainIds[0]}, TransactionId: {txn.TransactionId}"); + } } return new GetActivitiesDto() @@ -153,26 +169,9 @@ public async Task GetActivitiesAsync(GetActivitiesRequestDto r }; } - private async Task> GetTokenMapAsync(List txnDetails) + private async Task> GetTokenMapAsync(HashSet tokens) { - var result = new Dictionary(); - foreach (var transactionDetail in txnDetails) - { - foreach (var tokenTransferred in transactionDetail.TokenTransferreds) - { - if (!result.ContainsKey(tokenTransferred.Symbol)) - { - result[tokenTransferred.Symbol] = null; - } - } - foreach (var nftsTransferred in transactionDetail.NftsTransferreds) - { - if (!result.ContainsKey(nftsTransferred.Symbol)) - { - result[nftsTransferred.Symbol] = null; - } - } - } + var result = tokens.ToDictionary(t => t, t => new TokenInfoDto()); var sideChain = _chainOptions.ChainInfos.FirstOrDefault(t => t.Value.IsMainChain == false); var tokenChain = sideChain.Value.ChainId; diff --git a/src/EoaServer.Domain/DateTimeHelper.cs b/src/EoaServer.Domain/DateTimeHelper.cs new file mode 100644 index 0000000..d7cbec3 --- /dev/null +++ b/src/EoaServer.Domain/DateTimeHelper.cs @@ -0,0 +1,23 @@ +using System; + +namespace EoaServer; + +public class DateTimeHelper +{ + public static long ToUnixTimeMilliseconds(DateTime value) + { + var span = value - DateTime.UnixEpoch; + return (long) span.TotalMilliseconds; + } + + public static long ToUnixTimeSeconds(DateTime value) + { + var span = value - DateTime.UnixEpoch; + return (long) span.TotalMilliseconds / 1000; + } + + public static DateTime FromUnixTimeMilliseconds(long value) + { + return DateTime.UnixEpoch.AddMilliseconds(value); + } +} diff --git a/src/EoaServer.HttpApi.Host/appsettings.json b/src/EoaServer.HttpApi.Host/appsettings.json index a9acd72..981db19 100755 --- a/src/EoaServer.HttpApi.Host/appsettings.json +++ b/src/EoaServer.HttpApi.Host/appsettings.json @@ -93,7 +93,7 @@ }, "AElfScanOptions": { "BaseUrl": "https://testnet.aelfscan.io", - "Timeout": 3000 + "Timeout": 5000 }, "ActivityOptions": { "ActivityTransactionFeeFix": [ @@ -537,5 +537,15 @@ "portkey-im-testnet.s3.ap-northeast-1.amazonaws.com", "portkey-did.s3.ap-northeast-1.amazonaws.com" ] + }, + "GraphQLOptions": { + "IndexerOptions": { + "BlockChainIndexer": { + "BaseUrl": "https://gcptest-indexer-api.aefinder.io/api/app/graphql/blockchainapp" + }, + "TokenIndexer": { + "BaseUrl": "https://gcptest-indexer-api.aefinder.io/api/app/graphql/dailyholderapp" + } + } } }