From 8f36425ccff098bf74b3baa2bf4589dd60ebd592 Mon Sep 17 00:00:00 2001 From: ArdenHide Date: Wed, 12 Nov 2025 16:53:49 +0300 Subject: [PATCH 1/9] Retries for HTTP calls via Polly lib --- src/MetaDataAPI/DefaultServiceProvider.cs | 2 ++ src/MetaDataAPI/MetaDataAPI.csproj | 1 + .../Services/Http/FailureOnlyLoggingHandler.cs | 15 ++++++++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/MetaDataAPI/DefaultServiceProvider.cs b/src/MetaDataAPI/DefaultServiceProvider.cs index daed75c6..d16ab0fa 100644 --- a/src/MetaDataAPI/DefaultServiceProvider.cs +++ b/src/MetaDataAPI/DefaultServiceProvider.cs @@ -5,6 +5,7 @@ using MetaDataAPI.Services.Strapi; using GraphQL.Client.Abstractions; using MetaDataAPI.Services.ChainsInfo; +using Poolz.Finance.CSharp.Polly.Extensions; using Microsoft.Extensions.DependencyInjection; using poolz.finance.csharp.contracts.LockDealNFT; using MediatR.Extensions.FluentValidation.AspNetCore; @@ -20,6 +21,7 @@ public static class DefaultServiceProvider serviceCollection.AddMediatR(x => x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); serviceCollection.AddFluentValidation([Assembly.GetExecutingAssembly()]); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/src/MetaDataAPI/MetaDataAPI.csproj b/src/MetaDataAPI/MetaDataAPI.csproj index 9fb9a19c..01314fd4 100644 --- a/src/MetaDataAPI/MetaDataAPI.csproj +++ b/src/MetaDataAPI/MetaDataAPI.csproj @@ -21,6 +21,7 @@ + diff --git a/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs b/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs index 32d79965..7adbeaf4 100644 --- a/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs +++ b/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs @@ -1,13 +1,22 @@ -namespace MetaDataAPI.Services.Http; +using Amazon.Lambda.Core; +using Poolz.Finance.CSharp.Polly.Extensions; -public class FailureOnlyLoggingHandler(HttpMessageHandler inner) : DelegatingHandler(inner) +namespace MetaDataAPI.Services.Http; + +public class FailureOnlyLoggingHandler(HttpMessageHandler inner, IRetryExecutor retry) : DelegatingHandler(inner) { protected override async Task SendAsync(HttpRequestMessage req, CancellationToken ct) { try { - var response = await base.SendAsync(req, ct); + var response = await retry.ExecuteAsync( + async token => await base.SendAsync(req, token), + new DefaultRetryStrategyOptions(LambdaLogger.Log), + ct + ); + response.EnsureSuccessStatusCode(); + return response; } catch (HttpRequestException exception) From ebcc34be2aab6943e003dbf73dd52400368004cf Mon Sep 17 00:00:00 2001 From: ArdenHide Date: Wed, 12 Nov 2025 17:18:48 +0300 Subject: [PATCH 2/9] - update retrying code - use dependencies --- .../Services/Http/FailureOnlyLoggingHandler.cs | 9 ++++++--- src/MetaDataAPI/Services/Http/HttpClientFactory.cs | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs b/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs index 7adbeaf4..e519db01 100644 --- a/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs +++ b/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs @@ -10,13 +10,16 @@ protected override async Task SendAsync(HttpRequestMessage try { var response = await retry.ExecuteAsync( - async token => await base.SendAsync(req, token), + async token => + { + var response = await base.SendAsync(req, token); + response.EnsureSuccessStatusCode(); + return response; + }, new DefaultRetryStrategyOptions(LambdaLogger.Log), ct ); - response.EnsureSuccessStatusCode(); - return response; } catch (HttpRequestException exception) diff --git a/src/MetaDataAPI/Services/Http/HttpClientFactory.cs b/src/MetaDataAPI/Services/Http/HttpClientFactory.cs index 70d327f1..6f627751 100644 --- a/src/MetaDataAPI/Services/Http/HttpClientFactory.cs +++ b/src/MetaDataAPI/Services/Http/HttpClientFactory.cs @@ -1,12 +1,13 @@ using System.Net.Http.Headers; +using Poolz.Finance.CSharp.Polly.Extensions; namespace MetaDataAPI.Services.Http; -public class HttpClientFactory : IHttpClientFactory +public class HttpClientFactory(IRetryExecutor retry) : IHttpClientFactory { public HttpClient Create(string url, Action? configureHeaders = null) { - var client = new HttpClient(new FailureOnlyLoggingHandler(new HttpClientHandler())) + var client = new HttpClient(new FailureOnlyLoggingHandler(new HttpClientHandler(), retry)) { BaseAddress = new Uri(url) }; From d1c7f417aca3f2e937eebe0651afc79af81af333 Mon Sep 17 00:00:00 2001 From: ArdenHide Date: Thu, 13 Nov 2025 12:45:12 +0300 Subject: [PATCH 3/9] - save code --- src/MetaDataAPI/DefaultServiceProvider.cs | 56 ++++++++++++++++++- src/MetaDataAPI/LambdaFunction.cs | 7 +++ src/MetaDataAPI/MetaDataAPI.csproj | 1 + .../Http/FailureOnlyLoggingHandler.cs | 24 ++++---- .../Services/Http/HttpClientFactory.cs | 12 ++-- .../Services/Strapi/StrapiGraphQLClient.cs | 2 +- 6 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/MetaDataAPI/DefaultServiceProvider.cs b/src/MetaDataAPI/DefaultServiceProvider.cs index d16ab0fa..bb5e191e 100644 --- a/src/MetaDataAPI/DefaultServiceProvider.cs +++ b/src/MetaDataAPI/DefaultServiceProvider.cs @@ -1,4 +1,7 @@ -using System.Reflection; +using Polly; +using System.Reflection; +using Amazon.Lambda.Core; +using Flurl.Http.Configuration; using Net.Cache.DynamoDb.ERC20; using MetaDataAPI.Services.Http; using MetaDataAPI.Services.Erc20; @@ -9,6 +12,9 @@ using Microsoft.Extensions.DependencyInjection; using poolz.finance.csharp.contracts.LockDealNFT; using MediatR.Extensions.FluentValidation.AspNetCore; +using IHttpClientFactory = Flurl.Http.Configuration.IHttpClientFactory; +using Polly.Retry; +using Polly.Timeout; namespace MetaDataAPI; @@ -21,8 +27,54 @@ public static class DefaultServiceProvider serviceCollection.AddMediatR(x => x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); serviceCollection.AddFluentValidation([Assembly.GetExecutingAssembly()]); + serviceCollection.AddTransient(); + + serviceCollection + .AddHttpClient(HttpClientFactory.GenericClientName, client => + { + client.Timeout = Timeout.InfiniteTimeSpan; + }) + .AddHttpMessageHandler() + .AddResilienceHandler("genericClient", pipeline => + { + pipeline.AddRetry(new RetryStrategyOptions + { + MaxRetryAttempts = 3, + BackoffType = DelayBackoffType.Exponential, + UseJitter = true, + Delay = TimeSpan.FromMilliseconds(200), + ShouldHandle = args => + { + var exception = args.Outcome.Exception; + + return new ValueTask( + exception is HttpRequestException + or TaskCanceledException + or TimeoutRejectedException + ); + }, + OnRetry = args => + { + if (args.Outcome.Exception is { } ex) + { + var message = + $"[Retry] Attempt={args.AttemptNumber + 1}, Delay={args.RetryDelay}, Exception={ex.GetType().Name}: {ex.Message}"; + LambdaLogger.Log(message); + } + + return default; + } + }); + + pipeline.AddTimeout(TimeSpan.FromSeconds(3)); + }); + + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton( + provider => provider.GetRequiredService() + ); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/src/MetaDataAPI/LambdaFunction.cs b/src/MetaDataAPI/LambdaFunction.cs index 9bdd28b9..61b05078 100644 --- a/src/MetaDataAPI/LambdaFunction.cs +++ b/src/MetaDataAPI/LambdaFunction.cs @@ -6,6 +6,8 @@ using MetaDataAPI.Routing.Requests; using Microsoft.Extensions.DependencyInjection; using Amazon.Lambda.ApplicationLoadBalancerEvents; +using MetaDataAPI.Extensions; +using MetaDataAPI.Services.Http; [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] @@ -23,6 +25,11 @@ public async Task FunctionHandler(ApplicationLoadBalancerRequest try { + var web3Factory = scope.ServiceProvider.GetRequiredService(); + var web3 = web3Factory.Create("https://binance.llamarpc.com"); + var _ = await web3.Client.SendRequestAsync("eth_this_method_does_not_exist"); + + return await mediator.Send(new RouteApplicationLoadBalancerRequest(request)); } catch (ValidationException ex) diff --git a/src/MetaDataAPI/MetaDataAPI.csproj b/src/MetaDataAPI/MetaDataAPI.csproj index 01314fd4..5a898ab9 100644 --- a/src/MetaDataAPI/MetaDataAPI.csproj +++ b/src/MetaDataAPI/MetaDataAPI.csproj @@ -18,6 +18,7 @@ + diff --git a/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs b/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs index e519db01..e653a812 100644 --- a/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs +++ b/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs @@ -1,29 +1,27 @@ using Amazon.Lambda.Core; -using Poolz.Finance.CSharp.Polly.Extensions; namespace MetaDataAPI.Services.Http; -public class FailureOnlyLoggingHandler(HttpMessageHandler inner, IRetryExecutor retry) : DelegatingHandler(inner) +public class FailureOnlyLoggingHandler : DelegatingHandler { + public FailureOnlyLoggingHandler() { } + + public FailureOnlyLoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { } + protected override async Task SendAsync(HttpRequestMessage req, CancellationToken ct) { try { - var response = await retry.ExecuteAsync( - async token => - { - var response = await base.SendAsync(req, token); - response.EnsureSuccessStatusCode(); - return response; - }, - new DefaultRetryStrategyOptions(LambdaLogger.Log), - ct - ); - + var response = await base.SendAsync(req, ct); + response.EnsureSuccessStatusCode(); return response; } catch (HttpRequestException exception) { + LambdaLogger.Log( + $"HTTP request failed. METHOD: {req.Method}. URL: {req.RequestUri}. STATUS: {exception.StatusCode}" + ); + throw new HttpRequestException( $"HTTP request failed. METHOD: {req.Method}. URL: {req.RequestUri}", exception, diff --git a/src/MetaDataAPI/Services/Http/HttpClientFactory.cs b/src/MetaDataAPI/Services/Http/HttpClientFactory.cs index 6f627751..9ead7620 100644 --- a/src/MetaDataAPI/Services/Http/HttpClientFactory.cs +++ b/src/MetaDataAPI/Services/Http/HttpClientFactory.cs @@ -1,18 +1,18 @@ using System.Net.Http.Headers; -using Poolz.Finance.CSharp.Polly.Extensions; namespace MetaDataAPI.Services.Http; -public class HttpClientFactory(IRetryExecutor retry) : IHttpClientFactory +public class HttpClientFactory(System.Net.Http.IHttpClientFactory innerFactory) : IHttpClientFactory { + public const string GenericClientName = "GenericClient"; + public HttpClient Create(string url, Action? configureHeaders = null) { - var client = new HttpClient(new FailureOnlyLoggingHandler(new HttpClientHandler(), retry)) - { - BaseAddress = new Uri(url) - }; + var client = innerFactory.CreateClient(GenericClientName); + client.BaseAddress = new Uri(url); configureHeaders?.Invoke(client.DefaultRequestHeaders); + return client; } } \ No newline at end of file diff --git a/src/MetaDataAPI/Services/Strapi/StrapiGraphQLClient.cs b/src/MetaDataAPI/Services/Strapi/StrapiGraphQLClient.cs index 5cf174d1..e35340b1 100644 --- a/src/MetaDataAPI/Services/Strapi/StrapiGraphQLClient.cs +++ b/src/MetaDataAPI/Services/Strapi/StrapiGraphQLClient.cs @@ -1,8 +1,8 @@ using GraphQL.Client.Http; using System.Net.Http.Headers; -using MetaDataAPI.Services.Http; using EnvironmentManager.Extensions; using GraphQL.Client.Serializer.Newtonsoft; +using IHttpClientFactory = MetaDataAPI.Services.Http.IHttpClientFactory; namespace MetaDataAPI.Services.Strapi; From 2fbac3cda8ac46f445512cc62d74c61fd8e938cf Mon Sep 17 00:00:00 2001 From: ArdenHide Date: Thu, 13 Nov 2025 13:00:25 +0300 Subject: [PATCH 4/9] - cleanup --- src/MetaDataAPI/DefaultServiceProvider.cs | 56 +------------------ src/MetaDataAPI/LambdaFunction.cs | 7 --- src/MetaDataAPI/MetaDataAPI.csproj | 1 - .../Http/FailureOnlyLoggingHandler.cs | 14 +---- .../Services/Http/HttpClientFactory.cs | 10 ++-- .../Services/Strapi/StrapiGraphQLClient.cs | 2 +- 6 files changed, 10 insertions(+), 80 deletions(-) diff --git a/src/MetaDataAPI/DefaultServiceProvider.cs b/src/MetaDataAPI/DefaultServiceProvider.cs index bb5e191e..0664e641 100644 --- a/src/MetaDataAPI/DefaultServiceProvider.cs +++ b/src/MetaDataAPI/DefaultServiceProvider.cs @@ -1,7 +1,4 @@ -using Polly; -using System.Reflection; -using Amazon.Lambda.Core; -using Flurl.Http.Configuration; +using System.Reflection; using Net.Cache.DynamoDb.ERC20; using MetaDataAPI.Services.Http; using MetaDataAPI.Services.Erc20; @@ -12,9 +9,6 @@ using Microsoft.Extensions.DependencyInjection; using poolz.finance.csharp.contracts.LockDealNFT; using MediatR.Extensions.FluentValidation.AspNetCore; -using IHttpClientFactory = Flurl.Http.Configuration.IHttpClientFactory; -using Polly.Retry; -using Polly.Timeout; namespace MetaDataAPI; @@ -27,54 +21,8 @@ public static class DefaultServiceProvider serviceCollection.AddMediatR(x => x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); serviceCollection.AddFluentValidation([Assembly.GetExecutingAssembly()]); - serviceCollection.AddTransient(); - - serviceCollection - .AddHttpClient(HttpClientFactory.GenericClientName, client => - { - client.Timeout = Timeout.InfiniteTimeSpan; - }) - .AddHttpMessageHandler() - .AddResilienceHandler("genericClient", pipeline => - { - pipeline.AddRetry(new RetryStrategyOptions - { - MaxRetryAttempts = 3, - BackoffType = DelayBackoffType.Exponential, - UseJitter = true, - Delay = TimeSpan.FromMilliseconds(200), - ShouldHandle = args => - { - var exception = args.Outcome.Exception; - - return new ValueTask( - exception is HttpRequestException - or TaskCanceledException - or TimeoutRejectedException - ); - }, - OnRetry = args => - { - if (args.Outcome.Exception is { } ex) - { - var message = - $"[Retry] Attempt={args.AttemptNumber + 1}, Delay={args.RetryDelay}, Exception={ex.GetType().Name}: {ex.Message}"; - LambdaLogger.Log(message); - } - - return default; - } - }); - - pipeline.AddTimeout(TimeSpan.FromSeconds(3)); - }); - - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton( - provider => provider.GetRequiredService() - ); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/src/MetaDataAPI/LambdaFunction.cs b/src/MetaDataAPI/LambdaFunction.cs index 61b05078..9bdd28b9 100644 --- a/src/MetaDataAPI/LambdaFunction.cs +++ b/src/MetaDataAPI/LambdaFunction.cs @@ -6,8 +6,6 @@ using MetaDataAPI.Routing.Requests; using Microsoft.Extensions.DependencyInjection; using Amazon.Lambda.ApplicationLoadBalancerEvents; -using MetaDataAPI.Extensions; -using MetaDataAPI.Services.Http; [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] @@ -25,11 +23,6 @@ public async Task FunctionHandler(ApplicationLoadBalancerRequest try { - var web3Factory = scope.ServiceProvider.GetRequiredService(); - var web3 = web3Factory.Create("https://binance.llamarpc.com"); - var _ = await web3.Client.SendRequestAsync("eth_this_method_does_not_exist"); - - return await mediator.Send(new RouteApplicationLoadBalancerRequest(request)); } catch (ValidationException ex) diff --git a/src/MetaDataAPI/MetaDataAPI.csproj b/src/MetaDataAPI/MetaDataAPI.csproj index 5a898ab9..01314fd4 100644 --- a/src/MetaDataAPI/MetaDataAPI.csproj +++ b/src/MetaDataAPI/MetaDataAPI.csproj @@ -18,7 +18,6 @@ - diff --git a/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs b/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs index e653a812..0d66f056 100644 --- a/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs +++ b/src/MetaDataAPI/Services/Http/FailureOnlyLoggingHandler.cs @@ -1,13 +1,7 @@ -using Amazon.Lambda.Core; +namespace MetaDataAPI.Services.Http; -namespace MetaDataAPI.Services.Http; - -public class FailureOnlyLoggingHandler : DelegatingHandler +public class FailureOnlyLoggingHandler(HttpMessageHandler innerHandler) : DelegatingHandler(innerHandler) { - public FailureOnlyLoggingHandler() { } - - public FailureOnlyLoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { } - protected override async Task SendAsync(HttpRequestMessage req, CancellationToken ct) { try @@ -18,10 +12,6 @@ protected override async Task SendAsync(HttpRequestMessage } catch (HttpRequestException exception) { - LambdaLogger.Log( - $"HTTP request failed. METHOD: {req.Method}. URL: {req.RequestUri}. STATUS: {exception.StatusCode}" - ); - throw new HttpRequestException( $"HTTP request failed. METHOD: {req.Method}. URL: {req.RequestUri}", exception, diff --git a/src/MetaDataAPI/Services/Http/HttpClientFactory.cs b/src/MetaDataAPI/Services/Http/HttpClientFactory.cs index 9ead7620..d9af16c1 100644 --- a/src/MetaDataAPI/Services/Http/HttpClientFactory.cs +++ b/src/MetaDataAPI/Services/Http/HttpClientFactory.cs @@ -2,14 +2,14 @@ namespace MetaDataAPI.Services.Http; -public class HttpClientFactory(System.Net.Http.IHttpClientFactory innerFactory) : IHttpClientFactory +public class HttpClientFactory : IHttpClientFactory { - public const string GenericClientName = "GenericClient"; - public HttpClient Create(string url, Action? configureHeaders = null) { - var client = innerFactory.CreateClient(GenericClientName); - client.BaseAddress = new Uri(url); + var client = new HttpClient(new FailureOnlyLoggingHandler(new HttpClientHandler())) + { + BaseAddress = new Uri(url) + }; configureHeaders?.Invoke(client.DefaultRequestHeaders); diff --git a/src/MetaDataAPI/Services/Strapi/StrapiGraphQLClient.cs b/src/MetaDataAPI/Services/Strapi/StrapiGraphQLClient.cs index e35340b1..5cf174d1 100644 --- a/src/MetaDataAPI/Services/Strapi/StrapiGraphQLClient.cs +++ b/src/MetaDataAPI/Services/Strapi/StrapiGraphQLClient.cs @@ -1,8 +1,8 @@ using GraphQL.Client.Http; using System.Net.Http.Headers; +using MetaDataAPI.Services.Http; using EnvironmentManager.Extensions; using GraphQL.Client.Serializer.Newtonsoft; -using IHttpClientFactory = MetaDataAPI.Services.Http.IHttpClientFactory; namespace MetaDataAPI.Services.Strapi; From a65c5899332965062b305b0db043df7f7d45cd9f Mon Sep 17 00:00:00 2001 From: ArdenHide Date: Thu, 13 Nov 2025 13:11:46 +0300 Subject: [PATCH 5/9] - include retries --- .../Requests/GetMetadataRequestHandler.cs | 10 +++++++--- .../Services/Erc20/Erc20Provider.cs | 20 +++++++++++++------ .../Services/Http/HttpClientFactory.cs | 1 - .../Services/Strapi/StrapiClient.cs | 7 +++++-- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs b/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs index 37c7d3a2..1ffa0a28 100644 --- a/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs +++ b/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs @@ -5,6 +5,7 @@ using MetaDataAPI.Models.Errors; using MetaDataAPI.Services.Http; using MetaDataAPI.Services.ChainsInfo; +using Poolz.Finance.CSharp.Polly.Extensions; using poolz.finance.csharp.contracts.LockDealNFT; namespace MetaDataAPI.Routing.Requests; @@ -13,7 +14,8 @@ public class GetMetadataRequestHandler( IServiceProvider serviceProvider, IChainManager chainManager, ILockDealNFTService lockDealNft, - IWeb3Factory web3Factory + IWeb3Factory web3Factory, + IRetryExecutor retry ) : IRequestHandler { public Task Handle(GetMetadataRequest request, CancellationToken cancellationToken) @@ -28,9 +30,11 @@ public Task Handle(GetMetadataRequest request, CancellationToken } lockDealNft.Initialize(web3Factory.Create(chainId.ToRpcUrl()), chainInfo.LockDealNFT); - if (!lockDealNft.IsPoolIdInSupplyRange(poolId)) + var isPoolIdInSupplyRange = retry.Execute(_ => lockDealNft.IsPoolIdInSupplyRange(poolId), ct: cancellationToken); + + if (!isPoolIdInSupplyRange) { - return Task.FromResult(new PoolIdNotInSupplyRangeResponse(poolId)); + return Task.FromResult(new PoolIdNotInSupplyRangeResponse(chainId)); } var poolsInfo = lockDealNft.FetchPoolInfo(poolId); diff --git a/src/MetaDataAPI/Services/Erc20/Erc20Provider.cs b/src/MetaDataAPI/Services/Erc20/Erc20Provider.cs index f44b8d8a..2b46dc0b 100644 --- a/src/MetaDataAPI/Services/Erc20/Erc20Provider.cs +++ b/src/MetaDataAPI/Services/Erc20/Erc20Provider.cs @@ -4,11 +4,16 @@ using MetaDataAPI.Services.Http; using EnvironmentManager.Extensions; using MetaDataAPI.Services.ChainsInfo; +using Poolz.Finance.CSharp.Polly.Extensions; using Net.Cache.DynamoDb.ERC20.DynamoDb.Models; namespace MetaDataAPI.Services.Erc20; -public class Erc20Provider(IErc20CacheService erc20Cache, IWeb3Factory web3Factory) : IErc20Provider +public class Erc20Provider( + IErc20CacheService erc20Cache, + IWeb3Factory web3Factory, + IRetryExecutor retry +) : IErc20Provider { public Erc20Token GetErc20Token(ChainInfo chainInfo, EthereumAddress address) { @@ -17,11 +22,14 @@ public Erc20Token GetErc20Token(ChainInfo chainInfo, EthereumAddress address) public Erc20Token GetErc20Token(string rpcUrl, long chainId, EthereumAddress address) { - var cache = erc20Cache.GetOrAddAsync( - new HashKey(chainId, address), - () => Task.FromResult(web3Factory.Create(rpcUrl)), - () => Task.FromResult(new EthereumAddress(Env.MULTI_CALL_V3_ADDRESS.GetRequired())) - ).GetAwaiter().GetResult(); + var cache = retry.Execute(_ => + { + return erc20Cache.GetOrAddAsync( + new HashKey(chainId, address), + () => Task.FromResult(web3Factory.Create(rpcUrl)), + () => Task.FromResult(new EthereumAddress(Env.MULTI_CALL_V3_ADDRESS.GetRequired())) + ).GetAwaiter().GetResult(); + }); return new Erc20Token(cache); } } \ No newline at end of file diff --git a/src/MetaDataAPI/Services/Http/HttpClientFactory.cs b/src/MetaDataAPI/Services/Http/HttpClientFactory.cs index d9af16c1..70d327f1 100644 --- a/src/MetaDataAPI/Services/Http/HttpClientFactory.cs +++ b/src/MetaDataAPI/Services/Http/HttpClientFactory.cs @@ -12,7 +12,6 @@ public HttpClient Create(string url, Action? configureHeader }; configureHeaders?.Invoke(client.DefaultRequestHeaders); - return client; } } \ No newline at end of file diff --git a/src/MetaDataAPI/Services/Strapi/StrapiClient.cs b/src/MetaDataAPI/Services/Strapi/StrapiClient.cs index 8edf8c03..9b385d66 100644 --- a/src/MetaDataAPI/Services/Strapi/StrapiClient.cs +++ b/src/MetaDataAPI/Services/Strapi/StrapiClient.cs @@ -4,10 +4,11 @@ using Net.Utils.GraphQL.Extensions; using MetaDataAPI.Services.ChainsInfo; using MetaDataAPI.Services.Strapi.Models; +using Poolz.Finance.CSharp.Polly.Extensions; namespace MetaDataAPI.Services.Strapi; -public class StrapiClient(IGraphQLClient graphQlClient) : IStrapiClient +public class StrapiClient(IGraphQLClient graphQlClient, IRetryExecutor retry) : IStrapiClient { private const string NameOfLockDealNFT = "LockDealNFT"; @@ -55,7 +56,9 @@ public class StrapiClient(IGraphQLClient graphQlClient) : IStrapiClient .WithParameter(chainFilter) .Build(); - var response = await graphQlClient.SendQueryAsync(new GraphQLQuery(query)); + var response = await retry.ExecuteAsync(async token => + await graphQlClient.SendQueryAsync(new GraphQLQuery(query), cancellationToken: token) + ); var data = response.EnsureNoErrors(); From f86e90d908e05d0ceea2b182381b503209fb663d Mon Sep 17 00:00:00 2001 From: ArdenHide Date: Thu, 13 Nov 2025 13:17:43 +0300 Subject: [PATCH 6/9] - use retries for getData rpc call --- src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs b/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs index 1ffa0a28..262da7b6 100644 --- a/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs +++ b/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs @@ -37,7 +37,7 @@ public Task Handle(GetMetadataRequest request, CancellationToken return Task.FromResult(new PoolIdNotInSupplyRangeResponse(chainId)); } - var poolsInfo = lockDealNft.FetchPoolInfo(poolId); + var poolsInfo = retry.Execute(_ => lockDealNft.FetchPoolInfo(poolId), ct: cancellationToken); var provider = AbstractProvider.CreateFromPoolInfo(poolsInfo, chainInfo, serviceProvider); var metadata = provider.GetErc721Metadata(); From 37891d4bb80bcd752e7718a873d57a6afda51e06 Mon Sep 17 00:00:00 2001 From: ArdenHide Date: Thu, 13 Nov 2025 14:49:11 +0300 Subject: [PATCH 7/9] - fix test --- .../Services/Erc20/Erc20ProviderTests.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/MetaDataAPI.Tests/Services/Erc20/Erc20ProviderTests.cs b/tests/MetaDataAPI.Tests/Services/Erc20/Erc20ProviderTests.cs index 6d0de437..2c23ef5c 100644 --- a/tests/MetaDataAPI.Tests/Services/Erc20/Erc20ProviderTests.cs +++ b/tests/MetaDataAPI.Tests/Services/Erc20/Erc20ProviderTests.cs @@ -10,6 +10,7 @@ using MetaDataAPI.Services.ChainsInfo; using Net.Cache.DynamoDb.ERC20.Rpc.Models; using Net.Cache.DynamoDb.ERC20.DynamoDb.Models; +using Poolz.Finance.CSharp.Polly.Extensions; namespace MetaDataAPI.Tests.Services.Erc20; @@ -26,6 +27,7 @@ internal void ShouldReceiveExpectedErc20FromDynamoDb() Environment.SetEnvironmentVariable(nameof(Env.BASE_URL_OF_RPC), baseRpcUrl); Environment.SetEnvironmentVariable(nameof(Env.MULTI_CALL_V3_ADDRESS), EthereumAddress.ZeroAddress); + var retryExecutor = new Mock(); var cacheProvider = new Mock(); var web3Factory = new Mock(); var web3 = new Mock(); @@ -47,7 +49,15 @@ internal void ShouldReceiveExpectedErc20FromDynamoDb() )) .ReturnsAsync(cacheItem); - var provider = new Erc20Provider(cacheProvider.Object, web3Factory.Object); + retryExecutor + .Setup(x => x.Execute( + It.IsAny>(), + It.IsAny>(), + It.IsAny() + )) + .Returns((Func action, CancellationToken token) => action(token)); + + var provider = new Erc20Provider(cacheProvider.Object, web3Factory.Object, retryExecutor.Object); var result = provider.GetErc20Token( new ChainInfo(chainId, EthereumAddress.ZeroAddress), From 43585521ba7a0795e4e48b7cb80ed01179f6f23a Mon Sep 17 00:00:00 2001 From: ArdenHide Date: Thu, 13 Nov 2025 14:51:54 +0300 Subject: [PATCH 8/9] - fix test --- tests/MetaDataAPI.Tests/Services/Erc20/Erc20ProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MetaDataAPI.Tests/Services/Erc20/Erc20ProviderTests.cs b/tests/MetaDataAPI.Tests/Services/Erc20/Erc20ProviderTests.cs index 2c23ef5c..9cca7f4e 100644 --- a/tests/MetaDataAPI.Tests/Services/Erc20/Erc20ProviderTests.cs +++ b/tests/MetaDataAPI.Tests/Services/Erc20/Erc20ProviderTests.cs @@ -55,7 +55,7 @@ internal void ShouldReceiveExpectedErc20FromDynamoDb() It.IsAny>(), It.IsAny() )) - .Returns((Func action, CancellationToken token) => action(token)); + .Returns((Func action, DefaultRetryStrategyOptions _, CancellationToken token) => action(token)); var provider = new Erc20Provider(cacheProvider.Object, web3Factory.Object, retryExecutor.Object); From 217a6fc4db555b02743c643fc603b7be3caf69f5 Mon Sep 17 00:00:00 2001 From: Stanislav Vysotskyi <66203238+ArdenHide@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:15:25 +0300 Subject: [PATCH 9/9] Update src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs b/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs index 262da7b6..f06ca55f 100644 --- a/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs +++ b/src/MetaDataAPI/Routing/Requests/GetMetadataRequestHandler.cs @@ -34,7 +34,7 @@ public Task Handle(GetMetadataRequest request, CancellationToken if (!isPoolIdInSupplyRange) { - return Task.FromResult(new PoolIdNotInSupplyRangeResponse(chainId)); + return Task.FromResult(new PoolIdNotInSupplyRangeResponse(poolId)); } var poolsInfo = retry.Execute(_ => lockDealNft.FetchPoolInfo(poolId), ct: cancellationToken);