From 253ccbaadf04266e5ce3b1bd401f3c6e5b4b8fc7 Mon Sep 17 00:00:00 2001 From: Aram Kocharyan Date: Sun, 27 Apr 2025 01:14:32 +0400 Subject: [PATCH 01/19] Add build scriptis --- .github/workflows/station.yml | 66 +++++++++++++++++++++++++++++++++++ TrainStation/Dockerfile | 19 ++++++++++ 2 files changed, 85 insertions(+) create mode 100644 .github/workflows/station.yml create mode 100644 TrainStation/Dockerfile diff --git a/.github/workflows/station.yml b/.github/workflows/station.yml new file mode 100644 index 0000000..acffcfe --- /dev/null +++ b/.github/workflows/station.yml @@ -0,0 +1,66 @@ +name: API + +on: + push: + branches: [ main, dev ] + paths: + - 'TrainStation/**' + - '.github/workflows/station.yml' + pull_request: + branches: [ main, dev ] + paths: + - 'TrainStation/**' + - '.github/workflows/station.yml' + release: + types: [published, created] + +env: + DOCKER_IMAGE: trainprotocol/station + DOTNET_VERSION: 9 + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Format version + id: format_version + run: | + commitHash=${GITHUB_SHA:0:7} + ts=$(date +%s) + version=$commitHash-$ts + echo "VERSION=$version" >> $GITHUB_ENV + + - name: Sanitize branch name + id: sanitize_branch_name + run: | + sanitized_ref_name=$(echo "${GITHUB_REF_NAME}" | sed 's/[\/.]/-/g') + echo "SANITIZED_REF_NAME=$sanitized_ref_name" >> $GITHUB_ENV + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: TrainStation/Dockerfile + push: ${{github.ref_name == 'main' || github.ref_name == 'dev'}} + build-args: | + DOTNET_VERSION=${{ env.DOTNET_VERSION }} + tags: | + ${{ env.DOCKER_IMAGE }}:${{ env.SANITIZED_REF_NAME }}-${{ env.VERSION }} + ${{ env.DOCKER_IMAGE }}:${{ env.SANITIZED_REF_NAME }} + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/TrainStation/Dockerfile b/TrainStation/Dockerfile new file mode 100644 index 0000000..eaf3a98 --- /dev/null +++ b/TrainStation/Dockerfile @@ -0,0 +1,19 @@ +ARG DOTNET_VERSION + +FROM mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION}.0 AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}.0 AS build +WORKDIR /build + +COPY csharp/ csharp/ + +FROM build AS publish +RUN dotnet publish "TrainStation/API.csproj" -c Release -o /app/publish + +FROM base AS final + +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Train.Station.API.dll"] +EXPOSE 8080 \ No newline at end of file From a86f1ab22b1ff06af40a34fbd918a2284025c871 Mon Sep 17 00:00:00 2001 From: Aram Kocharyan Date: Sun, 27 Apr 2025 01:15:34 +0400 Subject: [PATCH 02/19] Fix DockerFile --- TrainStation/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TrainStation/Dockerfile b/TrainStation/Dockerfile index eaf3a98..3392422 100644 --- a/TrainStation/Dockerfile +++ b/TrainStation/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /app FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}.0 AS build WORKDIR /build -COPY csharp/ csharp/ +COPY TrainStation/ TrainStation/ FROM build AS publish RUN dotnet publish "TrainStation/API.csproj" -c Release -o /app/publish From 79f26316e11a6f570e35935a98b9451d265a0c8a Mon Sep 17 00:00:00 2001 From: Aram Kocharyan Date: Sun, 27 Apr 2025 01:20:32 +0400 Subject: [PATCH 03/19] Remove output for nswag test --- TrainStation/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TrainStation/Dockerfile b/TrainStation/Dockerfile index 3392422..633c6df 100644 --- a/TrainStation/Dockerfile +++ b/TrainStation/Dockerfile @@ -9,7 +9,7 @@ WORKDIR /build COPY TrainStation/ TrainStation/ FROM build AS publish -RUN dotnet publish "TrainStation/API.csproj" -c Release -o /app/publish +RUN dotnet publish "TrainStation/API.csproj" -c Release FROM base AS final From 07d9214ef467c8462d0ab3dd2d0314d0df779c4e Mon Sep 17 00:00:00 2001 From: Aram Kocharyan Date: Sun, 27 Apr 2025 01:48:19 +0400 Subject: [PATCH 04/19] Add missing nswag dependecy --- TrainStation/API.csproj | 6 +++++- TrainStation/Dockerfile | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/TrainStation/API.csproj b/TrainStation/API.csproj index 9a08a1a..e03262b 100644 --- a/TrainStation/API.csproj +++ b/TrainStation/API.csproj @@ -12,6 +12,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + @@ -23,6 +27,6 @@ - + diff --git a/TrainStation/Dockerfile b/TrainStation/Dockerfile index 633c6df..3392422 100644 --- a/TrainStation/Dockerfile +++ b/TrainStation/Dockerfile @@ -9,7 +9,7 @@ WORKDIR /build COPY TrainStation/ TrainStation/ FROM build AS publish -RUN dotnet publish "TrainStation/API.csproj" -c Release +RUN dotnet publish "TrainStation/API.csproj" -c Release -o /app/publish FROM base AS final From f7d56a02d993a512283c3b34798752073424f3c6 Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Tue, 29 Apr 2025 14:24:24 +0400 Subject: [PATCH 05/19] Add get quote sse endpouint --- TrainStation/Endpoints/StationEndpoints.cs | 99 +++++++++++++------- TrainStation/Program.cs | 5 +- TrainStation/Services/RoutePollingService.cs | 4 +- TrainStation/Services/SolverCache.cs | 9 +- 4 files changed, 77 insertions(+), 40 deletions(-) diff --git a/TrainStation/Endpoints/StationEndpoints.cs b/TrainStation/Endpoints/StationEndpoints.cs index a1ac488..22c2a1d 100644 --- a/TrainStation/Endpoints/StationEndpoints.cs +++ b/TrainStation/Endpoints/StationEndpoints.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using System.Text; using Train.Station.API.Models; using Train.Station.API.Services; using Train.Station.Client; @@ -17,8 +18,8 @@ public static RouteGroupBuilder MapEndpoints(this RouteGroupBuilder group) group.MapGet("/routes", GetAllRoutesAsync) .Produces>(); - //group.MapGet("/quote", GetQuoteAsync) - // .Produces(); + group.MapGet("/quote-sse", GetQuoteAsync) + .Produces(statusCode: 200, contentType: "text/event-stream"); //group.MapGet("/{solver}/swaps", GetAllSwapsAsync) // .Produces(); @@ -103,34 +104,68 @@ private static async Task GetAllRoutesAsync( return Results.Ok(routes); } - //private static async Task GetQuoteAsync( - // IRouteService routeService, - // HttpContext httpContext, - // [AsParameters] GetQuoteQueryParams queryParams) - //{ - // var quoteRequest = new QuoteRequest - // { - // SourceNetwork = queryParams.SourceNetwork!, - // SourceToken = queryParams.SourceToken!, - // DestinationNetwork = queryParams.DestinationNetwork!, - // DestinationToken = queryParams.DestinationToken!, - // Amount = queryParams.Amount!.Value, - // }; - - // var quote = await routeService.GetValidatedQuoteAsync(quoteRequest); - - // if (quote == null) - // { - // return Results.NotFound(new ApiResponse() - // { - // Error = new ApiError() - // { - // Code = "QUOTE_NOT_FOUND", - // Message = "Quote not found", - // } - // }); - // } - - // return Results.Ok(new ApiResponseQuoteDto { Data = quote }); - //} + private static async Task GetQuoteAsync( + HttpContext httpContext, + RouteCache routeCache, + SolverCache solverCache, + IHttpClientFactory httpClientFactory, + [FromQuery] string sourceNetwork, + [FromQuery] string sourceToken, + [FromQuery] string destinationNetwork, + [FromQuery] string destinationToken, + [FromQuery] double amount) + { + httpContext.Response.Headers.ContentType = "text/event-stream"; + + var lps = routeCache.GetLpsByRoute( + sourceNetwork, + sourceToken, + destinationNetwork, + destinationToken); + + var cancellationToken = httpContext.RequestAborted; + + foreach (var lpName in lps) + { + if (cancellationToken.IsCancellationRequested) + { + break; + } + + try + { + var solverInfo = solverCache.GetAll()[lpName]; + var httpClient = httpClientFactory.CreateClient(lpName); + + var trainSilverClient = new TrainSolverApiClient( + solverInfo.Url.ToString(), httpClient); + + var quote = await trainSilverClient.QuoteAsync( + amount, + sourceNetwork, + sourceToken, + destinationNetwork, + destinationToken); + + var message = new + { + Provider = lpName, + Quote = quote.Data + }; + + var json = System.Text.Json.JsonSerializer.Serialize(message); + + await httpContext.Response.WriteAsync($"data: {json}\n\n"); + await httpContext.Response.Body.FlushAsync(); + + } + catch + { + } + } + + + + await httpContext.Response.Body.FlushAsync(); + } } diff --git a/TrainStation/Program.cs b/TrainStation/Program.cs index 18d1f63..b4b5359 100644 --- a/TrainStation/Program.cs +++ b/TrainStation/Program.cs @@ -67,6 +67,9 @@ var app = builder.Build(); +app.UseRateLimiter(); +app.UseCors(); + app.MapGroup("/api") .MapGet("/health", () => Results.Ok()) .WithTags("System") @@ -85,7 +88,5 @@ c.DisplayRequestDuration(); }); -app.UseRateLimiter(); -app.UseCors(); await app.RunAsync(); diff --git a/TrainStation/Services/RoutePollingService.cs b/TrainStation/Services/RoutePollingService.cs index fe06bee..bd867af 100644 --- a/TrainStation/Services/RoutePollingService.cs +++ b/TrainStation/Services/RoutePollingService.cs @@ -15,8 +15,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { - var name = lp.Name; - var baseAddress = lp.Url.ToString(); + var name = lp.Key; + var baseAddress = lp.Value.Url.ToString(); var trainSilverClient = new TrainSolverApiClient( baseAddress, diff --git a/TrainStation/Services/SolverCache.cs b/TrainStation/Services/SolverCache.cs index e01e0be..bc66d43 100644 --- a/TrainStation/Services/SolverCache.cs +++ b/TrainStation/Services/SolverCache.cs @@ -1,11 +1,12 @@ -using System.Text.Json; +using System.Collections.Concurrent; +using System.Text.Json; using Train.Station.API.Models; namespace Train.Station.API.Services; public class SolverCache { - private readonly List _solvers; + private readonly Dictionary _solvers; public SolverCache(IWebHostEnvironment env) { @@ -19,8 +20,8 @@ public SolverCache(IWebHostEnvironment env) _solvers = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true - }) ?? new List(); + }).ToDictionary(x=> x.Name) ?? new (); } - public IReadOnlyList GetAll() => _solvers; + public IReadOnlyDictionary GetAll() => _solvers; } \ No newline at end of file From 17741b89510c64e32d23b8e42123396ed6c920af Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Wed, 30 Apr 2025 16:06:04 +0400 Subject: [PATCH 06/19] Use redis --- TrainStation.sln | 5 + TrainStation/API.csproj | 1 + TrainStation/Client/TrainSolverApiClient.cs | 12 ++ TrainStation/Endpoints/StationEndpoints.cs | 106 ++++++---------- TrainStation/Options/TrainStationOptions.cs | 10 ++ TrainStation/Program.cs | 14 +++ TrainStation/Services/JsonFileCache.cs | 28 +++++ .../Services/NetworkConfigurationCache.cs | 24 +--- TrainStation/Services/RouteCache.cs | 118 ++++++------------ TrainStation/Services/RoutePollingService.cs | 2 +- TrainStation/Services/SolverCache.cs | 23 +--- TrainStation/solvers.json | 7 +- 12 files changed, 163 insertions(+), 187 deletions(-) create mode 100644 TrainStation/Options/TrainStationOptions.cs create mode 100644 TrainStation/Services/JsonFileCache.cs diff --git a/TrainStation.sln b/TrainStation.sln index e1cdf5a..e4de273 100644 --- a/TrainStation.sln +++ b/TrainStation.sln @@ -5,6 +5,11 @@ VisualStudioVersion = 17.13.35825.156 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "TrainStation\API.csproj", "{2CBBA91C-0E98-44EC-8417-5C2E686F9431}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_config", "_config", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/TrainStation/API.csproj b/TrainStation/API.csproj index e03262b..2d1db34 100644 --- a/TrainStation/API.csproj +++ b/TrainStation/API.csproj @@ -16,6 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/TrainStation/Client/TrainSolverApiClient.cs b/TrainStation/Client/TrainSolverApiClient.cs index 6b96534..47d90ca 100644 --- a/TrainStation/Client/TrainSolverApiClient.cs +++ b/TrainStation/Client/TrainSolverApiClient.cs @@ -1335,6 +1335,12 @@ public partial class QuoteDto [Newtonsoft.Json.JsonProperty("receiveAmountInUsd", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public double ReceiveAmountInUsd { get; set; } + [Newtonsoft.Json.JsonProperty("sourceAmount", Required = Newtonsoft.Json.Required.Always)] + public double SourceAmount { get; set; } + + [Newtonsoft.Json.JsonProperty("sourceAmountInUsd", Required = Newtonsoft.Json.Required.Always)] + public double SourceAmountInUsd { get; set; } + } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] @@ -1363,6 +1369,9 @@ public partial class SwapDto [Newtonsoft.Json.JsonProperty("sourceAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public double SourceAmount { get; set; } + [Newtonsoft.Json.JsonProperty("sourceAmountInUsd", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double SourceAmountInUsd { get; set; } + [Newtonsoft.Json.JsonProperty("sourceAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string SourceAddress { get; set; } @@ -1375,6 +1384,9 @@ public partial class SwapDto [Newtonsoft.Json.JsonProperty("destinationAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public double DestinationAmount { get; set; } + [Newtonsoft.Json.JsonProperty("destinationAmountInUsd", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double DestinationAmountInUsd { get; set; } + [Newtonsoft.Json.JsonProperty("destinationAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string DestinationAddress { get; set; } diff --git a/TrainStation/Endpoints/StationEndpoints.cs b/TrainStation/Endpoints/StationEndpoints.cs index 22c2a1d..77b9d16 100644 --- a/TrainStation/Endpoints/StationEndpoints.cs +++ b/TrainStation/Endpoints/StationEndpoints.cs @@ -21,86 +21,60 @@ public static RouteGroupBuilder MapEndpoints(this RouteGroupBuilder group) group.MapGet("/quote-sse", GetQuoteAsync) .Produces(statusCode: 200, contentType: "text/event-stream"); - //group.MapGet("/{solver}/swaps", GetAllSwapsAsync) - // .Produces(); + group.MapGet("/{solver}/swaps/{commitId}", GetSwapAsync) + .Produces(); - //group.MapGet("/{solver}/swaps/{commitId}", GetSwapAsync) - // .Produces(); - - //group.MapPost("/{solver}/swaps/{commitId}/addLockSig", AddLockSigAsync) - // .Produces(); + group.MapPost("/{solver}/swaps/{commitId}/addLockSig", AddLockSigAsync) + .Produces(); return group; } + private static async Task GetSwapAsync( + HttpContext httpContext, + [FromRoute] string solver, + [FromRoute] string commitId, + IHttpClientFactory httpClientFactory) + { + var httpClient = httpClientFactory.CreateClient(solver); + var trainSilverClient = new TrainSolverApiClient( + solver, httpClient); + var swap = await trainSilverClient.Swaps2Async(commitId); - //private static async Task GetSwapRouteLimitsAsync( - // HttpContext httpContext, - // IRouteService routeService, - // [AsParameters] GetRouteLimitsQueryParams queryParams) - //{ - // var limit = await routeService.GetLimitAsync( - // new() - // { - // SourceNetwork = queryParams.SourceNetwork!, - // SourceToken = queryParams.SourceToken!, - // DestinationNetwork = queryParams.DestinationNetwork!, - // DestinationToken = queryParams.DestinationToken!, - // }); - - // if (limit == null) - // { - // return Results.NotFound(new ApiResponse() - // { - // Error = new ApiError() - // { - // Code = "LIMIT_NOT_FOUND", - // Message = "Limit not found", - // } - // }); - // } - - // return Results.Ok(new ApiResponse { Data = limit }); - //} + return Results.Ok(swap); + } - private static async Task GetNetworksAsync( + private static async Task AddLockSigAsync( HttpContext httpContext, - NetworkConfigurationCache networkConfigurationCache) + [FromRoute] string solver, + [FromRoute] string commitId, + IHttpClientFactory httpClientFactory, + AddLockSignatureModel addLock) { - var networks = networkConfigurationCache.GetAll(); - - return Results.Ok(networks); + var httpClient = httpClientFactory.CreateClient(solver); + var trainSilverClient = new TrainSolverApiClient( + solver, httpClient); + var swap = await trainSilverClient.Swaps2Async(commitId); + + if (swap == null) + { + return Results.NotFound(); + } + + var lockSig = await trainSilverClient.AddLockSigAsync(commitId, addLock); + return Results.Ok(lockSig); } - //private static async Task GetAllSourcesAsync( - // IRouteService routeService, - // INetworkRepository networkRepository, - // [FromQuery] string? destinationNetwork, - // [FromQuery] string? destinationToken) - //{ - // var sources = await routeService.GetSourcesAsync( - // networkName: destinationNetwork, - // token: destinationToken); - - // if (sources == null || !sources.Any()) - // { - // return Results.NotFound(new ApiResponse() - // { - // Error = new ApiError() - // { - // Code = "REACHABLE_POINTS_NOT_FOUND", - // Message = "No reachable points found", - // } - // }); - // } - - // return Results.Ok(new ApiResponseListDetailedNetworkDto { Data = sources }); - //} + private static async Task GetNetworksAsync( + NetworkConfigurationCache networkConfigurationCache) + { + return Results.Ok(networkConfigurationCache.GetAll()); + } private static async Task GetAllRoutesAsync( RouteCache routeCache) { - var routes = routeCache.GetAll(); + var routes = await routeCache.GetAllAsync(); return Results.Ok(routes); } @@ -117,7 +91,7 @@ private static async Task GetQuoteAsync( { httpContext.Response.Headers.ContentType = "text/event-stream"; - var lps = routeCache.GetLpsByRoute( + var lps = await routeCache.GetLpsByRouteAsync( sourceNetwork, sourceToken, destinationNetwork, diff --git a/TrainStation/Options/TrainStationOptions.cs b/TrainStation/Options/TrainStationOptions.cs new file mode 100644 index 0000000..3e5a36e --- /dev/null +++ b/TrainStation/Options/TrainStationOptions.cs @@ -0,0 +1,10 @@ +namespace Train.Station.API.Options; + +public class TrainStationOptions +{ + public const string SectionName = "TrainStation"; + + public string RedisConnectionString { get; set; } = null!; + + public int RedisDatabaseIndex { get; set; } = 4; +} diff --git a/TrainStation/Program.cs b/TrainStation/Program.cs index b4b5359..bb44e10 100644 --- a/TrainStation/Program.cs +++ b/TrainStation/Program.cs @@ -1,7 +1,10 @@ +using Microsoft.Extensions.Options; +using StackExchange.Redis; using System.Text.Json.Serialization; using System.Threading.RateLimiting; using Train.Station.API.Endpoints; using Train.Station.API.Extensions; +using Train.Station.API.Options; using Train.Station.API.Services; var builder = WebApplication.CreateBuilder(args); @@ -37,6 +40,17 @@ options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); +var options = new TrainStationOptions(); +configuration.GetSection(TrainStationOptions.SectionName).Bind(options); + +builder.Services.AddSingleton( + ConnectionMultiplexer.Connect(options.RedisConnectionString)); + +builder.Services.AddTransient(sp => sp + .GetRequiredService() + .GetDatabase(options.RedisDatabaseIndex)); + + builder.Services.AddHttpClient(); builder.Services.AddMemoryCache(); builder.Services.AddSingleton(); diff --git a/TrainStation/Services/JsonFileCache.cs b/TrainStation/Services/JsonFileCache.cs new file mode 100644 index 0000000..df8ec80 --- /dev/null +++ b/TrainStation/Services/JsonFileCache.cs @@ -0,0 +1,28 @@ +using System.Text.Json; + +namespace Train.Station.API.Services; + +public abstract class JsonFileCache +{ + protected TCollection Items { get; } + + protected JsonFileCache( + IWebHostEnvironment env, + string fileName, + Func, TCollection> transform) + { + var filePath = Path.Combine(env.ContentRootPath, fileName); + if (!File.Exists(filePath)) + { + throw new FileNotFoundException($"Could not find {fileName}", filePath); + } + + var json = File.ReadAllText(filePath); + var deserialized = JsonSerializer.Deserialize>(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }) ?? throw new InvalidOperationException($"Deserialization of {fileName} returned null."); + + Items = transform(deserialized); + } +} \ No newline at end of file diff --git a/TrainStation/Services/NetworkConfigurationCache.cs b/TrainStation/Services/NetworkConfigurationCache.cs index 5102b1c..d1a84c0 100644 --- a/TrainStation/Services/NetworkConfigurationCache.cs +++ b/TrainStation/Services/NetworkConfigurationCache.cs @@ -3,24 +3,10 @@ namespace Train.Station.API.Services; -public class NetworkConfigurationCache +public class NetworkConfigurationCache( + IWebHostEnvironment env) + : JsonFileCache, NetworkConfiguration>( + env, "networks.json", list => list) { - private readonly List _networks; - - public NetworkConfigurationCache(IWebHostEnvironment env) - { - var filePath = Path.Combine(env.ContentRootPath, "networks.json"); - if (!File.Exists(filePath)) - { - throw new FileNotFoundException("Could not find networks.json", filePath); - } - - var json = File.ReadAllText(filePath); - _networks = JsonSerializer.Deserialize>(json, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }) ?? new List(); - } - - public IReadOnlyList GetAll() => _networks; + public IReadOnlyList GetAll() => Items; } \ No newline at end of file diff --git a/TrainStation/Services/RouteCache.cs b/TrainStation/Services/RouteCache.cs index 26876de..8acc937 100644 --- a/TrainStation/Services/RouteCache.cs +++ b/TrainStation/Services/RouteCache.cs @@ -1,18 +1,23 @@ -using Microsoft.Extensions.Caching.Memory; -using System.Collections.Concurrent; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Microsoft.AspNetCore.Routing; +using StackExchange.Redis; +using System.Text.Json; +using System.Text.Json.Nodes; using Train.Station.Client; namespace Train.Station.API.Services; -public class RouteCache(IMemoryCache cache, NetworkConfigurationCache networkConfigCache) +public class RouteCache( + IDatabase cache, + IConnectionMultiplexer connectionMultiplexer, + NetworkConfigurationCache networkConfigCache) { - private readonly ConcurrentDictionary> _routeToLps = new(); private readonly HashSet _validNetworkNames = networkConfigCache .GetAll() .Select(n => n.Name) .ToHashSet(StringComparer.OrdinalIgnoreCase); - public void AddOrUpdateRoute(string lpId, IEnumerable routes, TimeSpan ttl) + public async Task AddOrUpdateRouteAsync(string lpId, IEnumerable routes, TimeSpan ttl) { foreach (var route in routes) { @@ -22,107 +27,58 @@ public void AddOrUpdateRoute(string lpId, IEnumerable routes, TimeSpan continue; } - string key = GetRouteKey(route); + await cache.SortedSetAddAsync( + "ROUTES", + JsonSerializer.Serialize(route), + DateTimeOffset.UtcNow.ToUnixTimeSeconds()); - var cacheEntryOptions = new MemoryCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = ttl, - PostEvictionCallbacks = - { - new PostEvictionCallbackRegistration - { - EvictionCallback = (evictedKey, value, reason, state) => - { - var routeKey = (string)evictedKey; - - if (_routeToLps.TryGetValue(routeKey, out var lpSet)) - { - lpSet.TryRemove(lpId, out _); - - if (lpSet.IsEmpty) - _routeToLps.TryRemove(routeKey, out _); - } - } - } - } - }; - - cache.Set(key, route, cacheEntryOptions); - - if (_routeToLps.TryGetValue(key, out var lpMap)) - { - lpMap[lpId] = true; - } - else - { - _routeToLps[key] = new ConcurrentDictionary( - [new KeyValuePair(lpId, true)]); - } + await cache.SetAddAsync( + GetRouteKey("LP", route), + new RedisValue(lpId)); } } - public HashSet GetAll() + public async Task> GetAllAsync() { - var routes = new HashSet(); + var now = DateTimeOffset.UtcNow; + var start = now.AddMinutes(-5).ToUnixTimeSeconds(); + var end = now.ToUnixTimeSeconds(); - foreach (var key in _routeToLps.Keys) - { - if (cache.TryGetValue(key, out var route)) - { - routes.Add(route); - } - } + var members = await cache.SortedSetRangeByScoreAsync("ROUTES", start, end); - return routes; - } + var entries = new List(); - public HashSet GetAllSources() - { - var sources = new HashSet(); - foreach (var key in _routeToLps.Keys) + foreach (var member in members) { - if (cache.TryGetValue(key, out var route)) + try { - sources.Add(route.Source); + var dto = JsonSerializer.Deserialize(member); + if (dto != null) + entries.Add(dto); } - } - return sources; - } - - public HashSet GetAllDestinations() - { - var destinations = new HashSet(); - foreach (var key in _routeToLps.Keys) - { - if (cache.TryGetValue(key, out var route)) + catch { - destinations.Add(route.Destionation); } } - return destinations; + return entries; } - public IEnumerable GetLpsByRoute(string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken) + public async Task> GetLpsByRouteAsync(string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken) { - var requestedRouteKey = GetRouteKey(sourceNetwork, sourceToken, destinationNetwork, destinationToken); - - if (_routeToLps.TryGetValue(requestedRouteKey, out var lps)) - { - return lps.Keys; - } - - return Enumerable.Empty(); + var members = await cache.SetMembersAsync(GetRouteKey("LP", sourceNetwork, sourceToken, destinationNetwork, destinationToken)); + return members.Select(m => m.ToString()); } - private static string GetRouteKey(string sourceNetwork, string sourceToken, string destNetwork, string destToken) + private static string GetRouteKey(string prefix, string sourceNetwork, string sourceToken, string destNetwork, string destToken) { - return $"{sourceNetwork}.{sourceToken}->{destNetwork}.{destToken}"; + return $"{prefix}:{sourceNetwork}.{sourceToken}->{destNetwork}.{destToken}"; } - private static string GetRouteKey(RouteDto r) + private static string GetRouteKey(string prefix, RouteDto r) { return GetRouteKey( + prefix, r.Source.Network.Name, r.Source.Token.Symbol, r.Destionation.Network.Name, diff --git a/TrainStation/Services/RoutePollingService.cs b/TrainStation/Services/RoutePollingService.cs index bd867af..2421f12 100644 --- a/TrainStation/Services/RoutePollingService.cs +++ b/TrainStation/Services/RoutePollingService.cs @@ -29,7 +29,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) continue; } - routeCache.AddOrUpdateRoute(name, routesResponse.Data, TimeSpan.FromMinutes(10)); + await routeCache.AddOrUpdateRouteAsync(name, routesResponse.Data, TimeSpan.FromMinutes(5)); } catch (Exception ex) { diff --git a/TrainStation/Services/SolverCache.cs b/TrainStation/Services/SolverCache.cs index bc66d43..bf69caf 100644 --- a/TrainStation/Services/SolverCache.cs +++ b/TrainStation/Services/SolverCache.cs @@ -4,24 +4,9 @@ namespace Train.Station.API.Services; -public class SolverCache +public class SolverCache(IWebHostEnvironment env) + : JsonFileCache, Solver>( + env, "solvers.json", list => list.ToDictionary(x => x.Name)) { - private readonly Dictionary _solvers; - - public SolverCache(IWebHostEnvironment env) - { - var filePath = Path.Combine(env.ContentRootPath, "solvers.json"); - if (!File.Exists(filePath)) - { - throw new FileNotFoundException("Could not find lp.json", filePath); - } - - var json = File.ReadAllText(filePath); - _solvers = JsonSerializer.Deserialize>(json, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }).ToDictionary(x=> x.Name) ?? new (); - } - - public IReadOnlyDictionary GetAll() => _solvers; + public IReadOnlyDictionary GetAll() => Items; } \ No newline at end of file diff --git a/TrainStation/solvers.json b/TrainStation/solvers.json index 9ffebca..1b79ca7 100644 --- a/TrainStation/solvers.json +++ b/TrainStation/solvers.json @@ -2,6 +2,11 @@ { "name": "EC69", "url": "https://train.dev.lb.layerswap.cloud", - "version" : "v1" + "version": "v1" + }, + { + "name": "IC420", + "url": "https://train.dev.lb.layerswap.cloud", + "version": "v1" } ] \ No newline at end of file From f1939a315f733d893dbeb4460110a9dce9f4c2bc Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Wed, 30 Apr 2025 16:21:25 +0400 Subject: [PATCH 07/19] Update appsettings section name --- TrainStation/Options/TrainStationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TrainStation/Options/TrainStationOptions.cs b/TrainStation/Options/TrainStationOptions.cs index 3e5a36e..844df94 100644 --- a/TrainStation/Options/TrainStationOptions.cs +++ b/TrainStation/Options/TrainStationOptions.cs @@ -2,7 +2,7 @@ public class TrainStationOptions { - public const string SectionName = "TrainStation"; + public const string SectionName = "TrainSolver"; public string RedisConnectionString { get; set; } = null!; From 84a9f2a3e742621635400d5f779f018fcfbaaef2 Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Wed, 30 Apr 2025 18:34:21 +0400 Subject: [PATCH 08/19] Add solver check in api --- TrainStation/Endpoints/StationEndpoints.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/TrainStation/Endpoints/StationEndpoints.cs b/TrainStation/Endpoints/StationEndpoints.cs index 77b9d16..8e505be 100644 --- a/TrainStation/Endpoints/StationEndpoints.cs +++ b/TrainStation/Endpoints/StationEndpoints.cs @@ -32,10 +32,16 @@ public static RouteGroupBuilder MapEndpoints(this RouteGroupBuilder group) private static async Task GetSwapAsync( HttpContext httpContext, + SolverCache solverCache, [FromRoute] string solver, [FromRoute] string commitId, IHttpClientFactory httpClientFactory) { + if (!solverCache.GetAll().ContainsKey(solver)) + { + return Results.NotFound(); + } + var httpClient = httpClientFactory.CreateClient(solver); var trainSilverClient = new TrainSolverApiClient( solver, httpClient); @@ -46,11 +52,17 @@ private static async Task GetSwapAsync( private static async Task AddLockSigAsync( HttpContext httpContext, + SolverCache solverCache, [FromRoute] string solver, [FromRoute] string commitId, IHttpClientFactory httpClientFactory, AddLockSignatureModel addLock) { + if (!solverCache.GetAll().ContainsKey(solver)) + { + return Results.NotFound(); + } + var httpClient = httpClientFactory.CreateClient(solver); var trainSilverClient = new TrainSolverApiClient( solver, httpClient); From 44e5696ac0950926c654711f9a6ebb408cc5bfc8 Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Thu, 17 Jul 2025 18:53:41 +0400 Subject: [PATCH 09/19] Update client --- TrainStation/Client/TrainSolverApiClient.cs | 662 +------------------- TrainStation/Endpoints/StationEndpoints.cs | 6 +- TrainStation/Services/RouteCache.cs | 6 +- 3 files changed, 20 insertions(+), 654 deletions(-) diff --git a/TrainStation/Client/TrainSolverApiClient.cs b/TrainStation/Client/TrainSolverApiClient.cs index 47d90ca..a604594 100644 --- a/TrainStation/Client/TrainSolverApiClient.cs +++ b/TrainStation/Client/TrainSolverApiClient.cs @@ -28,11 +28,6 @@ namespace Train.Station.Client public partial interface ITrainSolverApiClient { - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - System.Threading.Tasks.Task NetworksAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. @@ -41,32 +36,12 @@ public partial interface ITrainSolverApiClient /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. - System.Threading.Tasks.Task SourcesAsync(string destinationNetwork = null, string destinationToken = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - System.Threading.Tasks.Task DestinationsAsync(string sourceNetwork = null, string sourceToken = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - System.Threading.Tasks.Task LimitsAsync(string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - System.Threading.Tasks.Task QuoteAsync(double amount, string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task QuoteAsync(string amount, string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. - System.Threading.Tasks.Task SwapsAsync(System.Collections.Generic.IEnumerable addresses = null, int? page = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - System.Threading.Tasks.Task Swaps2Async(string commitId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task SwapsAsync(string commitId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK @@ -128,77 +103,6 @@ public string BaseUrl partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task NetworksAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/networks" - urlBuilder_.Append("api/v1/networks"); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - { - var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. @@ -273,258 +177,7 @@ public string BaseUrl /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task SourcesAsync(string destinationNetwork = null, string destinationToken = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/sources" - urlBuilder_.Append("api/v1/sources"); - urlBuilder_.Append('?'); - if (destinationNetwork != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("destinationNetwork")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(destinationNetwork, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (destinationToken != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("destinationToken")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(destinationToken, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - { - var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task DestinationsAsync(string sourceNetwork = null, string sourceToken = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/destinations" - urlBuilder_.Append("api/v1/destinations"); - urlBuilder_.Append('?'); - if (sourceNetwork != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("sourceNetwork")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(sourceNetwork, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (sourceToken != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("sourceToken")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(sourceToken, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - { - var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task LimitsAsync(string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) - { - if (sourceNetwork == null) - throw new System.ArgumentNullException("sourceNetwork"); - - if (sourceToken == null) - throw new System.ArgumentNullException("sourceToken"); - - if (destinationNetwork == null) - throw new System.ArgumentNullException("destinationNetwork"); - - if (destinationToken == null) - throw new System.ArgumentNullException("destinationToken"); - - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/limits" - urlBuilder_.Append("api/v1/limits"); - urlBuilder_.Append('?'); - urlBuilder_.Append(System.Uri.EscapeDataString("SourceNetwork")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(sourceNetwork, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - urlBuilder_.Append(System.Uri.EscapeDataString("SourceToken")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(sourceToken, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - urlBuilder_.Append(System.Uri.EscapeDataString("DestinationNetwork")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(destinationNetwork, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - urlBuilder_.Append(System.Uri.EscapeDataString("DestinationToken")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(destinationToken, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - urlBuilder_.Length--; - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - { - var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task QuoteAsync(double amount, string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + public virtual async System.Threading.Tasks.Task QuoteAsync(string amount, string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { if (amount == null) throw new System.ArgumentNullException("amount"); @@ -617,88 +270,7 @@ public string BaseUrl /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task SwapsAsync(System.Collections.Generic.IEnumerable addresses = null, int? page = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/swaps" - urlBuilder_.Append("api/v1/swaps"); - urlBuilder_.Append('?'); - if (addresses != null) - { - foreach (var item_ in addresses) { urlBuilder_.Append(System.Uri.EscapeDataString("addresses")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } - } - if (page != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("page")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(page, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - { - var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task Swaps2Async(string commitId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + public virtual async System.Threading.Tasks.Task SwapsAsync(string commitId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { if (commitId == null) throw new System.ArgumentNullException("commitId"); @@ -1030,18 +602,6 @@ private string ConvertToString(object value, System.Globalization.CultureInfo cu } } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public enum AccountType - { - - [System.Runtime.Serialization.EnumMember(Value = @"LP")] - LP = 0, - - [System.Runtime.Serialization.EnumMember(Value = @"Charging")] - Charging = 1, - - } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class AddLockSignatureModel { @@ -1068,9 +628,6 @@ public partial class AddLockSignatureModel [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiError { - [Newtonsoft.Json.JsonProperty("code", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Code { get; set; } - [Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string Message { get; set; } @@ -1084,28 +641,6 @@ public partial class ApiResponse } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class ApiResponseLimitDto - { - [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public ApiError Error { get; set; } - - [Newtonsoft.Json.JsonProperty("data", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public LimitDto Data { get; set; } - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class ApiResponseListDetailedNetworkDto - { - [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public ApiError Error { get; set; } - - [Newtonsoft.Json.JsonProperty("data", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public System.Collections.Generic.ICollection Data { get; set; } - - } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiResponseListRouteDto { @@ -1139,134 +674,13 @@ public partial class ApiResponseSwapDto } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public enum ContarctType - { - - [System.Runtime.Serialization.EnumMember(Value = @"HTLCNativeContractAddress")] - HTLCNativeContractAddress = 0, - - [System.Runtime.Serialization.EnumMember(Value = @"HTLCTokenContractAddress")] - HTLCTokenContractAddress = 1, - - [System.Runtime.Serialization.EnumMember(Value = @"GasPriceOracleContract")] - GasPriceOracleContract = 2, - - [System.Runtime.Serialization.EnumMember(Value = @"EvmMultiCallContract")] - EvmMultiCallContract = 3, - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class ContractDto - { - [Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - public ContarctType Type { get; set; } - - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Address { get; set; } - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class DetailedNetworkDto - { - [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Name { get; set; } - - [Newtonsoft.Json.JsonProperty("chainId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string ChainId { get; set; } - - [Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - public NetworkType Type { get; set; } - - [Newtonsoft.Json.JsonProperty("displayName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string DisplayName { get; set; } - - [Newtonsoft.Json.JsonProperty("logo", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Logo { get; set; } - - [Newtonsoft.Json.JsonProperty("transactionExplorerTemplate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string TransactionExplorerTemplate { get; set; } - - [Newtonsoft.Json.JsonProperty("accountExplorerTemplate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string AccountExplorerTemplate { get; set; } - - [Newtonsoft.Json.JsonProperty("nativeToken", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public DetailedTokenDto NativeToken { get; set; } - - [Newtonsoft.Json.JsonProperty("tokens", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public System.Collections.Generic.ICollection Tokens { get; set; } - - [Newtonsoft.Json.JsonProperty("nodes", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public System.Collections.Generic.ICollection Nodes { get; set; } - - [Newtonsoft.Json.JsonProperty("contracts", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public System.Collections.Generic.ICollection Contracts { get; set; } - - [Newtonsoft.Json.JsonProperty("managedAccounts", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public System.Collections.Generic.ICollection ManagedAccounts { get; set; } - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class DetailedTokenDto - { - [Newtonsoft.Json.JsonProperty("symbol", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Symbol { get; set; } - - [Newtonsoft.Json.JsonProperty("contract", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Contract { get; set; } - - [Newtonsoft.Json.JsonProperty("decimals", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public int Decimals { get; set; } - - [Newtonsoft.Json.JsonProperty("precision", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public int Precision { get; set; } - - [Newtonsoft.Json.JsonProperty("logo", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Logo { get; set; } - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class LimitDto - { - [Newtonsoft.Json.JsonProperty("minAmountInUsd", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double MinAmountInUsd { get; set; } - - [Newtonsoft.Json.JsonProperty("minAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double MinAmount { get; set; } - - [Newtonsoft.Json.JsonProperty("maxAmountInUsd", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double MaxAmountInUsd { get; set; } - - [Newtonsoft.Json.JsonProperty("maxAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double MaxAmount { get; set; } - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class ManagedAccountDto - { - [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Address { get; set; } - - [Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - public AccountType Type { get; set; } - - } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class NetworkDto { [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string Name { get; set; } - [Newtonsoft.Json.JsonProperty("chainId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonProperty("chainId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string ChainId { get; set; } [Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] @@ -1288,35 +702,8 @@ public enum NetworkType [System.Runtime.Serialization.EnumMember(Value = @"Starknet")] Starknet = 2, - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class NodeDto - { - [Newtonsoft.Json.JsonProperty("url", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Url { get; set; } - - [Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - public NodeType Type { get; set; } - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public enum NodeType - { - - [System.Runtime.Serialization.EnumMember(Value = @"Primary")] - Primary = 0, - - [System.Runtime.Serialization.EnumMember(Value = @"DepositTracking")] - DepositTracking = 1, - - [System.Runtime.Serialization.EnumMember(Value = @"Public")] - Public = 2, - - [System.Runtime.Serialization.EnumMember(Value = @"Secondary")] - Secondary = 3, + [System.Runtime.Serialization.EnumMember(Value = @"Fuel")] + Fuel = 3, } @@ -1324,22 +711,10 @@ public enum NodeType public partial class QuoteDto { [Newtonsoft.Json.JsonProperty("totalFee", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double TotalFee { get; set; } - - [Newtonsoft.Json.JsonProperty("totalFeeInUsd", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double TotalFeeInUsd { get; set; } + public string TotalFee { get; set; } [Newtonsoft.Json.JsonProperty("receiveAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double ReceiveAmount { get; set; } - - [Newtonsoft.Json.JsonProperty("receiveAmountInUsd", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double ReceiveAmountInUsd { get; set; } - - [Newtonsoft.Json.JsonProperty("sourceAmount", Required = Newtonsoft.Json.Required.Always)] - public double SourceAmount { get; set; } - - [Newtonsoft.Json.JsonProperty("sourceAmountInUsd", Required = Newtonsoft.Json.Required.Always)] - public double SourceAmountInUsd { get; set; } + public string ReceiveAmount { get; set; } } @@ -1349,8 +724,8 @@ public partial class RouteDto [Newtonsoft.Json.JsonProperty("source", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public TokenNetworkDto Source { get; set; } - [Newtonsoft.Json.JsonProperty("destionation", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public TokenNetworkDto Destionation { get; set; } + [Newtonsoft.Json.JsonProperty("destination", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public TokenNetworkDto Destination { get; set; } } @@ -1367,10 +742,7 @@ public partial class SwapDto public string SourceToken { get; set; } [Newtonsoft.Json.JsonProperty("sourceAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double SourceAmount { get; set; } - - [Newtonsoft.Json.JsonProperty("sourceAmountInUsd", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double SourceAmountInUsd { get; set; } + public string SourceAmount { get; set; } [Newtonsoft.Json.JsonProperty("sourceAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string SourceAddress { get; set; } @@ -1382,16 +754,13 @@ public partial class SwapDto public string DestinationToken { get; set; } [Newtonsoft.Json.JsonProperty("destinationAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double DestinationAmount { get; set; } - - [Newtonsoft.Json.JsonProperty("destinationAmountInUsd", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double DestinationAmountInUsd { get; set; } + public string DestinationAmount { get; set; } [Newtonsoft.Json.JsonProperty("destinationAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string DestinationAddress { get; set; } [Newtonsoft.Json.JsonProperty("feeAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public double FeeAmount { get; set; } + public string FeeAmount { get; set; } [Newtonsoft.Json.JsonProperty("transactions", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public System.Collections.Generic.ICollection Transactions { get; set; } @@ -1410,9 +779,6 @@ public partial class TokenDto [Newtonsoft.Json.JsonProperty("decimals", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public int Decimals { get; set; } - [Newtonsoft.Json.JsonProperty("precision", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public int Precision { get; set; } - } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] diff --git a/TrainStation/Endpoints/StationEndpoints.cs b/TrainStation/Endpoints/StationEndpoints.cs index 8e505be..d1013d9 100644 --- a/TrainStation/Endpoints/StationEndpoints.cs +++ b/TrainStation/Endpoints/StationEndpoints.cs @@ -45,7 +45,7 @@ private static async Task GetSwapAsync( var httpClient = httpClientFactory.CreateClient(solver); var trainSilverClient = new TrainSolverApiClient( solver, httpClient); - var swap = await trainSilverClient.Swaps2Async(commitId); + var swap = await trainSilverClient.SwapsAsync(commitId); return Results.Ok(swap); } @@ -66,7 +66,7 @@ private static async Task AddLockSigAsync( var httpClient = httpClientFactory.CreateClient(solver); var trainSilverClient = new TrainSolverApiClient( solver, httpClient); - var swap = await trainSilverClient.Swaps2Async(commitId); + var swap = await trainSilverClient.SwapsAsync(commitId); if (swap == null) { @@ -99,7 +99,7 @@ private static async Task GetQuoteAsync( [FromQuery] string sourceToken, [FromQuery] string destinationNetwork, [FromQuery] string destinationToken, - [FromQuery] double amount) + [FromQuery] string amount) { httpContext.Response.Headers.ContentType = "text/event-stream"; diff --git a/TrainStation/Services/RouteCache.cs b/TrainStation/Services/RouteCache.cs index 8acc937..a0a1e68 100644 --- a/TrainStation/Services/RouteCache.cs +++ b/TrainStation/Services/RouteCache.cs @@ -22,7 +22,7 @@ public async Task AddOrUpdateRouteAsync(string lpId, IEnumerable route foreach (var route in routes) { if (!_validNetworkNames.Contains(route.Source.Network.Name) || - !_validNetworkNames.Contains(route.Destionation.Network.Name)) + !_validNetworkNames.Contains(route.Destination.Network.Name)) { continue; } @@ -81,8 +81,8 @@ private static string GetRouteKey(string prefix, RouteDto r) prefix, r.Source.Network.Name, r.Source.Token.Symbol, - r.Destionation.Network.Name, - r.Destionation.Token.Symbol + r.Destination.Network.Name, + r.Destination.Token.Symbol ); } } From 5827a9f43ed11a93eb6b3c902a14e369f57d6652 Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Thu, 17 Jul 2025 19:21:34 +0400 Subject: [PATCH 10/19] Refactor get quote --- TrainStation/Endpoints/StationEndpoints.cs | 59 +++++---------------- TrainStation/Models/NetworkConfiguration.cs | 6 ++- TrainStation/Options/TrainStationOptions.cs | 2 +- TrainStation/networks.json | 35 ++++++------ 4 files changed, 39 insertions(+), 63 deletions(-) diff --git a/TrainStation/Endpoints/StationEndpoints.cs b/TrainStation/Endpoints/StationEndpoints.cs index d1013d9..f9bf7b5 100644 --- a/TrainStation/Endpoints/StationEndpoints.cs +++ b/TrainStation/Endpoints/StationEndpoints.cs @@ -19,7 +19,7 @@ public static RouteGroupBuilder MapEndpoints(this RouteGroupBuilder group) .Produces>(); group.MapGet("/quote-sse", GetQuoteAsync) - .Produces(statusCode: 200, contentType: "text/event-stream"); + .Produces(); group.MapGet("/{solver}/swaps/{commitId}", GetSwapAsync) .Produces(); @@ -90,8 +90,7 @@ private static async Task GetAllRoutesAsync( return Results.Ok(routes); } - private static async Task GetQuoteAsync( - HttpContext httpContext, + private static async Task GetQuoteAsync( RouteCache routeCache, SolverCache solverCache, IHttpClientFactory httpClientFactory, @@ -101,57 +100,27 @@ private static async Task GetQuoteAsync( [FromQuery] string destinationToken, [FromQuery] string amount) { - httpContext.Response.Headers.ContentType = "text/event-stream"; - var lps = await routeCache.GetLpsByRouteAsync( sourceNetwork, sourceToken, destinationNetwork, destinationToken); - var cancellationToken = httpContext.RequestAborted; - - foreach (var lpName in lps) - { - if (cancellationToken.IsCancellationRequested) - { - break; - } - - try - { - var solverInfo = solverCache.GetAll()[lpName]; - var httpClient = httpClientFactory.CreateClient(lpName); - - var trainSilverClient = new TrainSolverApiClient( - solverInfo.Url.ToString(), httpClient); - - var quote = await trainSilverClient.QuoteAsync( - amount, - sourceNetwork, - sourceToken, - destinationNetwork, - destinationToken); - - var message = new - { - Provider = lpName, - Quote = quote.Data - }; + var lpName = lps.First(); - var json = System.Text.Json.JsonSerializer.Serialize(message); + var solverInfo = solverCache.GetAll()[lpName]; + var httpClient = httpClientFactory.CreateClient(lpName); - await httpContext.Response.WriteAsync($"data: {json}\n\n"); - await httpContext.Response.Body.FlushAsync(); - - } - catch - { - } - } + var trainSilverClient = new TrainSolverApiClient( + solverInfo.Url.ToString(), httpClient); - + var quote = await trainSilverClient.QuoteAsync( + amount, + sourceNetwork, + sourceToken, + destinationNetwork, + destinationToken); - await httpContext.Response.Body.FlushAsync(); + return Results.Ok(quote); } } diff --git a/TrainStation/Models/NetworkConfiguration.cs b/TrainStation/Models/NetworkConfiguration.cs index 05ecd77..41e1b9a 100644 --- a/TrainStation/Models/NetworkConfiguration.cs +++ b/TrainStation/Models/NetworkConfiguration.cs @@ -12,9 +12,11 @@ public class NetworkConfiguration public string AccountExplorerTemplate { get; set; } = null!; - public string NativeSymbol { get; set; } = null!; + public string NativeTokenSymbol { get; set; } = null!; - public int Decimals { get; set; } + public int NativeTokenDecimals { get; set; } public string RpcUrl { get; set; } = null!; + + public string Type { get; set; } = null!; } diff --git a/TrainStation/Options/TrainStationOptions.cs b/TrainStation/Options/TrainStationOptions.cs index 844df94..3e5a36e 100644 --- a/TrainStation/Options/TrainStationOptions.cs +++ b/TrainStation/Options/TrainStationOptions.cs @@ -2,7 +2,7 @@ public class TrainStationOptions { - public const string SectionName = "TrainSolver"; + public const string SectionName = "TrainStation"; public string RedisConnectionString { get; set; } = null!; diff --git a/TrainStation/networks.json b/TrainStation/networks.json index ca6c277..65cae99 100644 --- a/TrainStation/networks.json +++ b/TrainStation/networks.json @@ -5,9 +5,10 @@ "ChainId": "11155111", "TransactionExplorerTemplate": "https://sepolia.etherscan.io/tx/{0}", "AccountExplorerTemplate": "https://sepolia.etherscan.io/address/{0}", - "NativeSymbol": "ETH", - "Decimals": 18, - "RpcUrl": "https://ethereum-sepolia-rpc.publicnode.com" + "NativeTokenSymbol": "ETH", + "NativeTokenDecimals": 18, + "RpcUrl": "https://ethereum-sepolia-rpc.publicnode.com", + "Type": "EVM" }, { "Name": "ARBITRUM_SEPOLIA", @@ -15,9 +16,10 @@ "ChainId": "421614", "TransactionExplorerTemplate": "https://sepolia.arbiscan.io/tx/{0}", "AccountExplorerTemplate": "https://sepolia.arbiscan.io/address/{0}", - "NativeSymbol": "ETH", - "Decimals": 18, - "RpcUrl": "https://arbitrum-sepolia-rpc.publicnode.com" + "NativeTokenSymbol": "ETH", + "NativeTokenDecimals": 18, + "RpcUrl": "https://arbitrum-sepolia-rpc.publicnode.com", + "Type": "EVM" }, { "Name": "OPTIMISM_SEPOLIA", @@ -25,9 +27,10 @@ "ChainId": "11155420", "TransactionExplorerTemplate": "https://sepolia-optimism.etherscan.io/tx/{0}", "AccountExplorerTemplate": "https://sepolia-optimism.etherscan.io/address/{0}", - "NativeSymbol": "ETH", - "Decimals": 18, - "RpcUrl": "https://sepolia.optimism.io" + "NativeTokenSymbol": "ETH", + "NativeTokenDecimals": 18, + "RpcUrl": "https://sepolia.optimism.io", + "Type": "EVM" }, { "Name": "STARKNET_SEPOLIA", @@ -35,9 +38,10 @@ "ChainId": "0x534e5f5345504f4c4941", "TransactionExplorerTemplate": "https://sepolia.starkscan.co/tx/{0}", "AccountExplorerTemplate": "https://sepolia.starkscan.co/contract/{0}", - "NativeSymbol": "ETH", - "Decimals": 18, - "RpcUrl": "https://starknet-sepolia.public.blastapi.io" + "NativeTokenSymbol": "ETH", + "NativeTokenDecimals": 18, + "RpcUrl": "https://starknet-sepolia.public.blastapi.io", + "Type": "EVM" }, { "Name": "SOLANA_DEVNET", @@ -45,8 +49,9 @@ "ChainId": "devnet", "TransactionExplorerTemplate": "https://explorer.solana.com/tx/{0}?cluster=devnet", "AccountExplorerTemplate": "https://explorer.solana.com/address/{0}?cluster=devnet", - "NativeSymbol": "SOL", - "Decimals": 9, - "RpcUrl": "https://api.devnet.solana.com" + "NativeTokenSymbol": "SOL", + "NativeTokenDecimals": 9, + "RpcUrl": "https://api.devnet.solana.com", + "Type": "Solana" } ] \ No newline at end of file From 5fadce449b90db4825d5b0bdb474c7f1ca78029d Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Thu, 17 Jul 2025 19:29:32 +0400 Subject: [PATCH 11/19] Fix quote route path --- TrainStation/API.csproj | 5 ----- TrainStation/Client/QuoteDto.cs | 9 +++++++++ TrainStation/Endpoints/StationEndpoints.cs | 4 +++- TrainStation/solvers.json | 5 ----- 4 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 TrainStation/Client/QuoteDto.cs diff --git a/TrainStation/API.csproj b/TrainStation/API.csproj index 2d1db34..a1bc221 100644 --- a/TrainStation/API.csproj +++ b/TrainStation/API.csproj @@ -22,11 +22,6 @@ - - - - - diff --git a/TrainStation/Client/QuoteDto.cs b/TrainStation/Client/QuoteDto.cs new file mode 100644 index 0000000..81d8d56 --- /dev/null +++ b/TrainStation/Client/QuoteDto.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Train.Station.Client; + +public partial class QuoteDto +{ + [JsonProperty("lpName", NullValueHandling = NullValueHandling.Ignore)] + public string LPName { get; set; } +} \ No newline at end of file diff --git a/TrainStation/Endpoints/StationEndpoints.cs b/TrainStation/Endpoints/StationEndpoints.cs index f9bf7b5..8b914e1 100644 --- a/TrainStation/Endpoints/StationEndpoints.cs +++ b/TrainStation/Endpoints/StationEndpoints.cs @@ -18,7 +18,7 @@ public static RouteGroupBuilder MapEndpoints(this RouteGroupBuilder group) group.MapGet("/routes", GetAllRoutesAsync) .Produces>(); - group.MapGet("/quote-sse", GetQuoteAsync) + group.MapGet("/quote", GetQuoteAsync) .Produces(); group.MapGet("/{solver}/swaps/{commitId}", GetSwapAsync) @@ -121,6 +121,8 @@ private static async Task GetQuoteAsync( destinationNetwork, destinationToken); + quote.Data.LPName = lpName; + return Results.Ok(quote); } } diff --git a/TrainStation/solvers.json b/TrainStation/solvers.json index 1b79ca7..028d9be 100644 --- a/TrainStation/solvers.json +++ b/TrainStation/solvers.json @@ -3,10 +3,5 @@ "name": "EC69", "url": "https://train.dev.lb.layerswap.cloud", "version": "v1" - }, - { - "name": "IC420", - "url": "https://train.dev.lb.layerswap.cloud", - "version": "v1" } ] \ No newline at end of file From 8503f87c4a9f672784c5a2a08c6ceecfc0b768db Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Thu, 17 Jul 2025 19:43:22 +0400 Subject: [PATCH 12/19] Update http client --- .../{QuoteDto.cs => QuoteWithSolverDto.cs} | 2 +- TrainStation/Client/TrainSolverApiClient.cs | 21 +++++++++++++------ TrainStation/Endpoints/StationEndpoints.cs | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) rename TrainStation/Client/{QuoteDto.cs => QuoteWithSolverDto.cs} (81%) diff --git a/TrainStation/Client/QuoteDto.cs b/TrainStation/Client/QuoteWithSolverDto.cs similarity index 81% rename from TrainStation/Client/QuoteDto.cs rename to TrainStation/Client/QuoteWithSolverDto.cs index 81d8d56..269b9a5 100644 --- a/TrainStation/Client/QuoteDto.cs +++ b/TrainStation/Client/QuoteWithSolverDto.cs @@ -2,7 +2,7 @@ namespace Train.Station.Client; -public partial class QuoteDto +public partial class QuoteWithSolverDto { [JsonProperty("lpName", NullValueHandling = NullValueHandling.Ignore)] public string LPName { get; set; } diff --git a/TrainStation/Client/TrainSolverApiClient.cs b/TrainStation/Client/TrainSolverApiClient.cs index a604594..4c66257 100644 --- a/TrainStation/Client/TrainSolverApiClient.cs +++ b/TrainStation/Client/TrainSolverApiClient.cs @@ -36,7 +36,7 @@ public partial interface ITrainSolverApiClient /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. - System.Threading.Tasks.Task QuoteAsync(string amount, string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task QuoteAsync(string amount, string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK @@ -177,7 +177,7 @@ public string BaseUrl /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task QuoteAsync(string amount, string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + public virtual async System.Threading.Tasks.Task QuoteAsync(string amount, string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { if (amount == null) throw new System.ArgumentNullException("amount"); @@ -240,7 +240,7 @@ public string BaseUrl var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -653,13 +653,13 @@ public partial class ApiResponseListRouteDto } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class ApiResponseQuoteDto + public partial class ApiResponseQuoteWithSolverDto { [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public ApiError Error { get; set; } [Newtonsoft.Json.JsonProperty("data", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public QuoteDto Data { get; set; } + public QuoteWithSolverDto Data { get; set; } } @@ -708,7 +708,7 @@ public enum NetworkType } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class QuoteDto + public partial class QuoteWithSolverDto { [Newtonsoft.Json.JsonProperty("totalFee", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string TotalFee { get; set; } @@ -716,6 +716,15 @@ public partial class QuoteDto [Newtonsoft.Json.JsonProperty("receiveAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string ReceiveAmount { get; set; } + [Newtonsoft.Json.JsonProperty("destinationSolverAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string DestinationSolverAddress { get; set; } + + [Newtonsoft.Json.JsonProperty("sourceContractAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string SourceContractAddress { get; set; } + + [Newtonsoft.Json.JsonProperty("destinationContractAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string DestinationContractAddress { get; set; } + } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] diff --git a/TrainStation/Endpoints/StationEndpoints.cs b/TrainStation/Endpoints/StationEndpoints.cs index 8b914e1..7372be6 100644 --- a/TrainStation/Endpoints/StationEndpoints.cs +++ b/TrainStation/Endpoints/StationEndpoints.cs @@ -19,7 +19,7 @@ public static RouteGroupBuilder MapEndpoints(this RouteGroupBuilder group) .Produces>(); group.MapGet("/quote", GetQuoteAsync) - .Produces(); + .Produces(); group.MapGet("/{solver}/swaps/{commitId}", GetSwapAsync) .Produces(); From 47badc80b5324825aff43a3d34b6424e3de23281 Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Thu, 17 Jul 2025 19:52:05 +0400 Subject: [PATCH 13/19] Rename lp name to solver name --- TrainStation/Client/QuoteWithSolverDto.cs | 4 ++-- TrainStation/Endpoints/StationEndpoints.cs | 10 +++++----- TrainStation/Services/RouteCache.cs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/TrainStation/Client/QuoteWithSolverDto.cs b/TrainStation/Client/QuoteWithSolverDto.cs index 269b9a5..e50f842 100644 --- a/TrainStation/Client/QuoteWithSolverDto.cs +++ b/TrainStation/Client/QuoteWithSolverDto.cs @@ -4,6 +4,6 @@ namespace Train.Station.Client; public partial class QuoteWithSolverDto { - [JsonProperty("lpName", NullValueHandling = NullValueHandling.Ignore)] - public string LPName { get; set; } + [JsonProperty("solverName", NullValueHandling = NullValueHandling.Ignore)] + public string SolverName { get; set; } } \ No newline at end of file diff --git a/TrainStation/Endpoints/StationEndpoints.cs b/TrainStation/Endpoints/StationEndpoints.cs index 7372be6..0a95b16 100644 --- a/TrainStation/Endpoints/StationEndpoints.cs +++ b/TrainStation/Endpoints/StationEndpoints.cs @@ -100,16 +100,16 @@ private static async Task GetQuoteAsync( [FromQuery] string destinationToken, [FromQuery] string amount) { - var lps = await routeCache.GetLpsByRouteAsync( + var solvers = await routeCache.GetSolversByRouteAsync( sourceNetwork, sourceToken, destinationNetwork, destinationToken); - var lpName = lps.First(); + var solverName = solvers.First(); - var solverInfo = solverCache.GetAll()[lpName]; - var httpClient = httpClientFactory.CreateClient(lpName); + var solverInfo = solverCache.GetAll()[solverName]; + var httpClient = httpClientFactory.CreateClient(solverName); var trainSilverClient = new TrainSolverApiClient( solverInfo.Url.ToString(), httpClient); @@ -121,7 +121,7 @@ private static async Task GetQuoteAsync( destinationNetwork, destinationToken); - quote.Data.LPName = lpName; + quote.Data.SolverName = solverName; return Results.Ok(quote); } diff --git a/TrainStation/Services/RouteCache.cs b/TrainStation/Services/RouteCache.cs index a0a1e68..fc8ec3f 100644 --- a/TrainStation/Services/RouteCache.cs +++ b/TrainStation/Services/RouteCache.cs @@ -33,7 +33,7 @@ await cache.SortedSetAddAsync( DateTimeOffset.UtcNow.ToUnixTimeSeconds()); await cache.SetAddAsync( - GetRouteKey("LP", route), + GetRouteKey("SOLVERS", route), new RedisValue(lpId)); } } @@ -64,9 +64,9 @@ public async Task> GetAllAsync() return entries; } - public async Task> GetLpsByRouteAsync(string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken) + public async Task> GetSolversByRouteAsync(string sourceNetwork, string sourceToken, string destinationNetwork, string destinationToken) { - var members = await cache.SetMembersAsync(GetRouteKey("LP", sourceNetwork, sourceToken, destinationNetwork, destinationToken)); + var members = await cache.SetMembersAsync(GetRouteKey("SOLVERS", sourceNetwork, sourceToken, destinationNetwork, destinationToken)); return members.Select(m => m.ToString()); } From f4a420d9cd755b5b08eea54d423c123463cb1c68 Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Fri, 18 Jul 2025 16:54:00 +0400 Subject: [PATCH 14/19] Update client --- TrainStation/Client/TrainSolverApiClient.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/TrainStation/Client/TrainSolverApiClient.cs b/TrainStation/Client/TrainSolverApiClient.cs index 4c66257..0b9e39d 100644 --- a/TrainStation/Client/TrainSolverApiClient.cs +++ b/TrainStation/Client/TrainSolverApiClient.cs @@ -716,6 +716,9 @@ public partial class QuoteWithSolverDto [Newtonsoft.Json.JsonProperty("receiveAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string ReceiveAmount { get; set; } + [Newtonsoft.Json.JsonProperty("sourceSolverAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string SourceSolverAddress { get; set; } + [Newtonsoft.Json.JsonProperty("destinationSolverAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string DestinationSolverAddress { get; set; } @@ -744,11 +747,8 @@ public partial class SwapDto [Newtonsoft.Json.JsonProperty("commitId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string CommitId { get; set; } - [Newtonsoft.Json.JsonProperty("sourceNetwork", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string SourceNetwork { get; set; } - - [Newtonsoft.Json.JsonProperty("sourceToken", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string SourceToken { get; set; } + [Newtonsoft.Json.JsonProperty("source", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public TokenNetworkDto Source { get; set; } [Newtonsoft.Json.JsonProperty("sourceAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string SourceAmount { get; set; } @@ -756,11 +756,11 @@ public partial class SwapDto [Newtonsoft.Json.JsonProperty("sourceAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string SourceAddress { get; set; } - [Newtonsoft.Json.JsonProperty("destinationNetwork", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string DestinationNetwork { get; set; } + [Newtonsoft.Json.JsonProperty("sourceContractAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string SourceContractAddress { get; set; } - [Newtonsoft.Json.JsonProperty("destinationToken", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string DestinationToken { get; set; } + [Newtonsoft.Json.JsonProperty("destination", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public TokenNetworkDto Destination { get; set; } [Newtonsoft.Json.JsonProperty("destinationAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string DestinationAmount { get; set; } @@ -768,6 +768,9 @@ public partial class SwapDto [Newtonsoft.Json.JsonProperty("destinationAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string DestinationAddress { get; set; } + [Newtonsoft.Json.JsonProperty("destinationContractAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string DestinationContractAddress { get; set; } + [Newtonsoft.Json.JsonProperty("feeAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string FeeAmount { get; set; } From 36efb4dfc71ff8734239fb264c605679ae350823 Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Fri, 18 Jul 2025 18:45:55 +0400 Subject: [PATCH 15/19] Fix solver api client url --- TrainStation/Endpoints/StationEndpoints.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/TrainStation/Endpoints/StationEndpoints.cs b/TrainStation/Endpoints/StationEndpoints.cs index 0a95b16..6fad2a3 100644 --- a/TrainStation/Endpoints/StationEndpoints.cs +++ b/TrainStation/Endpoints/StationEndpoints.cs @@ -37,14 +37,15 @@ private static async Task GetSwapAsync( [FromRoute] string commitId, IHttpClientFactory httpClientFactory) { - if (!solverCache.GetAll().ContainsKey(solver)) + if (!solverCache.GetAll().TryGetValue(solver, out var solverObj)) { return Results.NotFound(); } var httpClient = httpClientFactory.CreateClient(solver); var trainSilverClient = new TrainSolverApiClient( - solver, httpClient); + solverObj.Url.ToString(), httpClient); + var swap = await trainSilverClient.SwapsAsync(commitId); return Results.Ok(swap); @@ -58,14 +59,15 @@ private static async Task AddLockSigAsync( IHttpClientFactory httpClientFactory, AddLockSignatureModel addLock) { - if (!solverCache.GetAll().ContainsKey(solver)) + if (!solverCache.GetAll().TryGetValue(solver, out var solverObj)) { return Results.NotFound(); } var httpClient = httpClientFactory.CreateClient(solver); var trainSilverClient = new TrainSolverApiClient( - solver, httpClient); + solverObj.Url.ToString(), httpClient); + var swap = await trainSilverClient.SwapsAsync(commitId); if (swap == null) From 1f9b49d372e34b667c64b8d710e04d8553127504 Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Mon, 21 Jul 2025 17:29:45 +0400 Subject: [PATCH 16/19] Update client --- TrainStation/Client/TrainSolverApiClient.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/TrainStation/Client/TrainSolverApiClient.cs b/TrainStation/Client/TrainSolverApiClient.cs index 0b9e39d..a43e8cd 100644 --- a/TrainStation/Client/TrainSolverApiClient.cs +++ b/TrainStation/Client/TrainSolverApiClient.cs @@ -733,11 +733,13 @@ public partial class QuoteWithSolverDto [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class RouteDto { - [Newtonsoft.Json.JsonProperty("source", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public TokenNetworkDto Source { get; set; } + [Newtonsoft.Json.JsonProperty("source", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public TokenNetworkDto Source { get; set; } = new TokenNetworkDto(); - [Newtonsoft.Json.JsonProperty("destination", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public TokenNetworkDto Destination { get; set; } + [Newtonsoft.Json.JsonProperty("destination", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public TokenNetworkDto Destination { get; set; } = new TokenNetworkDto(); } From b0eb25165f83a00e35891d8c07880c259660bf81 Mon Sep 17 00:00:00 2001 From: Ruben Muradyan Date: Thu, 24 Jul 2025 17:11:07 +0400 Subject: [PATCH 17/19] Update client --- TrainStation/Client/TrainSolverApiClient.cs | 141 ++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/TrainStation/Client/TrainSolverApiClient.cs b/TrainStation/Client/TrainSolverApiClient.cs index a43e8cd..8fa77e1 100644 --- a/TrainStation/Client/TrainSolverApiClient.cs +++ b/TrainStation/Client/TrainSolverApiClient.cs @@ -43,6 +43,11 @@ public partial interface ITrainSolverApiClient /// A server side error occurred. System.Threading.Tasks.Task SwapsAsync(string commitId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// OK + /// A server side error occurred. + System.Threading.Tasks.Task BuildAsync(PrepareTransactionRequest body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. @@ -342,6 +347,84 @@ public string BaseUrl } } + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task BuildAsync(PrepareTransactionRequest body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "api/v1/transactions/build" + urlBuilder_.Append("api/v1/transactions/build"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. @@ -652,6 +735,17 @@ public partial class ApiResponseListRouteDto } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class ApiResponsePrepareTransactionDto + { + [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public ApiError Error { get; set; } + + [Newtonsoft.Json.JsonProperty("data", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public PrepareTransactionDto Data { get; set; } + + } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiResponseQuoteWithSolverDto { @@ -707,12 +801,56 @@ public enum NetworkType } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class PrepareTransactionDto + { + [Newtonsoft.Json.JsonProperty("toAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ToAddress { get; set; } + + [Newtonsoft.Json.JsonProperty("data", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Data { get; set; } + + [Newtonsoft.Json.JsonProperty("asset", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Asset { get; set; } + + [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amount { get; set; } + + [Newtonsoft.Json.JsonProperty("callDataAsset", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string CallDataAsset { get; set; } + + [Newtonsoft.Json.JsonProperty("callDataAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string CallDataAmount { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class PrepareTransactionRequest + { + [Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public TransactionType Type { get; set; } + + [Newtonsoft.Json.JsonProperty("args", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Args { get; set; } + + [Newtonsoft.Json.JsonProperty("networkName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string NetworkName { get; set; } + + } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class QuoteWithSolverDto { [Newtonsoft.Json.JsonProperty("totalFee", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string TotalFee { get; set; } + [Newtonsoft.Json.JsonProperty("totalServiceFee", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string TotalServiceFee { get; set; } + + [Newtonsoft.Json.JsonProperty("totalExpenseFee", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string TotalExpenseFee { get; set; } + [Newtonsoft.Json.JsonProperty("receiveAmount", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string ReceiveAmount { get; set; } @@ -749,6 +887,9 @@ public partial class SwapDto [Newtonsoft.Json.JsonProperty("commitId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string CommitId { get; set; } + [Newtonsoft.Json.JsonProperty("hashlock", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Hashlock { get; set; } + [Newtonsoft.Json.JsonProperty("source", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public TokenNetworkDto Source { get; set; } From 66b186d6081c5b359da822309e353b21f1dcfbee Mon Sep 17 00:00:00 2001 From: Davit Mandzikyan Date: Tue, 29 Jul 2025 17:49:02 +0400 Subject: [PATCH 18/19] Add fuel network. --- TrainStation/networks.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/TrainStation/networks.json b/TrainStation/networks.json index 65cae99..0b29543 100644 --- a/TrainStation/networks.json +++ b/TrainStation/networks.json @@ -53,5 +53,16 @@ "NativeTokenDecimals": 9, "RpcUrl": "https://api.devnet.solana.com", "Type": "Solana" + }, + { + "Name": "FUEL_TESTNET", + "DisplayName": "Fuel TESTNET", + "ChainId": "0", + "TransactionExplorerTemplate": "https://app-testnet.fuel.network/tx/{0}", + "AccountExplorerTemplate": "https://app-testnet.fuel.network/account/{0}/assets", + "NativeTokenSymbol": "ETH", + "NativeTokenDecimals": 9, + "RpcUrl": "https://testnet.fuel.network/v1/graphql", + "Type": "Fuel" } -] \ No newline at end of file +] From 4320e634e7371113c58749d0273b72c930ef4950 Mon Sep 17 00:00:00 2001 From: Davit Mandzikyan Date: Tue, 29 Jul 2025 17:57:14 +0400 Subject: [PATCH 19/19] Fix network display name. --- TrainStation/networks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TrainStation/networks.json b/TrainStation/networks.json index 0b29543..423473d 100644 --- a/TrainStation/networks.json +++ b/TrainStation/networks.json @@ -56,7 +56,7 @@ }, { "Name": "FUEL_TESTNET", - "DisplayName": "Fuel TESTNET", + "DisplayName": "Fuel Testnet", "ChainId": "0", "TransactionExplorerTemplate": "https://app-testnet.fuel.network/tx/{0}", "AccountExplorerTemplate": "https://app-testnet.fuel.network/account/{0}/assets",