Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/station.yml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions TrainStation.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions TrainStation/API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="NSwag.MSBuild" Version="14.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="StackExchange.Redis" Version="2.8.31" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="8.1.0" />
<PackageReference Include="Flurl.Http" Version="4.0.2" />
</ItemGroup>


<ItemGroup>
<Folder Include="Client\" />
</ItemGroup>

<Target Name="NSwagGenerate" BeforeTargets="BeforeBuild">
<Exec Command="nswag run $(ProjectDir)client.nswag" />
<Exec Command="$(NSwagExe_Net90) run $(ProjectDir)client.nswag" />
</Target>
</Project>
9 changes: 9 additions & 0 deletions TrainStation/Client/QuoteWithSolverDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Newtonsoft.Json;

namespace Train.Station.Client;

public partial class QuoteWithSolverDto
{
[JsonProperty("solverName", NullValueHandling = NullValueHandling.Ignore)]
public string SolverName { get; set; }
}
661 changes: 97 additions & 564 deletions TrainStation/Client/TrainSolverApiClient.cs

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions TrainStation/Dockerfile
Original file line number Diff line number Diff line change
@@ -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 TrainStation/ TrainStation/

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
186 changes: 90 additions & 96 deletions TrainStation/Endpoints/StationEndpoints.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,120 +18,113 @@ public static RouteGroupBuilder MapEndpoints(this RouteGroupBuilder group)
group.MapGet("/routes", GetAllRoutesAsync)
.Produces<IEnumerable<RouteDto>>();

//group.MapGet("/quote", GetQuoteAsync)
// .Produces<ApiResponseQuoteDto>();
group.MapGet("/quote", GetQuoteAsync)
.Produces<ApiResponseQuoteWithSolverDto>();

//group.MapGet("/{solver}/swaps", GetAllSwapsAsync)
// .Produces<ApiResponseSwapDto>();
group.MapGet("/{solver}/swaps/{commitId}", GetSwapAsync)
.Produces<ApiResponseSwapDto>();

//group.MapGet("/{solver}/swaps/{commitId}", GetSwapAsync)
// .Produces<ApiResponseSwapDto>();

//group.MapPost("/{solver}/swaps/{commitId}/addLockSig", AddLockSigAsync)
// .Produces<ApiResponse>();
group.MapPost("/{solver}/swaps/{commitId}/addLockSig", AddLockSigAsync)
.Produces<ApiResponse>();

return group;
}

private static async Task<IResult> GetSwapAsync(
HttpContext httpContext,
SolverCache solverCache,
[FromRoute] string solver,
[FromRoute] string commitId,
IHttpClientFactory httpClientFactory)
{
if (!solverCache.GetAll().TryGetValue(solver, out var solverObj))
{
return Results.NotFound();
}

var httpClient = httpClientFactory.CreateClient(solver);
var trainSilverClient = new TrainSolverApiClient(
solverObj.Url.ToString(), httpClient);

//private static async Task<IResult> 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<LimitDto> { Data = limit });
//}
var swap = await trainSilverClient.SwapsAsync(commitId);

private static async Task<IResult> GetNetworksAsync(
return Results.Ok(swap);
}

private static async Task<IResult> AddLockSigAsync(
HttpContext httpContext,
NetworkConfigurationCache networkConfigurationCache)
SolverCache solverCache,
[FromRoute] string solver,
[FromRoute] string commitId,
IHttpClientFactory httpClientFactory,
AddLockSignatureModel addLock)
{
var networks = networkConfigurationCache.GetAll();
if (!solverCache.GetAll().TryGetValue(solver, out var solverObj))
{
return Results.NotFound();
}

var httpClient = httpClientFactory.CreateClient(solver);
var trainSilverClient = new TrainSolverApiClient(
solverObj.Url.ToString(), httpClient);

return Results.Ok(networks);
var swap = await trainSilverClient.SwapsAsync(commitId);

if (swap == null)
{
return Results.NotFound();
}

var lockSig = await trainSilverClient.AddLockSigAsync(commitId, addLock);
return Results.Ok(lockSig);
}

//private static async Task<IResult> 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<IResult> GetNetworksAsync(
NetworkConfigurationCache networkConfigurationCache)
{
return Results.Ok(networkConfigurationCache.GetAll());
}

private static async Task<IResult> GetAllRoutesAsync(
RouteCache routeCache)
{
var routes = routeCache.GetAll();
var routes = await routeCache.GetAllAsync();
return Results.Ok(routes);
}

//private static async Task<IResult> 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<IResult> GetQuoteAsync(
RouteCache routeCache,
SolverCache solverCache,
IHttpClientFactory httpClientFactory,
[FromQuery] string sourceNetwork,
[FromQuery] string sourceToken,
[FromQuery] string destinationNetwork,
[FromQuery] string destinationToken,
[FromQuery] string amount)
{
var solvers = await routeCache.GetSolversByRouteAsync(
sourceNetwork,
sourceToken,
destinationNetwork,
destinationToken);

var solverName = solvers.First();

var solverInfo = solverCache.GetAll()[solverName];
var httpClient = httpClientFactory.CreateClient(solverName);

var trainSilverClient = new TrainSolverApiClient(
solverInfo.Url.ToString(), httpClient);

var quote = await trainSilverClient.QuoteAsync(
amount,
sourceNetwork,
sourceToken,
destinationNetwork,
destinationToken);

quote.Data.SolverName = solverName;

return Results.Ok(quote);
}
}
6 changes: 4 additions & 2 deletions TrainStation/Models/NetworkConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
}
10 changes: 10 additions & 0 deletions TrainStation/Options/TrainStationOptions.cs
Original file line number Diff line number Diff line change
@@ -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;
}
Loading