diff --git a/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TokenTransferInput.cs b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/GetTokenTransferRequestDto.cs similarity index 94% rename from src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TokenTransferInput.cs rename to src/EoaServer.Application.Contracts/Provider/Dto/Indexer/GetTokenTransferRequestDto.cs index 5c58874..27301e0 100644 --- a/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TokenTransferInput.cs +++ b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/GetTokenTransferRequestDto.cs @@ -5,19 +5,16 @@ namespace EoaServer.Provider.Dto.Indexer; -public class TokenTransferInput : BaseInput +public class GetTokenTransferRequestDto : 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() @@ -29,8 +26,7 @@ public void SetDefaultSort() OfOrderInfos((SortField.BlockTime, SortDirection.Desc), (SortField.TransactionId, SortDirection.Desc)); } - - + public void SetBlockTimeSort() { if (!OrderBy.IsNullOrEmpty() || !OrderInfos.IsNullOrEmpty()) diff --git a/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/IndexerTransactionListResultDto.cs b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/IndexerTransactionListResultDto.cs new file mode 100644 index 0000000..c1fb178 --- /dev/null +++ b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/IndexerTransactionListResultDto.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using EoaServer.Commons; +using EoaServer.Token.Dto; + +namespace EoaServer.Provider.Dto.Indexer; + +public class IndexerTransactionResultDto +{ + public IndexerTransactionListResultDto TransactionInfos { get; set; } +} + +public class IndexerTransactionListResultDto +{ + public long TotalCount { get; set; } + public List Items { get; set; } = new(); +} + +public class IndexerTransactionInfoDto +{ + public string TransactionId { get; set; } + public long BlockHeight { get; set; } + public string MethodName { get; set; } + public TransactionStatus Status { get; set; } + public string From { get; set; } + public string To { get; set; } + public long TransactionValue { get; set; } + public MetadataDto Metadata { get; set; } + public long Fee { get; set; } +} \ No newline at end of file diff --git a/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TransactionsRequestDto.cs b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TransactionsRequestDto.cs new file mode 100644 index 0000000..1b16614 --- /dev/null +++ b/src/EoaServer.Application.Contracts/Provider/Dto/Indexer/TransactionsRequestDto.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace EoaServer.Provider.Dto.Indexer; + +public class TransactionsRequestDto : BaseInput +{ + public string TransactionId { get; set; } = ""; + public int BlockHeight { get; set; } + public long StartTime { get; set; } + public long EndTime { get; set; } + public string Address { get; set; } = ""; + + public void SetDefaultSort() + { + if (!OrderBy.IsNullOrEmpty() || !OrderInfos.IsNullOrEmpty()) + { + return; + } + + OfOrderInfos((SortField.BlockTime, SortDirection.Desc), (SortField.TransactionId, SortDirection.Desc)); + } + + + public void SetFirstTransactionSort() + { + if (!OrderBy.IsNullOrEmpty() || !OrderInfos.IsNullOrEmpty()) + { + return; + } + + OfOrderInfos((SortField.BlockHeight, SortDirection.Asc), (SortField.TransactionId, SortDirection.Asc)); + } + + public void SetLastTransactionSort() + { + if (!OrderBy.IsNullOrEmpty() || !OrderInfos.IsNullOrEmpty()) + { + return; + } + + OfOrderInfos((SortField.BlockHeight, SortDirection.Desc), (SortField.TransactionId, SortDirection.Desc)); + } +} diff --git a/src/EoaServer.Application.Contracts/Provider/IGraphQLProvider.cs b/src/EoaServer.Application.Contracts/Provider/IGraphQLProvider.cs index 0c6b06c..98de740 100644 --- a/src/EoaServer.Application.Contracts/Provider/IGraphQLProvider.cs +++ b/src/EoaServer.Application.Contracts/Provider/IGraphQLProvider.cs @@ -5,5 +5,6 @@ namespace EoaServer.Provider; public interface IGraphQLProvider { - public Task GetTokenTransferInfoAsync(TokenTransferInput input); + Task GetTokenTransferInfoAsync(GetTokenTransferRequestDto requestDto); + Task GetTransactionsAsync(TransactionsRequestDto input); } \ No newline at end of file diff --git a/src/EoaServer.Application/Provider/GraphQLProvider.cs b/src/EoaServer.Application/Provider/GraphQLProvider.cs index 841065e..90a534a 100644 --- a/src/EoaServer.Application/Provider/GraphQLProvider.cs +++ b/src/EoaServer.Application/Provider/GraphQLProvider.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using EoaServer.Options; using EoaServer.Provider.Dto.Indexer; @@ -38,10 +39,10 @@ public GraphQLProvider(IClusterClient clusterClient, _tokenAppService = tokenAppService; } - public async Task GetTokenTransferInfoAsync(TokenTransferInput input) + public async Task GetTokenTransferInfoAsync(GetTokenTransferRequestDto requestDto) { - input.SetDefaultSort(); - var indexerResult = await _tokenIndexerClient.SendQueryAsync(new GraphQLRequest + requestDto.SetDefaultSort(); + var graphQlResponse = await _tokenIndexerClient.SendQueryAsync(new GraphQLRequest { Query = @"query($chainId:String!,$symbol:String!,$address:String,$collectionSymbol:String, @@ -65,14 +66,77 @@ public async Task GetTokenTransferInfoAsync(TokenTr }", 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 + chainId = requestDto.ChainId, symbol = requestDto.Symbol, address = requestDto.Address, search = requestDto.Search, + skipCount = requestDto.SkipCount, maxResultCount = requestDto.MaxResultCount, + collectionSymbol = requestDto.CollectionSymbol, + sort = requestDto.Sort, fuzzySearch = requestDto.FuzzySearch, + orderInfos = requestDto.OrderInfos, searchAfter = requestDto.SearchAfter, beginBlockTime = requestDto.BeginBlockTime } }); - return indexerResult == null || indexerResult.Data == null ? - new IndexerTokenTransferListDto() : indexerResult.Data.TransferInfo; + + if (graphQlResponse.Errors != null) + { + ErrorLog(graphQlResponse.Errors); + return new IndexerTokenTransferListDto(); + } + + return graphQlResponse.Data.TransferInfo; + } + + public async Task GetTransactionsAsync(TransactionsRequestDto requestDto) + { + requestDto.SetDefaultSort(); + var graphQlResponse = await _blockChainIndexerClient.SendQueryAsync(new GraphQLRequest + { + Query = + @"query($chainId:String,$skipCount:Int!,$maxResultCount:Int!,$startTime:Long!,$endTime:Long!,$address:String!,$searchAfter:[String],$orderInfos:[OrderInfo]){ + transactionInfos(input: {chainId:$chainId,skipCount:$skipCount,maxResultCount:$maxResultCount,startTime:$startTime,endTime:$endTime,address:$address,searchAfter:$searchAfter,orderInfos:$orderInfos}) + { + totalCount + items { + transactionId + blockHeight + chainId + methodName + status + from + to + transactionValue + fee + metadata { + chainId + block { + blockHash + blockHeight + blockTime + } + } + } + } + }", + Variables = new + { + chainId = requestDto.ChainId, skipCount = requestDto.SkipCount, maxResultCount = requestDto.MaxResultCount, + startTime = requestDto.StartTime, + endTime = requestDto.EndTime, address = requestDto.Address, + orderInfos = requestDto.OrderInfos, searchAfter = requestDto.SearchAfter + } + }); + + if (graphQlResponse.Errors != null) + { + ErrorLog(graphQlResponse.Errors); + return new IndexerTransactionListResultDto(); + } + + return graphQlResponse.Data.TransactionInfos; + } + + private void ErrorLog(GraphQLError[] errors) + { + errors.ToList().ForEach(error => + { + _logger.Error("GraphQL error: {message}", error.Message); + }); } } \ No newline at end of file diff --git a/src/EoaServer.Application/UserActivity/UserActivityAppService.cs b/src/EoaServer.Application/UserActivity/UserActivityAppService.cs index 5d7f6a7..263d01d 100644 --- a/src/EoaServer.Application/UserActivity/UserActivityAppService.cs +++ b/src/EoaServer.Application/UserActivity/UserActivityAppService.cs @@ -79,17 +79,59 @@ public async Task GetActivityAsync(GetActivityRequestDto request var tokenMap = await GetTokenMapAsync(tokens); return await ConvertDtoAsync(request.ChainId, txnDto.List[0], tokenMap, 0, 0, request.AddressInfos[0].Address); } + + public List MergeTxns(IndexerTransactionListResultDto txns, IndexerTokenTransferListDto tokenTransfers) + { + var result = new List(); + if (txns != null) + { + foreach (var txn in txns.Items) + { + result.Add(new TransactionInfo() + { + ChainId = txn.Metadata.ChainId, + TransactionId = txn.TransactionId, + Timestamp = DateTimeHelper.ToUnixTimeSeconds(txn.Metadata.Block.BlockTime) + }); + } + } + + if (tokenTransfers != null) + { + foreach (var transfer in tokenTransfers.Items) + { + var txn = txns.Items.FirstOrDefault(t => t.TransactionId == transfer.TransactionId); + if (txn == null) + { + result.Add(new TransactionInfo + { + TransactionId = transfer.TransactionId, + ChainId = transfer.Metadata.ChainId, + Timestamp = DateTimeHelper.ToUnixTimeSeconds(transfer.Metadata.Block.BlockTime) + }); + } + } + } + + return result; + } public async Task GetActivitiesAsync(GetActivitiesRequestDto request) { var address = request.AddressInfos[0].Address; var chainId = request.AddressInfos.Count == 1 ? request.AddressInfos[0].ChainId : ""; - var txns = new TransactionsResponseDto(); + var txns = new IndexerTransactionListResultDto(); var tokenTransfers = new IndexerTokenTransferListDto(); - var txnsTask = _aelfScanDataProvider.GetAddressTransactionsAsync(chainId, address, 0, request.SkipCount + request.MaxResultCount); - var tokenTransfersTask = _graphqlProvider.GetTokenTransferInfoAsync(new TokenTransferInput() + var txnsTask = _graphqlProvider.GetTransactionsAsync(new TransactionsRequestDto() + { + ChainId = chainId, + Address = address, + SkipCount = 0, + MaxResultCount = request.SkipCount + request.MaxResultCount + }); + var tokenTransfersTask = _graphqlProvider.GetTokenTransferInfoAsync(new GetTokenTransferRequestDto() { Address = address, ChainId = chainId, @@ -99,43 +141,19 @@ public async Task GetActivitiesAsync(GetActivitiesRequestDto r }); await Task.WhenAll(txnsTask, tokenTransfersTask); - + txns = await txnsTask; - if (txns == null) - { - txns = new TransactionsResponseDto(); - } tokenTransfers = await tokenTransfersTask; + var transactions = MergeTxns(txns, tokenTransfers); - if (tokenTransfers != null) - { - foreach (var transfer in tokenTransfers.Items) - { - var txn = txns.Transactions.FirstOrDefault(t => t.TransactionId == transfer.TransactionId); - if (txn == null) - { - txns.Transactions.Add(new TransactionResponseDto - { - TransactionId = transfer.TransactionId, - ChainIds = new List(){transfer.Metadata.ChainId}, - Timestamp = DateTimeHelper.ToUnixTimeSeconds(transfer.Metadata.Block.BlockTime) - }); - } - } - } - - txns.Transactions = txns.Transactions.OrderByDescending(item => item.Timestamp) + transactions = transactions.OrderByDescending(item => item.Timestamp) .Skip(request.SkipCount) .Take(request.MaxResultCount) .ToList(); - var mapTasks = txns.Transactions.Select(async txn => + var mapTasks = transactions.Select(async txn => { - if (txn.ChainIds.Count < 1) - { - return null; - } - return await _aelfScanDataProvider.GetTransactionDetailAsync(txn.ChainIds[0], txn.TransactionId); + return await _aelfScanDataProvider.GetTransactionDetailAsync(txn.ChainId, txn.TransactionId); }).ToList(); var txnDetailMap = (await Task.WhenAll(mapTasks)) @@ -153,15 +171,15 @@ public async Task GetActivitiesAsync(GetActivitiesRequestDto r var tokenMap = await GetTokenMapAsync(tokens); var activityDtos = new List(); - foreach (var txn in txns.Transactions) + foreach (var txn in transactions) { 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)); + activityDtos.Add(await ConvertDtoAsync(txn.ChainId, 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}"); + _logger.LogError($"Get transaction detail error. ChainId: {txn.ChainId}, TransactionId: {txn.TransactionId}"); } } @@ -421,4 +439,11 @@ private async Task ConvertDtoAsync(string chainId, TransactionDe return activityDto; } + + public class TransactionInfo + { + public string ChainId { get; set; } + public string TransactionId { get; set; } + public long Timestamp { get; set; } + } } \ No newline at end of file diff --git a/test/EoaServer.Application.Tests/EoaServerApplicationTestModule.cs b/test/EoaServer.Application.Tests/EoaServerApplicationTestModule.cs index 6973ee0..070254a 100644 --- a/test/EoaServer.Application.Tests/EoaServerApplicationTestModule.cs +++ b/test/EoaServer.Application.Tests/EoaServerApplicationTestModule.cs @@ -1,9 +1,12 @@ +using System; using System.Collections.Generic; using EoaServer.Common; using EoaServer.Commons; using EoaServer.EntityEventHandler.Core; using EoaServer.Grain.Tests; using EoaServer.Options; +using EoaServer.Provider; +using EoaServer.Provider.Dto.Indexer; using EoaServer.Token; using EoaServer.Token.Dto; using EoaServer.UserActivity; @@ -234,14 +237,89 @@ public override void ConfigureServices(ServiceConfigurationContext context) private void MockData(ServiceConfigurationContext context) { var mockHttpProvider = new Mock(); - MockTokenListData(mockHttpProvider); MockTokenInfoData(mockHttpProvider); MockTransactionData(mockHttpProvider); MockAssetsData(mockHttpProvider); MockNftAssetsData(mockHttpProvider); - context.Services.AddSingleton(mockHttpProvider.Object); + + var mockGraphQLProvider = new Mock(); + MockTransactions(mockGraphQLProvider); + context.Services.AddSingleton(mockGraphQLProvider.Object); + } + + private void MockTransactions(Mock mockGraphQLProvider) + { + mockGraphQLProvider.Setup(provider => provider.GetTransactionsAsync( + It.Is( + req => req.Address == EoaServerApplicationTestConstant.User1Address + && req.ChainId == EoaServerApplicationTestConstant.ChainIdTDVW))) + .ReturnsAsync(new IndexerTransactionListResultDto() + { + Items = new List() + { + new IndexerTransactionInfoDto() + { + TransactionId = "0x1", + Metadata = new MetadataDto() + { + ChainId = EoaServerApplicationTestConstant.ChainIdTDVW, + Block = new BlockMetadataDto() + { + BlockTime = new DateTime(2000, 1, 1) + } + } + } + } + }); + + mockGraphQLProvider.Setup(provider => provider.GetTokenTransferInfoAsync( + It.Is( + req => req.Address == EoaServerApplicationTestConstant.User1Address + && req.ChainId == EoaServerApplicationTestConstant.ChainIdTDVW))) + .ReturnsAsync(new IndexerTokenTransferListDto() + { + Items = new List() + { + new IndexerTransferInfoDto() + { + TransactionId = "0x3", + Metadata = new MetadataDto() + { + ChainId = EoaServerApplicationTestConstant.ChainIdTDVW, + Block = new BlockMetadataDto() + { + BlockTime = new DateTime(2000, 1, 3) + } + } + }, + new IndexerTransferInfoDto() + { + TransactionId = "0x2", + Metadata = new MetadataDto() + { + ChainId = EoaServerApplicationTestConstant.ChainIdTDVW, + Block = new BlockMetadataDto() + { + BlockTime = new DateTime(2000, 1, 2) + } + } + }, + new IndexerTransferInfoDto() + { + TransactionId = "0x1", + Metadata = new MetadataDto() + { + ChainId = EoaServerApplicationTestConstant.ChainIdTDVW, + Block = new BlockMetadataDto() + { + BlockTime = new DateTime(2000, 1, 1) + } + } + } + } + }); } private void MockNftAssetsData(Mock mockHttpProvider)