diff --git a/csharp/TrainSolver.slnx b/csharp/TrainSolver.slnx index 06f65438..2a765544 100644 --- a/csharp/TrainSolver.slnx +++ b/csharp/TrainSolver.slnx @@ -25,16 +25,16 @@ - - - - + + + + diff --git a/csharp/src/API/Endpoints/SolverV1Endpoints.cs b/csharp/src/API/Endpoints/SolverV1Endpoints.cs index e3f85cc4..27fc09a2 100644 --- a/csharp/src/API/Endpoints/SolverV1Endpoints.cs +++ b/csharp/src/API/Endpoints/SolverV1Endpoints.cs @@ -158,7 +158,7 @@ private static async Task BuildTransactionAsync( { var prepareTransactionResponse = await temporalClient .ExecuteWorkflowAsync( - "TransactionBuilderWorkflow", + "TransactionBuilderWorkflow", args: [request], new(id: Guid.CreateVersion7().ToString(), taskQueue: "Core")); diff --git a/csharp/src/AdminAPI/Endpoints/NetworkTypeEndpoints.cs b/csharp/src/AdminAPI/Endpoints/NetworkTypeEndpoints.cs new file mode 100644 index 00000000..8ab273b3 --- /dev/null +++ b/csharp/src/AdminAPI/Endpoints/NetworkTypeEndpoints.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; +using Train.Solver.Common.Enums; +using Train.Solver.Data.Abstractions.Entities; +using Train.Solver.Data.Abstractions.Repositories; +using Train.Solver.Infrastructure.Abstractions.Models; +using Train.Solver.Infrastructure.Extensions; + +namespace Train.Solver.AdminAPI.Endpoints; + +public static class NetworkTypeEndpoints +{ + public static RouteGroupBuilder MapNetworkTypeEndpoints(this RouteGroupBuilder group) + { + group.MapGet("/network-types", GetAllAsync) + .Produces>(); + + return group; + } + + private static async Task GetAllAsync() + { + return Results.Ok(typeof(NetworkType).GetEnumNames()); + } +} \ No newline at end of file diff --git a/csharp/src/AdminAPI/Endpoints/RebalanceEndpoints.cs b/csharp/src/AdminAPI/Endpoints/RebalanceEndpoints.cs index 3a5cbf39..617ac22a 100644 --- a/csharp/src/AdminAPI/Endpoints/RebalanceEndpoints.cs +++ b/csharp/src/AdminAPI/Endpoints/RebalanceEndpoints.cs @@ -1,9 +1,11 @@ using Microsoft.AspNetCore.Mvc; using Temporalio.Api.Enums.V1; using Temporalio.Client; +using Temporalio.Converters; using Train.Solver.AdminAPI.Models; using Train.Solver.Common.Enums; using Train.Solver.Common.Extensions; +using Train.Solver.Common.Helpers; using Train.Solver.Data.Abstractions.Repositories; using Train.Solver.Infrastructure.Abstractions.Models; using Train.Solver.Infrastructure.Extensions; @@ -16,12 +18,35 @@ public static class RebalanceEndpoints { public static RouteGroupBuilder MapRebalanceEndpoints(this RouteGroupBuilder group) { + group.MapGet("/rebalance", GetAllAsync) + .Produces(StatusCodes.Status200OK); + group.MapPost("/rebalance", RebalanceAsync) .Produces(StatusCodes.Status200OK); return group; } + private static async Task GetAllAsync( + ITemporalClient temporalClient) + { + var query = $"`WorkflowId` STARTS_WITH \"Rebalance\" AND `ExecutionStatus`=\"Running\""; + var results = new List(); + + await foreach (var wf in temporalClient.ListWorkflowsAsync(query)) + { + var whHandle = temporalClient.GetWorkflowHandle(wf.Id); + var describedWorkflow = await whHandle.DescribeAsync(); + + if (describedWorkflow.Memo.TryGetValue("Summary", out var summary) && summary != null) + { + results.Add(summary.ToString()!); + } + } + + return Results.Ok(results); + } + private static async Task RebalanceAsync( INetworkRepository repository, IWalletRepository walletRepository, @@ -74,12 +99,16 @@ private static async Task RebalanceAsync( SignerAgentUrl = wallet.SignerAgent.Url, }, new TransactionExecutionContext()], - new(id: TemporalHelper.BuildProcessorId(network.Name, TransactionType.Transfer, Guid.NewGuid()), + new(id: TemporalHelper.BuildRebalanceProcessorId(network.Name, Guid.NewGuid()), taskQueue: network.Type.ToString()) { IdReusePolicy = WorkflowIdReusePolicy.TerminateIfRunning, + Memo = new Dictionary + { + { "Summary", $"Rebalancing { TokenUnitHelper.FromBaseUnits(request.Amount, token.Decimals) } {token.Asset} in {network.DisplayName} from {wallet.Address} to {trustedWallet.Address}" }, + } }); - return Results.Ok(); + return Results.Ok(workflowId); } } diff --git a/csharp/src/AdminAPI/Models/CreateRouteRequest.cs b/csharp/src/AdminAPI/Models/CreateRouteRequest.cs index ed029d86..10167817 100644 --- a/csharp/src/AdminAPI/Models/CreateRouteRequest.cs +++ b/csharp/src/AdminAPI/Models/CreateRouteRequest.cs @@ -21,5 +21,5 @@ public class CreateRouteRequest public bool IgnoreExpenseFee { get; set; } - public string? ServiceFee { get; set; } + public string ServiceFee { get; set; } = default!; } diff --git a/csharp/src/AdminAPI/Models/UpdateRouteRequest.cs b/csharp/src/AdminAPI/Models/UpdateRouteRequest.cs index 358a1a16..e939abc8 100644 --- a/csharp/src/AdminAPI/Models/UpdateRouteRequest.cs +++ b/csharp/src/AdminAPI/Models/UpdateRouteRequest.cs @@ -8,6 +8,6 @@ public class UpdateRouteRequest public string RateProvider { get; set; } = default!; public BigInteger MinAmount { get; set; } public BigInteger MaxAmount { get; set; } - public string? ServiceFee { get; set; } + public string ServiceFee { get; set; } = null!; public RouteStatus Status { get; set; } } \ No newline at end of file diff --git a/csharp/src/AdminAPI/Program.cs b/csharp/src/AdminAPI/Program.cs index 87ba945e..34d88200 100644 --- a/csharp/src/AdminAPI/Program.cs +++ b/csharp/src/AdminAPI/Program.cs @@ -125,6 +125,11 @@ .RequireRateLimiting("Fixed") .WithTags("Rate Provider"); +app.MapGroup("/api") + .MapNetworkTypeEndpoints() + .RequireRateLimiting("Fixed") + .WithTags("Network Type"); + app.MapGroup("/api") .MapTokenPriceEndpoints() .RequireRateLimiting("Fixed") diff --git a/csharp/src/Client/Client.csproj b/csharp/src/Client/Client.csproj index 00a25102..ce68a18b 100644 --- a/csharp/src/Client/Client.csproj +++ b/csharp/src/Client/Client.csproj @@ -15,7 +15,11 @@ - + + + + + diff --git a/csharp/src/Common/Enums/NetworkType.cs b/csharp/src/Common/Enums/NetworkType.cs index 96b30280..1906da0a 100644 --- a/csharp/src/Common/Enums/NetworkType.cs +++ b/csharp/src/Common/Enums/NetworkType.cs @@ -6,4 +6,5 @@ public enum NetworkType Solana, Starknet, Fuel, + Aztec, } diff --git a/csharp/src/Data.Abstractions/Entities/Route.cs b/csharp/src/Data.Abstractions/Entities/Route.cs index e154dee7..54e72c78 100644 --- a/csharp/src/Data.Abstractions/Entities/Route.cs +++ b/csharp/src/Data.Abstractions/Entities/Route.cs @@ -19,7 +19,7 @@ public class Route : EntityBase public int RateProviderId { get; set; } - public int? ServiceFeeId { get; set; } + public int ServiceFeeId { get; set; } public RouteStatus Status { get; set; } @@ -35,5 +35,5 @@ public class Route : EntityBase public Wallet DestinationWallet { get; set; } = null!; - public ServiceFee? ServiceFee { get; set; } + public ServiceFee ServiceFee { get; set; } = null!; } \ No newline at end of file diff --git a/csharp/src/Data.Abstractions/Repositories/IRouteRepository.cs b/csharp/src/Data.Abstractions/Repositories/IRouteRepository.cs index 5c59e38a..ea75525c 100644 --- a/csharp/src/Data.Abstractions/Repositories/IRouteRepository.cs +++ b/csharp/src/Data.Abstractions/Repositories/IRouteRepository.cs @@ -28,7 +28,7 @@ public interface IRouteRepository BigInteger minAmount, BigInteger maxAmount, bool ignoreExpenseFee, - string? serviceFee); + string serviceFee); Task UpdateRoutesStatusAsync(int[] ids, RouteStatus status); @@ -43,5 +43,5 @@ public interface IRouteRepository BigInteger minAmount, BigInteger maxAmount, RouteStatus status, - string? serviceFeeName); + string serviceFeeName); } diff --git a/csharp/src/Data.Npgsql/EFNetworkRepository.cs b/csharp/src/Data.Npgsql/EFNetworkRepository.cs index 06cc1273..3d123257 100644 --- a/csharp/src/Data.Npgsql/EFNetworkRepository.cs +++ b/csharp/src/Data.Npgsql/EFNetworkRepository.cs @@ -193,6 +193,7 @@ await dbContext.Nodes NetworkId = network.Id, }; + dbContext.Tokens.Add(token); await dbContext.SaveChangesAsync(); return token; diff --git a/csharp/src/Data.Npgsql/EFRouteRepository.cs b/csharp/src/Data.Npgsql/EFRouteRepository.cs index ff0f5402..9624e3b7 100644 --- a/csharp/src/Data.Npgsql/EFRouteRepository.cs +++ b/csharp/src/Data.Npgsql/EFRouteRepository.cs @@ -26,7 +26,7 @@ public class EFRouteRepository( BigInteger minAmount, BigInteger maxAmount, bool ignoreExpenseFee, - string? serviceFeeName) + string serviceFeeName) { var sourceToken = await networkRepository.GetTokenAsync(sourceNetworkName, sourceTokenSymbol); @@ -63,6 +63,13 @@ public class EFRouteRepository( { throw new ArgumentException($"Rate provider {rateProviderName} not found"); } + + var serviceFee = await feeRepository.GetServiceFeeAsync(serviceFeeName); + + if (serviceFee == null) + { + throw new ArgumentException($"Service fee {serviceFeeName} not found"); + } var route = new Route { @@ -75,16 +82,9 @@ public class EFRouteRepository( MaxAmountInSource = maxAmount.ToString(), Status = RouteStatus.Active, IgnoreExpenseFee = ignoreExpenseFee, + ServiceFee = serviceFee, }; - - var serviceFee = string.IsNullOrEmpty(serviceFeeName) - ? null - : await feeRepository.GetServiceFeeAsync(serviceFeeName); - - if (serviceFee != null) - { - route.ServiceFeeId = serviceFee.Id; - } + dbContext.Routes.Add(route); await dbContext.SaveChangesAsync(); @@ -101,7 +101,7 @@ public class EFRouteRepository( BigInteger minAmount, BigInteger maxAmount, RouteStatus status, - string? serviceFeeName) + string serviceFeeName) { var route = await GetAsync( sourceNetworkName, @@ -123,17 +123,17 @@ public class EFRouteRepository( route.RateProviderId = rateProvider.Id; } - var serviceFee = string.IsNullOrEmpty(serviceFeeName) - ? null - : await feeRepository.GetServiceFeeAsync(serviceFeeName); + var serviceFee = await feeRepository.GetServiceFeeAsync(serviceFeeName); - if (serviceFee != null) + if (serviceFee == null) { - route.ServiceFeeId = serviceFee.Id; + throw new ArgumentException($"Service fee {serviceFeeName} not found"); } + route.MinAmountInSource = minAmount.ToString(); route.MaxAmountInSource = maxAmount.ToString(); + route.ServiceFee = serviceFee; route.Status = status; await dbContext.SaveChangesAsync(); diff --git a/csharp/src/Data.Npgsql/Migrations/20250812103659_AddSeedData.Designer.cs b/csharp/src/Data.Npgsql/Migrations/20250812103659_AddSeedData.Designer.cs new file mode 100644 index 00000000..a0a1a807 --- /dev/null +++ b/csharp/src/Data.Npgsql/Migrations/20250812103659_AddSeedData.Designer.cs @@ -0,0 +1,1067 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Train.Solver.Data.Npgsql; + +#nullable disable + +namespace Train.Solver.Data.Npgsql.Migrations +{ + [DbContext(typeof(SolverDbContext))] + [Migration("20250812103659_AddSeedData")] + partial class AddSeedData + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("FeeTokenId") + .HasColumnType("integer"); + + b.PrimitiveCollection("LastFeeValues") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("TokenId") + .HasColumnType("integer"); + + b.Property("TransactionType") + .HasColumnType("integer") + .HasComment("Transfer=0,Approve=1,HTLCCommit=2,HTLCLock=3,HTLCRedeem=4,HTLCRefund=5,HTLCAddLockSig=6"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("FeeTokenId"); + + b.HasIndex("TokenId", "FeeTokenId", "TransactionType") + .IsUnique(); + + b.ToTable("Expenses"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Network", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChainId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FeePercentageIncrease") + .HasColumnType("integer"); + + b.Property("FeeType") + .HasColumnType("integer"); + + b.Property("HTLCNativeContractAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("HTLCTokenContractAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NativeTokenId") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer") + .HasComment("EVM=0,Solana=1,Starknet=2,Fuel=3"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("NativeTokenId"); + + b.HasIndex("ChainId", "Type") + .IsUnique(); + + b.ToTable("Networks"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("NetworkId") + .HasColumnType("integer"); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("NetworkId"); + + b.HasIndex("ProviderName", "NetworkId") + .IsUnique(); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.RateProvider", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("RateProviders"); + + b.HasData( + new + { + Id = 100000, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Name = "SameAsset", + Version = 0u + }, + new + { + Id = 100001, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Name = "Binance", + Version = 0u + }); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Route", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DestinationTokenId") + .HasColumnType("integer"); + + b.Property("DestinationWalletId") + .HasColumnType("integer"); + + b.Property("IgnoreExpenseFee") + .HasColumnType("boolean"); + + b.Property("MaxAmountInSource") + .IsRequired() + .HasColumnType("text"); + + b.Property("MinAmountInSource") + .IsRequired() + .HasColumnType("text"); + + b.Property("RateProviderId") + .HasColumnType("integer"); + + b.Property("ServiceFeeId") + .HasColumnType("integer"); + + b.Property("SourceTokenId") + .HasColumnType("integer"); + + b.Property("SourceWalletId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("Active=0,Inactive=1,Archived=2"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("DestinationTokenId"); + + b.HasIndex("DestinationWalletId"); + + b.HasIndex("RateProviderId"); + + b.HasIndex("ServiceFeeId"); + + b.HasIndex("SourceWalletId"); + + b.HasIndex("SourceTokenId", "DestinationTokenId") + .IsUnique(); + + b.ToTable("Routes"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.ServiceFee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("FeeInUsd") + .HasColumnType("numeric"); + + b.Property("FeePercentage") + .HasColumnType("numeric"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ServiceFees"); + + b.HasData( + new + { + Id = 100000, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + FeeInUsd = 0m, + FeePercentage = 0m, + Name = "Free", + Version = 0u + }, + new + { + Id = 100001, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + FeeInUsd = 0m, + FeePercentage = 0m, + Name = "Default", + Version = 0u + }); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.SignerAgent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection("SupportedTypes") + .IsRequired() + .HasColumnType("integer[]"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("SignerAgents"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Swap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DestinationAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("DestinationAmount") + .IsRequired() + .HasColumnType("text"); + + b.Property("FeeAmount") + .IsRequired() + .HasColumnType("text"); + + b.Property("Hashlock") + .IsRequired() + .HasColumnType("text"); + + b.Property("MetricId") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("integer"); + + b.Property("SourceAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceAmount") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("CommitId") + .IsUnique(); + + b.HasIndex("CreatedDate"); + + b.HasIndex("DestinationAddress"); + + b.HasIndex("RouteId"); + + b.HasIndex("SourceAddress"); + + b.ToTable("Swaps"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.SwapMetric", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DestinationNetwork") + .IsRequired() + .HasColumnType("text"); + + b.Property("DestinationToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitInUsd") + .HasColumnType("numeric"); + + b.Property("SourceNetwork") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("SwapId") + .HasColumnType("integer"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("VolumeInUsd") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CreatedDate"); + + b.HasIndex("DestinationNetwork"); + + b.HasIndex("SourceNetwork"); + + b.HasIndex("SwapId") + .IsUnique(); + + b.ToTable("SwapMetrics"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Token", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Asset") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Decimals") + .HasColumnType("integer"); + + b.Property("NetworkId") + .HasColumnType("integer"); + + b.Property("TokenContract") + .HasColumnType("text"); + + b.Property("TokenPriceId") + .HasColumnType("integer"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Asset"); + + b.HasIndex("TokenPriceId"); + + b.HasIndex("NetworkId", "Asset") + .IsUnique(); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.TokenPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("PriceInUsd") + .HasColumnType("numeric"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Symbol") + .IsUnique(); + + b.ToTable("TokenPrices"); + + b.HasData( + new + { + Id = 10002, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "bitcoin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "BTC", + Version = 0u + }, + new + { + Id = 10008, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "fuel-network", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "FUEL", + Version = 0u + }, + new + { + Id = 10014, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "avalanche-2", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "AVAX", + Version = 0u + }, + new + { + Id = 10018, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "optimism", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "OP", + Version = 0u + }, + new + { + Id = 10026, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "ethereum", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "ETH", + Version = 0u + }, + new + { + Id = 10035, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "solana", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "SOL", + Version = 0u + }, + new + { + Id = 10043, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "dai", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "DAI", + Version = 0u + }, + new + { + Id = 10046, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "usd-coin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "USDC", + Version = 0u + }, + new + { + Id = 10050, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "immutable-x", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "IMX", + Version = 0u + }, + new + { + Id = 10054, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "binancecoin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "BNB", + Version = 0u + }, + new + { + Id = 10055, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "tether", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "USDT", + Version = 0u + }, + new + { + Id = 10056, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "matic-network", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "MATIC", + Version = 0u + }, + new + { + Id = 10063, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "polygon-ecosystem-token", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "POL", + Version = 0u + }, + new + { + Id = 10069, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "ronin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "RON", + Version = 0u + }); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("FeeAmount") + .IsRequired() + .HasColumnType("text"); + + b.Property("NetworkId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("Completed=0,Initiated=1,Failed=2"); + + b.Property("SwapId") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("TransactionHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer") + .HasComment("Transfer=0,Approve=1,HTLCCommit=2,HTLCLock=3,HTLCRedeem=4,HTLCRefund=5,HTLCAddLockSig=6"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("NetworkId"); + + b.HasIndex("Status"); + + b.HasIndex("SwapId"); + + b.HasIndex("TransactionHash"); + + b.HasIndex("Type"); + + b.HasIndex("TransactionHash", "NetworkId") + .IsUnique(); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.TrustedWallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NetworkType") + .HasColumnType("integer"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Address", "NetworkType"); + + b.HasIndex("Name", "NetworkType") + .IsUnique(); + + b.ToTable("TrustedWallets"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Wallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NetworkType") + .HasColumnType("integer"); + + b.Property("SignerAgentId") + .HasColumnType("integer"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("SignerAgentId"); + + b.HasIndex("Address", "NetworkType"); + + b.HasIndex("Name", "NetworkType") + .IsUnique(); + + b.ToTable("Wallets"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Expense", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "FeeToken") + .WithMany() + .HasForeignKey("FeeTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "Token") + .WithMany() + .HasForeignKey("TokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeeToken"); + + b.Navigation("Token"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Network", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "NativeToken") + .WithMany() + .HasForeignKey("NativeTokenId"); + + b.Navigation("NativeToken"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Node", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Network", "Network") + .WithMany("Nodes") + .HasForeignKey("NetworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Network"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Route", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "DestinationToken") + .WithMany() + .HasForeignKey("DestinationTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Wallet", "DestinationWallet") + .WithMany() + .HasForeignKey("DestinationWalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.RateProvider", "RateProvider") + .WithMany() + .HasForeignKey("RateProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.ServiceFee", "ServiceFee") + .WithMany() + .HasForeignKey("ServiceFeeId"); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "SourceToken") + .WithMany() + .HasForeignKey("SourceTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Wallet", "SourceWallet") + .WithMany() + .HasForeignKey("SourceWalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DestinationToken"); + + b.Navigation("DestinationWallet"); + + b.Navigation("RateProvider"); + + b.Navigation("ServiceFee"); + + b.Navigation("SourceToken"); + + b.Navigation("SourceWallet"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Swap", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Route", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.SwapMetric", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Swap", "Swap") + .WithOne("Metric") + .HasForeignKey("Train.Solver.Data.Abstractions.Entities.SwapMetric", "SwapId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Swap"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Token", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Network", "Network") + .WithMany("Tokens") + .HasForeignKey("NetworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.TokenPrice", "TokenPrice") + .WithMany() + .HasForeignKey("TokenPriceId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Network"); + + b.Navigation("TokenPrice"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Transaction", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Network", "Network") + .WithMany() + .HasForeignKey("NetworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Swap", "Swap") + .WithMany("Transactions") + .HasForeignKey("SwapId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Network"); + + b.Navigation("Swap"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Wallet", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.SignerAgent", "SignerAgent") + .WithMany() + .HasForeignKey("SignerAgentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SignerAgent"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Network", b => + { + b.Navigation("Nodes"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Swap", b => + { + b.Navigation("Metric"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/csharp/src/Data.Npgsql/Migrations/20250812103659_AddSeedData.cs b/csharp/src/Data.Npgsql/Migrations/20250812103659_AddSeedData.cs new file mode 100644 index 00000000..5e8a02a9 --- /dev/null +++ b/csharp/src/Data.Npgsql/Migrations/20250812103659_AddSeedData.cs @@ -0,0 +1,150 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Train.Solver.Data.Npgsql.Migrations +{ + /// + public partial class AddSeedData : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + table: "RateProviders", + columns: new[] { "Id", "Name" }, + values: new object[,] + { + { 100000, "SameAsset" }, + { 100001, "Binance" } + }); + + migrationBuilder.InsertData( + table: "ServiceFees", + columns: new[] { "Id", "FeeInUsd", "FeePercentage", "Name" }, + values: new object[,] + { + { 100000, 0m, 0m, "Free" }, + { 100001, 0m, 0m, "Default" } + }); + + migrationBuilder.InsertData( + table: "TokenPrices", + columns: new[] { "Id", "CreatedDate", "ExternalId", "LastUpdated", "PriceInUsd", "Symbol" }, + values: new object[,] + { + { 10002, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "bitcoin", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "BTC" }, + { 10008, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "fuel-network", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "FUEL" }, + { 10014, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "avalanche-2", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "AVAX" }, + { 10018, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "optimism", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "OP" }, + { 10026, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "ethereum", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "ETH" }, + { 10035, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "solana", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "SOL" }, + { 10043, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "dai", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "DAI" }, + { 10046, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "usd-coin", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "USDC" }, + { 10050, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "immutable-x", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "IMX" }, + { 10054, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "binancecoin", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "BNB" }, + { 10055, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "tether", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "USDT" }, + { 10056, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "matic-network", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "MATIC" }, + { 10063, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "polygon-ecosystem-token", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "POL" }, + { 10069, new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "ronin", new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 0m, "RON" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "RateProviders", + keyColumn: "Id", + keyValue: 100000); + + migrationBuilder.DeleteData( + table: "RateProviders", + keyColumn: "Id", + keyValue: 100001); + + migrationBuilder.DeleteData( + table: "ServiceFees", + keyColumn: "Id", + keyValue: 100000); + + migrationBuilder.DeleteData( + table: "ServiceFees", + keyColumn: "Id", + keyValue: 100001); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10002); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10008); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10014); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10018); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10026); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10035); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10043); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10046); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10050); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10054); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10055); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10056); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10063); + + migrationBuilder.DeleteData( + table: "TokenPrices", + keyColumn: "Id", + keyValue: 10069); + } + } +} diff --git a/csharp/src/Data.Npgsql/Migrations/20250812105030_MakeServiceFeeRequired.Designer.cs b/csharp/src/Data.Npgsql/Migrations/20250812105030_MakeServiceFeeRequired.Designer.cs new file mode 100644 index 00000000..1c8bff33 --- /dev/null +++ b/csharp/src/Data.Npgsql/Migrations/20250812105030_MakeServiceFeeRequired.Designer.cs @@ -0,0 +1,1069 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Train.Solver.Data.Npgsql; + +#nullable disable + +namespace Train.Solver.Data.Npgsql.Migrations +{ + [DbContext(typeof(SolverDbContext))] + [Migration("20250812105030_MakeServiceFeeRequired")] + partial class MakeServiceFeeRequired + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("FeeTokenId") + .HasColumnType("integer"); + + b.PrimitiveCollection("LastFeeValues") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("TokenId") + .HasColumnType("integer"); + + b.Property("TransactionType") + .HasColumnType("integer") + .HasComment("Transfer=0,Approve=1,HTLCCommit=2,HTLCLock=3,HTLCRedeem=4,HTLCRefund=5,HTLCAddLockSig=6"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("FeeTokenId"); + + b.HasIndex("TokenId", "FeeTokenId", "TransactionType") + .IsUnique(); + + b.ToTable("Expenses"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Network", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChainId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FeePercentageIncrease") + .HasColumnType("integer"); + + b.Property("FeeType") + .HasColumnType("integer"); + + b.Property("HTLCNativeContractAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("HTLCTokenContractAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NativeTokenId") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer") + .HasComment("EVM=0,Solana=1,Starknet=2,Fuel=3"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("NativeTokenId"); + + b.HasIndex("ChainId", "Type") + .IsUnique(); + + b.ToTable("Networks"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("NetworkId") + .HasColumnType("integer"); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("NetworkId"); + + b.HasIndex("ProviderName", "NetworkId") + .IsUnique(); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.RateProvider", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("RateProviders"); + + b.HasData( + new + { + Id = 100000, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Name = "SameAsset", + Version = 0u + }, + new + { + Id = 100001, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Name = "Binance", + Version = 0u + }); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Route", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DestinationTokenId") + .HasColumnType("integer"); + + b.Property("DestinationWalletId") + .HasColumnType("integer"); + + b.Property("IgnoreExpenseFee") + .HasColumnType("boolean"); + + b.Property("MaxAmountInSource") + .IsRequired() + .HasColumnType("text"); + + b.Property("MinAmountInSource") + .IsRequired() + .HasColumnType("text"); + + b.Property("RateProviderId") + .HasColumnType("integer"); + + b.Property("ServiceFeeId") + .HasColumnType("integer"); + + b.Property("SourceTokenId") + .HasColumnType("integer"); + + b.Property("SourceWalletId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("Active=0,Inactive=1,Archived=2"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("DestinationTokenId"); + + b.HasIndex("DestinationWalletId"); + + b.HasIndex("RateProviderId"); + + b.HasIndex("ServiceFeeId"); + + b.HasIndex("SourceWalletId"); + + b.HasIndex("SourceTokenId", "DestinationTokenId") + .IsUnique(); + + b.ToTable("Routes"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.ServiceFee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("FeeInUsd") + .HasColumnType("numeric"); + + b.Property("FeePercentage") + .HasColumnType("numeric"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ServiceFees"); + + b.HasData( + new + { + Id = 100000, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + FeeInUsd = 0m, + FeePercentage = 0m, + Name = "Free", + Version = 0u + }, + new + { + Id = 100001, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + FeeInUsd = 0m, + FeePercentage = 0m, + Name = "Default", + Version = 0u + }); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.SignerAgent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection("SupportedTypes") + .IsRequired() + .HasColumnType("integer[]"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("SignerAgents"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Swap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DestinationAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("DestinationAmount") + .IsRequired() + .HasColumnType("text"); + + b.Property("FeeAmount") + .IsRequired() + .HasColumnType("text"); + + b.Property("Hashlock") + .IsRequired() + .HasColumnType("text"); + + b.Property("MetricId") + .HasColumnType("integer"); + + b.Property("RouteId") + .HasColumnType("integer"); + + b.Property("SourceAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceAmount") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("CommitId") + .IsUnique(); + + b.HasIndex("CreatedDate"); + + b.HasIndex("DestinationAddress"); + + b.HasIndex("RouteId"); + + b.HasIndex("SourceAddress"); + + b.ToTable("Swaps"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.SwapMetric", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DestinationNetwork") + .IsRequired() + .HasColumnType("text"); + + b.Property("DestinationToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitInUsd") + .HasColumnType("numeric"); + + b.Property("SourceNetwork") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("SwapId") + .HasColumnType("integer"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.Property("VolumeInUsd") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CreatedDate"); + + b.HasIndex("DestinationNetwork"); + + b.HasIndex("SourceNetwork"); + + b.HasIndex("SwapId") + .IsUnique(); + + b.ToTable("SwapMetrics"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Token", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Asset") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Decimals") + .HasColumnType("integer"); + + b.Property("NetworkId") + .HasColumnType("integer"); + + b.Property("TokenContract") + .HasColumnType("text"); + + b.Property("TokenPriceId") + .HasColumnType("integer"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Asset"); + + b.HasIndex("TokenPriceId"); + + b.HasIndex("NetworkId", "Asset") + .IsUnique(); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.TokenPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("PriceInUsd") + .HasColumnType("numeric"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Symbol") + .IsUnique(); + + b.ToTable("TokenPrices"); + + b.HasData( + new + { + Id = 10002, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "bitcoin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "BTC", + Version = 0u + }, + new + { + Id = 10008, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "fuel-network", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "FUEL", + Version = 0u + }, + new + { + Id = 10014, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "avalanche-2", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "AVAX", + Version = 0u + }, + new + { + Id = 10018, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "optimism", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "OP", + Version = 0u + }, + new + { + Id = 10026, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "ethereum", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "ETH", + Version = 0u + }, + new + { + Id = 10035, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "solana", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "SOL", + Version = 0u + }, + new + { + Id = 10043, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "dai", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "DAI", + Version = 0u + }, + new + { + Id = 10046, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "usd-coin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "USDC", + Version = 0u + }, + new + { + Id = 10050, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "immutable-x", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "IMX", + Version = 0u + }, + new + { + Id = 10054, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "binancecoin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "BNB", + Version = 0u + }, + new + { + Id = 10055, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "tether", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "USDT", + Version = 0u + }, + new + { + Id = 10056, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "matic-network", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "MATIC", + Version = 0u + }, + new + { + Id = 10063, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "polygon-ecosystem-token", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "POL", + Version = 0u + }, + new + { + Id = 10069, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "ronin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "RON", + Version = 0u + }); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("FeeAmount") + .IsRequired() + .HasColumnType("text"); + + b.Property("NetworkId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("Completed=0,Initiated=1,Failed=2"); + + b.Property("SwapId") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("TransactionHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer") + .HasComment("Transfer=0,Approve=1,HTLCCommit=2,HTLCLock=3,HTLCRedeem=4,HTLCRefund=5,HTLCAddLockSig=6"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("NetworkId"); + + b.HasIndex("Status"); + + b.HasIndex("SwapId"); + + b.HasIndex("TransactionHash"); + + b.HasIndex("Type"); + + b.HasIndex("TransactionHash", "NetworkId") + .IsUnique(); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.TrustedWallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NetworkType") + .HasColumnType("integer"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Address", "NetworkType"); + + b.HasIndex("Name", "NetworkType") + .IsUnique(); + + b.ToTable("TrustedWallets"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Wallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NetworkType") + .HasColumnType("integer"); + + b.Property("SignerAgentId") + .HasColumnType("integer"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("SignerAgentId"); + + b.HasIndex("Address", "NetworkType"); + + b.HasIndex("Name", "NetworkType") + .IsUnique(); + + b.ToTable("Wallets"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Expense", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "FeeToken") + .WithMany() + .HasForeignKey("FeeTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "Token") + .WithMany() + .HasForeignKey("TokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeeToken"); + + b.Navigation("Token"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Network", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "NativeToken") + .WithMany() + .HasForeignKey("NativeTokenId"); + + b.Navigation("NativeToken"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Node", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Network", "Network") + .WithMany("Nodes") + .HasForeignKey("NetworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Network"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Route", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "DestinationToken") + .WithMany() + .HasForeignKey("DestinationTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Wallet", "DestinationWallet") + .WithMany() + .HasForeignKey("DestinationWalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.RateProvider", "RateProvider") + .WithMany() + .HasForeignKey("RateProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.ServiceFee", "ServiceFee") + .WithMany() + .HasForeignKey("ServiceFeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "SourceToken") + .WithMany() + .HasForeignKey("SourceTokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Wallet", "SourceWallet") + .WithMany() + .HasForeignKey("SourceWalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DestinationToken"); + + b.Navigation("DestinationWallet"); + + b.Navigation("RateProvider"); + + b.Navigation("ServiceFee"); + + b.Navigation("SourceToken"); + + b.Navigation("SourceWallet"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Swap", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Route", "Route") + .WithMany() + .HasForeignKey("RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.SwapMetric", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Swap", "Swap") + .WithOne("Metric") + .HasForeignKey("Train.Solver.Data.Abstractions.Entities.SwapMetric", "SwapId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Swap"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Token", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Network", "Network") + .WithMany("Tokens") + .HasForeignKey("NetworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.TokenPrice", "TokenPrice") + .WithMany() + .HasForeignKey("TokenPriceId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Network"); + + b.Navigation("TokenPrice"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Transaction", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.Network", "Network") + .WithMany() + .HasForeignKey("NetworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Train.Solver.Data.Abstractions.Entities.Swap", "Swap") + .WithMany("Transactions") + .HasForeignKey("SwapId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Network"); + + b.Navigation("Swap"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Wallet", b => + { + b.HasOne("Train.Solver.Data.Abstractions.Entities.SignerAgent", "SignerAgent") + .WithMany() + .HasForeignKey("SignerAgentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SignerAgent"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Network", b => + { + b.Navigation("Nodes"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Swap", b => + { + b.Navigation("Metric"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/csharp/src/Data.Npgsql/Migrations/20250812105030_MakeServiceFeeRequired.cs b/csharp/src/Data.Npgsql/Migrations/20250812105030_MakeServiceFeeRequired.cs new file mode 100644 index 00000000..de71cb60 --- /dev/null +++ b/csharp/src/Data.Npgsql/Migrations/20250812105030_MakeServiceFeeRequired.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Train.Solver.Data.Npgsql.Migrations +{ + /// + public partial class MakeServiceFeeRequired : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Routes_ServiceFees_ServiceFeeId", + table: "Routes"); + + migrationBuilder.AlterColumn( + name: "ServiceFeeId", + table: "Routes", + type: "integer", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "integer", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_Routes_ServiceFees_ServiceFeeId", + table: "Routes", + column: "ServiceFeeId", + principalTable: "ServiceFees", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Routes_ServiceFees_ServiceFeeId", + table: "Routes"); + + migrationBuilder.AlterColumn( + name: "ServiceFeeId", + table: "Routes", + type: "integer", + nullable: true, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AddForeignKey( + name: "FK_Routes_ServiceFees_ServiceFeeId", + table: "Routes", + column: "ServiceFeeId", + principalTable: "ServiceFees", + principalColumn: "Id"); + } + } +} diff --git a/csharp/src/Data.Npgsql/Migrations/SolverDbContextModelSnapshot.cs b/csharp/src/Data.Npgsql/Migrations/SolverDbContextModelSnapshot.cs index 0c933c34..5cc6a85d 100644 --- a/csharp/src/Data.Npgsql/Migrations/SolverDbContextModelSnapshot.cs +++ b/csharp/src/Data.Npgsql/Migrations/SolverDbContextModelSnapshot.cs @@ -199,6 +199,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsUnique(); b.ToTable("RateProviders"); + + b.HasData( + new + { + Id = 100000, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Name = "SameAsset", + Version = 0u + }, + new + { + Id = 100001, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Name = "Binance", + Version = 0u + }); }); modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Route", b => @@ -234,7 +250,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("RateProviderId") .HasColumnType("integer"); - b.Property("ServiceFeeId") + b.Property("ServiceFeeId") .HasColumnType("integer"); b.Property("SourceTokenId") @@ -306,6 +322,26 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsUnique(); b.ToTable("ServiceFees"); + + b.HasData( + new + { + Id = 100000, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + FeeInUsd = 0m, + FeePercentage = 0m, + Name = "Free", + Version = 0u + }, + new + { + Id = 100001, + CreatedDate = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + FeeInUsd = 0m, + FeePercentage = 0m, + Name = "Default", + Version = 0u + }); }); modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.SignerAgent", b => @@ -560,6 +596,148 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsUnique(); b.ToTable("TokenPrices"); + + b.HasData( + new + { + Id = 10002, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "bitcoin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "BTC", + Version = 0u + }, + new + { + Id = 10008, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "fuel-network", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "FUEL", + Version = 0u + }, + new + { + Id = 10014, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "avalanche-2", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "AVAX", + Version = 0u + }, + new + { + Id = 10018, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "optimism", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "OP", + Version = 0u + }, + new + { + Id = 10026, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "ethereum", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "ETH", + Version = 0u + }, + new + { + Id = 10035, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "solana", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "SOL", + Version = 0u + }, + new + { + Id = 10043, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "dai", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "DAI", + Version = 0u + }, + new + { + Id = 10046, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "usd-coin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "USDC", + Version = 0u + }, + new + { + Id = 10050, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "immutable-x", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "IMX", + Version = 0u + }, + new + { + Id = 10054, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "binancecoin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "BNB", + Version = 0u + }, + new + { + Id = 10055, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "tether", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "USDT", + Version = 0u + }, + new + { + Id = 10056, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "matic-network", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "MATIC", + Version = 0u + }, + new + { + Id = 10063, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "polygon-ecosystem-token", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "POL", + Version = 0u + }, + new + { + Id = 10069, + CreatedDate = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + ExternalId = "ronin", + LastUpdated = new DateTimeOffset(new DateTime(2025, 8, 12, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + PriceInUsd = 0m, + Symbol = "RON", + Version = 0u + }); }); modelBuilder.Entity("Train.Solver.Data.Abstractions.Entities.Transaction", b => @@ -770,7 +948,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("Train.Solver.Data.Abstractions.Entities.ServiceFee", "ServiceFee") .WithMany() - .HasForeignKey("ServiceFeeId"); + .HasForeignKey("ServiceFeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Train.Solver.Data.Abstractions.Entities.Token", "SourceToken") .WithMany() diff --git a/csharp/src/Data.Npgsql/SolverDbContext.cs b/csharp/src/Data.Npgsql/SolverDbContext.cs index 457e3488..8d624a59 100644 --- a/csharp/src/Data.Npgsql/SolverDbContext.cs +++ b/csharp/src/Data.Npgsql/SolverDbContext.cs @@ -46,6 +46,35 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().HasData( + new ServiceFee { Id = 100000, Name = "Free", FeeInUsd = 0, FeePercentage = 0 }, + new ServiceFee { Id = 100001, Name = "Default", FeeInUsd = 0, FeePercentage = 0 } + ); + + modelBuilder.Entity().HasData( + new RateProvider { Id = 100000, Name = "SameAsset" }, + new RateProvider { Id = 100001, Name = "Binance" } + ); + + var seedDate = new DateTime(2025, 8, 12, 0, 0, 0, DateTimeKind.Utc); + + modelBuilder.Entity().HasData( + new TokenPrice { Id = 10002, ExternalId = "bitcoin", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "BTC" }, + new TokenPrice { Id = 10008, ExternalId = "fuel-network", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "FUEL" }, + new TokenPrice { Id = 10014, ExternalId = "avalanche-2", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "AVAX" }, + new TokenPrice { Id = 10018, ExternalId = "optimism", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "OP" }, + new TokenPrice { Id = 10026, ExternalId = "ethereum", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "ETH" }, + new TokenPrice { Id = 10035, ExternalId = "solana", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "SOL" }, + new TokenPrice { Id = 10043, ExternalId = "dai", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "DAI" }, + new TokenPrice { Id = 10046, ExternalId = "usd-coin", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "USDC" }, + new TokenPrice { Id = 10050, ExternalId = "immutable-x", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "IMX" }, + new TokenPrice { Id = 10054, ExternalId = "binancecoin", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "BNB" }, + new TokenPrice { Id = 10055, ExternalId = "tether", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "USDT" }, + new TokenPrice { Id = 10056, ExternalId = "matic-network", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "MATIC" }, + new TokenPrice { Id = 10063, ExternalId = "polygon-ecosystem-token", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "POL" }, + new TokenPrice { Id = 10069, ExternalId = "ronin", LastUpdated = seedDate, CreatedDate = seedDate, Symbol = "RON" } + ); } private static void SetupCustomRelations(ModelBuilder modelBuilder) diff --git a/csharp/src/Infrastructure.Abstractions/Models/DetailedNetworkDto.cs b/csharp/src/Infrastructure.Abstractions/Models/DetailedNetworkDto.cs index 82a64ae1..8b325e87 100644 --- a/csharp/src/Infrastructure.Abstractions/Models/DetailedNetworkDto.cs +++ b/csharp/src/Infrastructure.Abstractions/Models/DetailedNetworkDto.cs @@ -2,20 +2,8 @@ namespace Train.Solver.Infrastructure.Abstractions.Models; -public class DetailedNetworkDto : NetworkDto +public class DetailedNetworkDto : ExtendedNetworkDto { - public string DisplayName { get; set; } = null!; - - public string HTLCNativeContractAddress { get; set; } = null!; - - public string HTLCTokenContractAddress { get; set; } = null!; - - public TransactionFeeType FeeType { get; set; } - - public int FeePercentageIncrease { get; set; } - - public TokenDto? NativeToken { get; set; } = null!; - public IEnumerable Tokens { get; set; } = []; public IEnumerable Nodes { get; set; } = []; diff --git a/csharp/src/Infrastructure.Abstractions/Models/ExtendedNetworkDto.cs b/csharp/src/Infrastructure.Abstractions/Models/ExtendedNetworkDto.cs new file mode 100644 index 00000000..32ea271f --- /dev/null +++ b/csharp/src/Infrastructure.Abstractions/Models/ExtendedNetworkDto.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Train.Solver.Common.Enums; + +namespace Train.Solver.Infrastructure.Abstractions.Models; + +public class ExtendedNetworkDto : NetworkDto +{ + public string DisplayName { get; set; } = null!; + + public string HTLCNativeContractAddress { get; set; } = null!; + + public string HTLCTokenContractAddress { get; set; } = null!; + + public TransactionFeeType FeeType { get; set; } + + public int FeePercentageIncrease { get; set; } + + public TokenDto? NativeToken { get; set; } = null!; +} diff --git a/csharp/src/Infrastructure.Abstractions/Models/ExtendedRouteDto.cs b/csharp/src/Infrastructure.Abstractions/Models/ExtendedRouteDto.cs new file mode 100644 index 00000000..163ea283 --- /dev/null +++ b/csharp/src/Infrastructure.Abstractions/Models/ExtendedRouteDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Train.Solver.Infrastructure.Abstractions.Models; + +public class ExtendedRouteDto +{ + public required ExtendedTokenNetworkDto Source { get; set; } = null!; + + public required ExtendedTokenNetworkDto Destination { get; set; } = null!; +} diff --git a/csharp/src/Infrastructure.Abstractions/Models/ExtendedTokenNetworkDto.cs b/csharp/src/Infrastructure.Abstractions/Models/ExtendedTokenNetworkDto.cs new file mode 100644 index 00000000..4610f879 --- /dev/null +++ b/csharp/src/Infrastructure.Abstractions/Models/ExtendedTokenNetworkDto.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Train.Solver.Infrastructure.Abstractions.Models; + +public class ExtendedTokenNetworkDto : TokenNetworkDto +{ + public new ExtendedNetworkDto Network { get; set; } = null!; +} diff --git a/csharp/src/Infrastructure.Abstractions/Models/RouteDetailedDto.cs b/csharp/src/Infrastructure.Abstractions/Models/RouteDetailedDto.cs index 16df9c3a..cabcc766 100644 --- a/csharp/src/Infrastructure.Abstractions/Models/RouteDetailedDto.cs +++ b/csharp/src/Infrastructure.Abstractions/Models/RouteDetailedDto.cs @@ -2,7 +2,7 @@ namespace Train.Solver.Infrastructure.Abstractions.Models; -public class RouteDetailedDto : RouteDto +public class RouteDetailedDto : ExtendedRouteDto { public required int Id { get; set; } @@ -16,5 +16,9 @@ public class RouteDetailedDto : RouteDto public required string SourceWallet { get; set; } - public required string DestinationWallet { get; set; } + public required string DestinationWallet { get; set; } + + public required bool IgnoreExpenseFee { get; set; } + + public required ServiceFeeDto ServiceFee { get; set; } } diff --git a/csharp/src/Infrastructure/Extensions/MapperExtensions.cs b/csharp/src/Infrastructure/Extensions/MapperExtensions.cs index 70b86c02..e21926ca 100644 --- a/csharp/src/Infrastructure/Extensions/MapperExtensions.cs +++ b/csharp/src/Infrastructure/Extensions/MapperExtensions.cs @@ -90,6 +90,17 @@ public static TokenNetworkDto ToWithNetworkDto(this Token token) return dto; } + public static ExtendedTokenNetworkDto ToWithExtendedNetworkDto(this Token token) + { + var dto = new ExtendedTokenNetworkDto + { + Network = token.Network.ToExtendedDto(), + Token = token.ToDto() + }; + + return dto; + } + public static DetailedNetworkDto ToDetailedDto(this Network network) { var dto = new DetailedNetworkDto @@ -110,19 +121,39 @@ public static DetailedNetworkDto ToDetailedDto(this Network network) return dto; } + public static ExtendedNetworkDto ToExtendedDto(this Network network) + { + var dto = new ExtendedNetworkDto + { + Name = network.Name, + DisplayName = network.DisplayName, + ChainId = network.ChainId, + Type = network.Type, + FeeType = network.FeeType, + FeePercentageIncrease = network.FeePercentageIncrease, + HTLCNativeContractAddress = network.HTLCNativeContractAddress, + HTLCTokenContractAddress = network.HTLCTokenContractAddress, + NativeToken = network.NativeToken?.ToDto(), + }; + + return dto; + } + public static RouteDetailedDto ToDetailedDto(this Route route) { return new RouteDetailedDto { Id = route.Id, - Source = route.SourceToken.ToWithNetworkDto(), + Source = route.SourceToken.ToWithExtendedNetworkDto(), SourceWallet = route.SourceWallet.Address, - Destination = route.DestinationToken.ToWithNetworkDto(), + Destination = route.DestinationToken.ToWithExtendedNetworkDto(), DestinationWallet = route.DestinationWallet.Address, MinAmountInSource = route.MinAmountInSource, MaxAmountInSource = route.MaxAmountInSource, Status = route.Status, RateProviderName = route.RateProvider.Name, + IgnoreExpenseFee = route.IgnoreExpenseFee, + ServiceFee = route.ServiceFee.ToDto(), }; } diff --git a/csharp/src/Workflow.Abstractions/Activities/IRouteActivities.cs b/csharp/src/Workflow.Abstractions/Activities/IRouteActivities.cs index 937e5ca4..e1b9572a 100644 --- a/csharp/src/Workflow.Abstractions/Activities/IRouteActivities.cs +++ b/csharp/src/Workflow.Abstractions/Activities/IRouteActivities.cs @@ -1,11 +1,15 @@ using Temporalio.Activities; using Train.Solver.Infrastructure.Abstractions.Models; using Train.Solver.Common.Enums; +using System.Numerics; namespace Train.Solver.Workflow.Abstractions.Activities; public interface IRouteActivities { + [Activity] + Task ConvertToSourceAsync(RouteDetailedDto route, BigInteger destinationAmount); + [Activity] Task> GetActiveSolverRouteSourceNetworksAsync(); diff --git a/csharp/src/Workflow.Abstractions/Models/PrepareTransactionRequest.cs b/csharp/src/Workflow.Abstractions/Models/PrepareTransactionRequest.cs index c0bc5d73..976fb892 100644 --- a/csharp/src/Workflow.Abstractions/Models/PrepareTransactionRequest.cs +++ b/csharp/src/Workflow.Abstractions/Models/PrepareTransactionRequest.cs @@ -6,7 +6,7 @@ public class PrepareTransactionRequest { public TransactionType Type { get; set; } - public string Args { get; set; } = null!; + public string PrepareArgs { get; set; } = null!; public string NetworkName { get; set; } = null!; } diff --git a/csharp/src/Workflow.Abstractions/Models/TransactionBuilderRequest.cs b/csharp/src/Workflow.Abstractions/Models/TransactionBuilderRequest.cs index e23a8e94..3467f538 100644 --- a/csharp/src/Workflow.Abstractions/Models/TransactionBuilderRequest.cs +++ b/csharp/src/Workflow.Abstractions/Models/TransactionBuilderRequest.cs @@ -6,5 +6,5 @@ public class TransactionBuilderRequest : BaseRequest { public TransactionType Type { get; set; } - public string Args { get; set; } = null!; + public string PrepareArgs { get; set; } = null!; } diff --git a/csharp/src/Workflow.Common/Helpers/TemporalHelper.cs b/csharp/src/Workflow.Common/Helpers/TemporalHelper.cs index bd8f7acb..d6851ac7 100644 --- a/csharp/src/Workflow.Common/Helpers/TemporalHelper.cs +++ b/csharp/src/Workflow.Common/Helpers/TemporalHelper.cs @@ -46,4 +46,7 @@ public static string BuildEventListenerId(string networkName) => $"EventListenerWorkflow-{networkName.ToUpper()}"; public static string BuildProcessorId(string networkName, TransactionType type, Guid uniqueId) => $"{networkName}-{type}-{uniqueId}"; + + public static string BuildRebalanceProcessorId(string networkName, Guid uniqueId) + => $"Rebalance-{networkName}-{uniqueId}"; } diff --git a/csharp/src/Workflow.EVM/Activities/EVMBlockchainActivities.cs b/csharp/src/Workflow.EVM/Activities/EVMBlockchainActivities.cs index 10f62655..0e291466 100644 --- a/csharp/src/Workflow.EVM/Activities/EVMBlockchainActivities.cs +++ b/csharp/src/Workflow.EVM/Activities/EVMBlockchainActivities.cs @@ -104,20 +104,20 @@ public virtual async Task GetBatchTransactionAsync(GetBatch } return transaction; - } + } [Activity] public virtual Task BuildTransactionAsync(TransactionBuilderRequest request) { PrepareTransactionDto result = request.Type switch { - TransactionType.Transfer => EVMTransactionBuilder.BuildTransferTransaction(request.Network, request.Args), - TransactionType.Approve => EVMTransactionBuilder.BuildApproveTransaction(request.Network, request.Args), - TransactionType.HTLCCommit => EVMTransactionBuilder.BuildHTLCCommitTransaction(request.Network, request.Args), - TransactionType.HTLCLock => EVMTransactionBuilder.BuildHTLCLockTransaction(request.Network, request.Args), - TransactionType.HTLCRedeem => EVMTransactionBuilder.BuildHTLCRedeemTranaction(request.Network, request.Args), - TransactionType.HTLCRefund => EVMTransactionBuilder.BuildHTLCRefundTransaction(request.Network, request.Args), - TransactionType.HTLCAddLockSig => EVMTransactionBuilder.BuildHTLCAddLockSigTransaction(request.Network, request.Args), + TransactionType.Transfer => EVMTransactionBuilder.BuildTransferTransaction(request.Network, request.PrepareArgs), + TransactionType.Approve => EVMTransactionBuilder.BuildApproveTransaction(request.Network, request.PrepareArgs), + TransactionType.HTLCCommit => EVMTransactionBuilder.BuildHTLCCommitTransaction(request.Network, request.PrepareArgs), + TransactionType.HTLCLock => EVMTransactionBuilder.BuildHTLCLockTransaction(request.Network, request.PrepareArgs), + TransactionType.HTLCRedeem => EVMTransactionBuilder.BuildHTLCRedeemTranaction(request.Network, request.PrepareArgs), + TransactionType.HTLCRefund => EVMTransactionBuilder.BuildHTLCRefundTransaction(request.Network, request.PrepareArgs), + TransactionType.HTLCAddLockSig => EVMTransactionBuilder.BuildHTLCAddLockSigTransaction(request.Network, request.PrepareArgs), _ => throw new ArgumentOutOfRangeException(nameof(request.Type), $"Transaction type {request.Type} is not supported for network {request.Network.Name}"), }; diff --git a/csharp/src/Workflow.EVM/Helpers/FeeEstimatorBase.cs b/csharp/src/Workflow.EVM/Helpers/FeeEstimatorBase.cs index c3dd8a90..c0bfc2e5 100644 --- a/csharp/src/Workflow.EVM/Helpers/FeeEstimatorBase.cs +++ b/csharp/src/Workflow.EVM/Helpers/FeeEstimatorBase.cs @@ -6,6 +6,7 @@ using Nethereum.RPC.Eth.DTOs; using Nethereum.Web3; using System.Numerics; +using Train.Solver.Common.Extensions; using Train.Solver.Infrastructure.Abstractions.Exceptions; using Train.Solver.SmartNodeInvoker; using Train.Solver.Workflow.Abstractions.Models; @@ -33,7 +34,8 @@ public async Task GetGasLimitAsync( string toAddress, string? tokenContract, BigInteger amount, - string? callData = null) + string? callData = null, + int increasePercentage = 100) { var callInput = new CallInput { @@ -64,7 +66,7 @@ public async Task GetGasLimitAsync( if (estimatedGasResult.Succeeded) { - return estimatedGasResult.Data.Value; + return estimatedGasResult.Data.Value.PercentageIncrease(increasePercentage); } else { diff --git a/csharp/src/Workflow.EVM/Workflows/EVMTransactionProcessor.cs b/csharp/src/Workflow.EVM/Workflows/EVMTransactionProcessor.cs index 6f1a9b21..206094fa 100644 --- a/csharp/src/Workflow.EVM/Workflows/EVMTransactionProcessor.cs +++ b/csharp/src/Workflow.EVM/Workflows/EVMTransactionProcessor.cs @@ -36,7 +36,7 @@ public async Task RunAsync(TransactionRequest request, Tran new TransactionBuilderRequest() { Network = request.Network, - Args = request.PrepareArgs, + PrepareArgs = request.PrepareArgs, Type = request.Type }), TemporalHelper.DefaultActivityOptions(request.Network.Type)); diff --git a/csharp/src/Workflow.Swap/Activities/RouteActivities.cs b/csharp/src/Workflow.Swap/Activities/RouteActivities.cs index 5a4a96df..4547698c 100644 --- a/csharp/src/Workflow.Swap/Activities/RouteActivities.cs +++ b/csharp/src/Workflow.Swap/Activities/RouteActivities.cs @@ -1,13 +1,20 @@ -using Temporalio.Activities; +using System.Numerics; +using Temporalio.Activities; +using Train.Solver.Common.Enums; +using Train.Solver.Common.Extensions; +using Train.Solver.Common.Helpers; using Train.Solver.Data.Abstractions.Repositories; +using Train.Solver.Infrastructure.Abstractions; using Train.Solver.Infrastructure.Abstractions.Models; +using Train.Solver.Infrastructure.DependencyInjection; using Train.Solver.Infrastructure.Extensions; -using Train.Solver.Common.Enums; using Train.Solver.Workflow.Abstractions.Activities; namespace Train.Solver.Workflow.Swap.Activities; -public class RouteActivities(IRouteRepository routeRepository) : IRouteActivities +public class RouteActivities( + IRouteRepository routeRepository, + KeyedServiceResolver rateProviderResolver) : IRouteActivities { [Activity] public async Task> GetAllRoutesAsync() @@ -37,4 +44,32 @@ public virtual async Task UpdateRoutesStatusAsync( { await routeRepository.UpdateRoutesStatusAsync(routeIds, status); } + + [Activity] + public virtual async Task ConvertToSourceAsync( + RouteDetailedDto route, + BigInteger destinationAmount) + { + var rateProvider = rateProviderResolver.Resolve(route.RateProviderName); + + if (rateProvider is null) + { + throw new ArgumentException($"Rate provider {route.RateProviderName} not found."); + } + + + var rate = await rateProvider.GetRateAsync(new RouteDto + { + Source = route.Source, + Destination = route.Destination, + }); + + var sourceAmount = destinationAmount.ConvertTokenAmount( + route.Source.Token.Decimals, + route.Destination.Token.Decimals, + TokenUnitHelper.ToBaseUnits(rate, route.Source.Token.Decimals), + route.Source.Token.Decimals); + + return sourceAmount; + } } diff --git a/csharp/src/Workflow.Swap/Workflows/RouteStatusUpdaterWorkflow.cs b/csharp/src/Workflow.Swap/Workflows/RouteStatusUpdaterWorkflow.cs index 07fcfc33..a03e225b 100644 --- a/csharp/src/Workflow.Swap/Workflows/RouteStatusUpdaterWorkflow.cs +++ b/csharp/src/Workflow.Swap/Workflows/RouteStatusUpdaterWorkflow.cs @@ -57,28 +57,42 @@ public async Task RunAsync() continue; } - var routesToDisable = group - .Where(route => route.Status == RouteStatus.Active && BigInteger.Parse(route.MaxAmountInSource) > balance.Amount) - .ToList(); - if (routesToDisable.Any()) + var routesIdsToDisable = new List(); + var routesIdsToEnable = new List(); + + foreach (var route in group) + { + var balanceInSource = await ExecuteActivityAsync( + (IRouteActivities x) => x.ConvertToSourceAsync( + route, + balance.Amount), + TemporalHelper.DefaultActivityOptions(Constants.CoreTaskQueue)); + + if (route.Status == RouteStatus.Active && balanceInSource < BigInteger.Parse(route.MaxAmountInSource)) + { + routesIdsToDisable.Add(route.Id); + } + else if (route.Status == RouteStatus.Inactive && balanceInSource >= BigInteger.Parse(route.MaxAmountInSource)) + { + routesIdsToEnable.Add(route.Id); + } + } + + if (routesIdsToDisable.Any()) { await ExecuteActivityAsync( (IRouteActivities x) => x.UpdateRoutesStatusAsync( - group.Select(route => route.Id).ToArray(), + routesIdsToDisable.ToArray(), RouteStatus.Inactive), TemporalHelper.DefaultActivityOptions(Constants.CoreTaskQueue)); } - var routesToEnable = group - .Where(route => route.Status == RouteStatus.Inactive && BigInteger.Parse(route.MaxAmountInSource) <= balance.Amount) - .ToList(); - - if (routesToEnable.Any()) + if (routesIdsToEnable.Any()) { await ExecuteActivityAsync( (IRouteActivities x) => x.UpdateRoutesStatusAsync( - group.Select(route => route.Id).ToArray(), + routesIdsToEnable.ToArray(), RouteStatus.Active), TemporalHelper.DefaultActivityOptions(Constants.CoreTaskQueue)); } diff --git a/csharp/src/Workflow.Swap/Workflows/TransactionBuilderWorkflow.cs b/csharp/src/Workflow.Swap/Workflows/TransactionBuilderWorkflow.cs index 43feee95..7efb97b9 100644 --- a/csharp/src/Workflow.Swap/Workflows/TransactionBuilderWorkflow.cs +++ b/csharp/src/Workflow.Swap/Workflows/TransactionBuilderWorkflow.cs @@ -22,7 +22,7 @@ public async Task RunAsync(PrepareTransactionRequest requ (IBlockchainActivities x) => x.BuildTransactionAsync(new() { Type = request.Type, - Args = request.Args, + PrepareArgs = request.PrepareArgs, Network = network }), TemporalHelper.DefaultActivityOptions(network.Type)); diff --git a/js/src/Blockchain/Blockchain.Abstraction/Extensions/StringExtensions.ts b/js/src/Blockchain/Blockchain.Abstraction/Extensions/StringExtensions.ts index 9e5bc02c..bcb31e84 100644 --- a/js/src/Blockchain/Blockchain.Abstraction/Extensions/StringExtensions.ts +++ b/js/src/Blockchain/Blockchain.Abstraction/Extensions/StringExtensions.ts @@ -1,9 +1,7 @@ import { TransactionType } from "../Models/TransacitonModels/TransactionType"; export function decodeJson(json: string): T { - const parsed = JSON.parse(json); - const lowerCamelCaseObj = convertKeysToCamelCase(parsed); - return lowerCamelCaseObj as T; + return JSON.parse(json) as T; } export function removeHexPrefix(hex: string): string { @@ -39,18 +37,4 @@ export function ToHex(value: bigint): string { export function buildProcessorId(guid: string, networkName: string, type: TransactionType): string { return `${networkName}-${TransactionType[type]}-${guid}`; -} - -function toLowerCamelCase(str) { - return str.charAt(0).toLowerCase() + str.slice(1); -} - -function convertKeysToCamelCase(obj) { - const result = {}; - for (const key in obj) { - if (Object.hasOwnProperty.call(obj, key)) { - result[toLowerCamelCase(key)] = obj[key]; - } - } - return result; -} +} \ No newline at end of file diff --git a/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/HTLCCommitTransactionPrepareRequest.ts b/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/HTLCCommitTransactionPrepareRequest.ts index 7ea0eb09..c4a6a2a8 100644 --- a/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/HTLCCommitTransactionPrepareRequest.ts +++ b/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/HTLCCommitTransactionPrepareRequest.ts @@ -1,5 +1,5 @@ export interface HTLCCommitTransactionPrepareRequest { - receiever: string; + receiver: string; hopChains: string[]; hopAssets: string[]; hopAddresses: string[]; @@ -10,4 +10,4 @@ export interface HTLCCommitTransactionPrepareRequest { timelock: number; amount: number; id: string; -} \ No newline at end of file +} diff --git a/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/TransactionBuilderRequest.ts b/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/TransactionBuilderRequest.ts index 2d934257..0ad2be82 100644 --- a/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/TransactionBuilderRequest.ts +++ b/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/TransactionBuilderRequest.ts @@ -2,7 +2,7 @@ import { BaseRequest } from "../BaseRequest"; import { TransactionType } from "../TransacitonModels/TransactionType"; export interface TransactionBuilderRequest extends BaseRequest { - args: string; + prepareArgs: string; type: TransactionType; fromAddress: string; swapId?: number; diff --git a/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/TransferBuilderResponse.ts b/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/TransferBuilderResponse.ts index 19540cf8..5698d899 100644 --- a/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/TransferBuilderResponse.ts +++ b/js/src/Blockchain/Blockchain.Abstraction/Models/TransactionBuilderModels/TransferBuilderResponse.ts @@ -1,8 +1,8 @@ export interface PrepareTransactionResponse { toAddress: string; data?: string; - amount: number; + amount: string; asset?: string; callDataAsset: string; - callDataAmount: number; + callDataAmount: string; } \ No newline at end of file diff --git a/js/src/Blockchain/Blockchain.Fuel/Activities/FuelBlockchainActivities.ts b/js/src/Blockchain/Blockchain.Fuel/Activities/FuelBlockchainActivities.ts index 596728a8..39bb5ab4 100644 --- a/js/src/Blockchain/Blockchain.Fuel/Activities/FuelBlockchainActivities.ts +++ b/js/src/Blockchain/Blockchain.Fuel/Activities/FuelBlockchainActivities.ts @@ -41,29 +41,29 @@ export class FuelBlockchainActivities implements IFuelBlockchainActivities { ) { } readonly MaxFeeMultiplier = 7; - readonly GasLimitMultiplier = 2; - - public async BuildTransaction(request: TransactionBuilderRequest): Promise { - try { - switch (request.type) { - case TransactionType.HTLCLock: - return createLockCallData(request.network, request.args); - case TransactionType.HTLCRedeem: - return createRedeemCallData(request.network, request.args); - case TransactionType.HTLCRefund: - return createRefundCallData(request.network, request.args); - case TransactionType.HTLCAddLockSig: - return createAddLockSigCallData(request.network, request.args); - case TransactionType.HTLCCommit: - return createCommitCallData(request.network, request.args); - default: - throw new Error(`Unknown function name ${request.type}`); - } - } - catch (error) { - throw error; - } + readonly GasLimitMultiplier = 3; + + public async BuildTransaction(request: TransactionBuilderRequest): Promise { + try { + switch (request.type) { + case TransactionType.HTLCLock: + return createLockCallData(request.network, request.prepareArgs); + case TransactionType.HTLCRedeem: + return createRedeemCallData(request.network, request.prepareArgs); + case TransactionType.HTLCRefund: + return createRefundCallData(request.network, request.prepareArgs); + case TransactionType.HTLCAddLockSig: + return createAddLockSigCallData(request.network, request.prepareArgs); + case TransactionType.HTLCCommit: + return createCommitCallData(request.network, request.prepareArgs); + default: + throw new Error(`Unknown function name ${request.type}`); + } + } + catch (error) { + throw error; } + } public async GetBalance(request: BalanceRequest): Promise { @@ -206,7 +206,7 @@ export class FuelBlockchainActivities implements IFuelBlockchainActivities { const estimatedDependencies = await wallet.provider.estimateTxDependencies(txRequest); txRequest.maxFee = bn(estimatedDependencies.dryRunStatus.totalFee).mul(this.MaxFeeMultiplier); - txRequest.gasLimit = bn(estimatedDependencies.dryRunStatus.totalGas).mul(this.GasLimitMultiplier); + txRequest.gasLimit = bn(estimatedDependencies.dryRunStatus.totalGas).add(bn("100000").mul(this.GasLimitMultiplier)) await this.ensureSufficientBalance( { diff --git a/js/src/Blockchain/Blockchain.Fuel/Activities/Helper/FuelTransactionBuilder.ts b/js/src/Blockchain/Blockchain.Fuel/Activities/Helper/FuelTransactionBuilder.ts index 16a4be9a..79dc1afb 100644 --- a/js/src/Blockchain/Blockchain.Fuel/Activities/Helper/FuelTransactionBuilder.ts +++ b/js/src/Blockchain/Blockchain.Fuel/Activities/Helper/FuelTransactionBuilder.ts @@ -36,10 +36,10 @@ export async function createRefundCallData(network: DetailedNetworkDto, args: st return { data: JSON.stringify(txRequest), - amount: 0, + amount: "0", asset: network.nativeToken.symbol, callDataAsset: token.symbol, - callDataAmount: 0, + callDataAmount: "0", toAddress: htlcContractAddress, }; } @@ -53,9 +53,12 @@ export async function createCommitCallData(network: DetailedNetworkDto, args: st throw new Error(`Token not found for network ${network.name} and asset ${commitRequest.sourceAsset}`) }; - if (token.symbol !== network.nativeToken.symbol) { - throw new Error(`Token ${token.symbol} is not the native token for network ${network.name}. Please use the native token for HTLC transactions.`); - } + const createEmptyArray = (length: number, char: string) => + Array.from({ length }, () => ''.padEnd(64, char)); + + const hopChains = createEmptyArray(5, ' '); + const hopAssets = createEmptyArray(5, ' '); + const hopAddresses = createEmptyArray(5, ' '); const htlcContractAddress = token.contract ? network.htlcNativeContractAddress @@ -63,51 +66,37 @@ export async function createCommitCallData(network: DetailedNetworkDto, args: st const provider = new Provider(network.nodes[0].url); const contractInstance = new Contract(htlcContractAddress, abi, provider); - const receiverAddress = { bits: commitRequest.receiever }; - - const createEmptyArray = (length: number, char: string) => - Array.from({ length }, () => ''.padEnd(64, char)); + const receiverAddress = { bits: commitRequest.receiver }; - const hopChains = createEmptyArray(5, ' ') - const hopAssets = createEmptyArray(5, ' ') - const hopAddresses = createEmptyArray(5, ' ') - - try { - const callConfig = await contractInstance.functions - .commit( - hopChains, hopAssets, hopAddresses, - commitRequest.destinationChain.padEnd(64, ' '), - commitRequest.destinationAsset.padEnd(64, ' '), - commitRequest.destinationAddress.padEnd(64, ' '), - commitRequest.sourceAsset.padEnd(64, ' '), - commitRequest.id, - receiverAddress, - DateTime.fromUnixSeconds(commitRequest.timelock).toTai64()) - .callParams({ - forward: [Number(commitRequest.amount), await provider.getBaseAssetId()] - }) - .txParams({ - maxFee: bn(1000000), - }); + const callConfig = contractInstance.functions + .commit( + hopChains, + hopAssets, + hopAddresses, + commitRequest.destinationChain.padEnd(64, ' '), + commitRequest.destinationAsset.padEnd(64, ' '), + commitRequest.destinationAddress.padEnd(64, ' '), + commitRequest.sourceAsset.padEnd(64, ' '), + commitRequest.id, + receiverAddress, + DateTime.fromUnixSeconds(commitRequest.timelock).toTai64()) + .callParams({ + forward: [Number(commitRequest.amount), await provider.getBaseAssetId()] + }) + .txParams({ + maxFee: bn(1000000), + }); const txRequest = await callConfig.getTransactionRequest(); - return { - data: JSON.stringify(txRequest), - amount: 0, - asset: network.nativeToken.symbol, - callDataAsset: token.symbol, - callDataAmount: 0, - toAddress: htlcContractAddress, - }; - - } - catch (ex) { - console.error(`Error creating commit call data: ${ex.message}`); - throw ex; - } - - + return { + data: JSON.stringify(txRequest), + amount: "0", + asset: network.nativeToken.symbol, + callDataAsset: token.symbol, + callDataAmount: "0", + toAddress: htlcContractAddress, + }; } export async function createRedeemCallData(network: DetailedNetworkDto, args: string): Promise { @@ -137,10 +126,10 @@ export async function createRedeemCallData(network: DetailedNetworkDto, args: st return { data: JSON.stringify(txRequest), - amount: 0, + amount: "0", asset: network.nativeToken.symbol, callDataAsset: token.symbol, - callDataAmount: 0, + callDataAmount: "0", toAddress: htlcContractAddress, }; } @@ -192,10 +181,10 @@ export async function createLockCallData(network: DetailedNetworkDto, args: stri return { data: JSON.stringify(txRequest), - amount: lockRequest.amount + lockRequest.reward, + amount: sendAmount.toString(), asset: lockRequest.sourceAsset, callDataAsset: lockRequest.sourceAsset, - callDataAmount: lockRequest.amount + lockRequest.reward, + callDataAmount: sendAmount.toString(), toAddress: htlcContractAddress, }; } @@ -233,14 +222,14 @@ export async function createAddLockSigCallData(network: DetailedNetworkDto, args return { data: JSON.stringify(txRequest), - amount: 0, + amount: "0", asset: network.nativeToken.symbol, callDataAsset: token.symbol, - callDataAmount: 0, + callDataAmount: "0", toAddress: htlcContractAddress, }; } function PadStringsTo64(input: string[]): string[] { return input.map(str => str.padEnd(64, ' ')); -} \ No newline at end of file +} diff --git a/js/src/Blockchain/Blockchain.Fuel/Workflows/FuelTransactionProcessor.ts b/js/src/Blockchain/Blockchain.Fuel/Workflows/FuelTransactionProcessor.ts index 09fdd3ae..bcc11ac3 100644 --- a/js/src/Blockchain/Blockchain.Fuel/Workflows/FuelTransactionProcessor.ts +++ b/js/src/Blockchain/Blockchain.Fuel/Workflows/FuelTransactionProcessor.ts @@ -53,7 +53,7 @@ export async function FuelTransactionProcessor( const preparedTransaction = await defaultActivities.BuildTransaction({ fromAddress: request.fromAddress, network: request.network, - args: request.prepareArgs, + prepareArgs: request.prepareArgs, type: request.type, }); @@ -62,7 +62,7 @@ export async function FuelTransactionProcessor( fromAddress: request.fromAddress, callData: preparedTransaction.data, callDataAsset: preparedTransaction.callDataAsset, - callDataAmount: preparedTransaction.callDataAmount, + callDataAmount: Number(preparedTransaction.callDataAmount), }); const signedRawData = await defaultActivities.signTransaction( diff --git a/js/src/Blockchain/Blockchain.Starknet/Activities/Helper/StarknetTransactionBuilder.ts b/js/src/Blockchain/Blockchain.Starknet/Activities/Helper/StarknetTransactionBuilder.ts index 9273cc4a..0b9d1774 100644 --- a/js/src/Blockchain/Blockchain.Starknet/Activities/Helper/StarknetTransactionBuilder.ts +++ b/js/src/Blockchain/Blockchain.Starknet/Activities/Helper/StarknetTransactionBuilder.ts @@ -35,9 +35,9 @@ export function createRefundCallData(network: DetailedNetworkDto, args: string): return { data: JSON.stringify(methodCall), - amount: 0, + amount: "0", asset: network.nativeToken.symbol, - callDataAmount: 0, + callDataAmount: "0", callDataAsset: token.symbol, toAddress: htlcContractAddress, }; @@ -70,10 +70,10 @@ export function createRedeemCallData(network: DetailedNetworkDto, args: string): return { data: JSON.stringify(methodCall), - amount: 0, + amount: "0", asset: network.nativeToken.symbol, callDataAsset: token.symbol, - callDataAmount: 0, + callDataAmount: "0", toAddress: htlcContractAddress, }; } @@ -113,12 +113,14 @@ export function createLockCallData(network: DetailedNetworkDto, args: string): P calldata: callData }; + const callDataAmount = lockRequest.amount + lockRequest.reward + return { data: JSON.stringify(methodCall), - amount: 0, + amount: "0", asset: lockRequest.sourceAsset, callDataAsset: lockRequest.sourceAsset, - callDataAmount: lockRequest.amount + lockRequest.reward, + callDataAmount: callDataAmount.toString(), toAddress: htlcContractAddress, }; } @@ -151,10 +153,10 @@ export function createAddLockSigCallData(network: DetailedNetworkDto, args: stri return { data: JSON.stringify(methodCall), - amount: 0, + amount: "0", asset: network.nativeToken.symbol, callDataAsset: token.symbol, - callDataAmount: 0, + callDataAmount: "0", toAddress: htlcContractAddress, }; } @@ -186,10 +188,10 @@ export function createApproveCallData(network: DetailedNetworkDto, args: string) return { data: JSON.stringify(methodCall), - amount: 0, + amount: "0", asset: token.symbol, callDataAsset: token.symbol, - callDataAmount: 0, + callDataAmount: "0", toAddress: token.contract, }; } @@ -216,10 +218,10 @@ export function createTransferCallData(network: DetailedNetworkDto, args: string return { data: JSON.stringify(methodCall), - amount: 0, + amount: "0", asset: token.symbol, callDataAsset: token.symbol, - callDataAmount: transferRequest.amount, + callDataAmount: transferRequest.amount.toString(), toAddress: token.contract, }; } @@ -245,7 +247,7 @@ export function createCommitCallData(network: DetailedNetworkDto, args: string): shortString.encodeShortString(commitRequest.sourceAsset), byteArray.byteArrayFromString(commitRequest.destinationAddress), shortString.encodeShortString(commitRequest.sourceAsset), - commitRequest.receiever, + commitRequest.receiver, cairo.uint256(commitRequest.timelock), token.contract ]; @@ -258,10 +260,10 @@ export function createCommitCallData(network: DetailedNetworkDto, args: string): return { data: JSON.stringify(methodCall), - amount: 0, + amount: "0", asset: commitRequest.sourceAsset, callDataAsset: commitRequest.sourceAsset, - callDataAmount: commitRequest.amount, + callDataAmount: commitRequest.amount.toString(), toAddress: htlcContractAddress, }; } diff --git a/js/src/Blockchain/Blockchain.Starknet/Activities/StarknetBlockchainActivities.ts b/js/src/Blockchain/Blockchain.Starknet/Activities/StarknetBlockchainActivities.ts index 8a177917..01f488cd 100644 --- a/js/src/Blockchain/Blockchain.Starknet/Activities/StarknetBlockchainActivities.ts +++ b/js/src/Blockchain/Blockchain.Starknet/Activities/StarknetBlockchainActivities.ts @@ -257,19 +257,19 @@ export class StarknetBlockchainActivities implements IStarknetBlockchainActiviti try { switch (request.type) { case TransactionType.HTLCLock: - return createLockCallData(request.network, request.args); + return createLockCallData(request.network, request.prepareArgs); case TransactionType.HTLCRedeem: - return createRedeemCallData(request.network, request.args); + return createRedeemCallData(request.network, request.prepareArgs); case TransactionType.HTLCRefund: - return createRefundCallData(request.network, request.args); + return createRefundCallData(request.network, request.prepareArgs); case TransactionType.HTLCAddLockSig: - return createAddLockSigCallData(request.network, request.args); + return createAddLockSigCallData(request.network, request.prepareArgs); case TransactionType.Approve: - return createApproveCallData(request.network, request.args); + return createApproveCallData(request.network, request.prepareArgs); case TransactionType.Transfer: - return createTransferCallData(request.network, request.args); + return createTransferCallData(request.network, request.prepareArgs); case TransactionType.HTLCCommit: - return createCommitCallData(request.network, request.args); + return createCommitCallData(request.network, request.prepareArgs); default: throw new Error(`Unknown function name ${request.type}`); } diff --git a/js/src/Blockchain/Blockchain.Starknet/Workflows/StarknetTransactionProcessor.ts b/js/src/Blockchain/Blockchain.Starknet/Workflows/StarknetTransactionProcessor.ts index d892c579..754bc6d5 100644 --- a/js/src/Blockchain/Blockchain.Starknet/Workflows/StarknetTransactionProcessor.ts +++ b/js/src/Blockchain/Blockchain.Starknet/Workflows/StarknetTransactionProcessor.ts @@ -44,7 +44,7 @@ export async function StarknetTransactionProcessor( const preparedTransaction = await defaultActivities.BuildTransaction({ network: request.network, - args: request.prepareArgs, + prepareArgs: request.prepareArgs, type: request.type, fromAddress: request.fromAddress, swapId: request.swapId, @@ -55,7 +55,7 @@ export async function StarknetTransactionProcessor( context.fee = await nonRetryableActivities.EstimateFee({ network: request.network, toAddress: preparedTransaction.toAddress, - amount: preparedTransaction.amount, + amount: Number(preparedTransaction.amount), fromAddress: request.fromAddress, asset: preparedTransaction.asset!, callData: preparedTransaction.data, diff --git a/js/src/index.ts b/js/src/index.ts index 086c3d99..2e817460 100644 --- a/js/src/index.ts +++ b/js/src/index.ts @@ -1,6 +1,8 @@ import starknetWorker from './Blockchain/Blockchain.Starknet/Worker/StarknetWorker'; import fuelWorker from './Blockchain/Blockchain.Fuel/Worker/FuelWorker'; +import * as dotenv from 'dotenv'; +dotenv.config(); const network = process.env.TrainSolver__NetworkType; // Run the corresponding worker based on the network name