From edd375dbf6ca335a25acdd7bc44694e8f66ad5d3 Mon Sep 17 00:00:00 2001 From: Aren Date: Wed, 11 Mar 2026 13:39:35 +0400 Subject: [PATCH 1/2] Add new network configurations for Starknet Sepolia, Aztec Devnet, and Solana Devnet Updated station-config.json to include new network entries with relevant details such as CAIP2 ID, display name, chain ID, network type, logo URL, and native token information. Enhanced the NetworkConfig class to support a new Slug property. Modified OrderEndpoints to enrich order responses with CAIP2 IDs based on network slugs. --- .../StationAPI/Configuration/StationConfig.cs | 1 + .../StationAPI/Endpoints/OrderEndpoints.cs | 36 ++++++++++++- csharp/src/StationAPI/station-config.json | 51 +++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/csharp/src/StationAPI/Configuration/StationConfig.cs b/csharp/src/StationAPI/Configuration/StationConfig.cs index 96349fbe..4ce3f2d0 100644 --- a/csharp/src/StationAPI/Configuration/StationConfig.cs +++ b/csharp/src/StationAPI/Configuration/StationConfig.cs @@ -11,6 +11,7 @@ public class StationConfig public class NetworkConfig { public string Caip2Id { get; set; } = null!; + public string Slug { get; set; } = null!; public string DisplayName { get; set; } = null!; public string ChainId { get; set; } = null!; public string NetworkType { get; set; } = null!; diff --git a/csharp/src/StationAPI/Endpoints/OrderEndpoints.cs b/csharp/src/StationAPI/Endpoints/OrderEndpoints.cs index d4d73d33..36aada1c 100644 --- a/csharp/src/StationAPI/Endpoints/OrderEndpoints.cs +++ b/csharp/src/StationAPI/Endpoints/OrderEndpoints.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Text.Json.Nodes; using Train.Solver.StationAPI.Configuration; using Train.Solver.StationAPI.Helpers; using Train.Solver.StationAPI.Models; @@ -59,6 +60,7 @@ private static async Task GetOrderAsync( }); } + var slugToCaip2Id = BuildSlugToCaip2Id(config); var response = new OrderStatusResponse { Solver = new SolverProfileResponse @@ -68,7 +70,7 @@ private static async Task GetOrderAsync( Description = solver.Description, LogoUrl = solver.LogoUrl }, - Order = orderJson + Order = EnrichOrderWithCaip2Id(orderJson.Value, slugToCaip2Id) }; return Results.Ok(new ApiResponse { Data = response }); @@ -140,6 +142,8 @@ await SseWriter.WriteEventAsync(context.Response, "error", LogoUrl = solver.LogoUrl }; + var slugToCaip2Id = BuildSlugToCaip2Id(config); + // Subscribe to webhook-pushed events var reader = hub.Subscribe(hashlock); @@ -149,7 +153,7 @@ await SseWriter.WriteEventAsync(context.Response, "error", var orderJson = await solverClient.GetOrderAsync(solverId, hashlock, ct); if (orderJson is not null) { - var orderResponse = new OrderStatusResponse { Solver = profile, Order = orderJson }; + var orderResponse = new OrderStatusResponse { Solver = profile, Order = EnrichOrderWithCaip2Id(orderJson.Value, slugToCaip2Id) }; var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var json = JsonSerializer.Serialize(orderResponse, jsonOptions); await SseWriter.WriteEventRawAsync(context.Response, "order", json, ct); @@ -227,4 +231,32 @@ private static bool IsTerminalEventType(string eventType, JsonElement data) return statusProp.GetString(); return null; } + + private static IReadOnlyDictionary BuildSlugToCaip2Id(StationConfig config) + { + return config.Networks + .Where(n => !string.IsNullOrEmpty(n.Slug)) + .ToDictionary(n => n.Slug, n => n.Caip2Id, StringComparer.OrdinalIgnoreCase); + } + + private static JsonElement EnrichOrderWithCaip2Id( + JsonElement orderJson, + IReadOnlyDictionary slugToCaip2Id) + { + var node = JsonNode.Parse(orderJson.GetRawText()); + if (node is JsonObject order && order["transactions"] is JsonArray transactions) + { + foreach (var txNode in transactions) + { + if (txNode is JsonObject tx) + { + var network = tx["network"]?.GetValue(); + if (network != null && slugToCaip2Id.TryGetValue(network, out var caip2Id)) + tx["caip2Id"] = caip2Id; + } + } + } + + return JsonSerializer.Deserialize(node?.ToJsonString() ?? orderJson.GetRawText()); + } } diff --git a/csharp/src/StationAPI/station-config.json b/csharp/src/StationAPI/station-config.json index 441a38c8..2653c451 100644 --- a/csharp/src/StationAPI/station-config.json +++ b/csharp/src/StationAPI/station-config.json @@ -62,6 +62,57 @@ "logoUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png" } ] + }, + { + "caip2Id": "starknet:SN_SEPOLIA", + "displayName": "Starknet Sepolia", + "chainId": "SN_SEPOLIA", + "networkType": "starknet", + "logoUrl": "https://raw.githubusercontent.com/TrainProtocol/icons/main/networks/starknet.png", + "nativeTokenAddress": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "tokens": [ + { + "symbol": "ETH", + "contract": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "decimals": 18, + "priceSymbol": "ETHUSDT", + "logoUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png" + } + ] + }, + { + "caip2Id": "aztec:devnet", + "displayName": "Aztec Devnet", + "chainId": "devnet", + "networkType": "aztec", + "logoUrl": "https://raw.githubusercontent.com/TrainProtocol/icons/main/networks/aztec.png", + "nativeTokenAddress": "0x02c31306cad429e0a00d3a4ee8ba251853099f835101ee2c637e9b3b9351a056", + "tokens": [ + { + "symbol": "ETH", + "contract": "0x02c31306cad429e0a00d3a4ee8ba251853099f835101ee2c637e9b3b9351a056", + "decimals": 18, + "priceSymbol": "ETHUSDT", + "logoUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png" + } + ] + }, + { + "caip2Id": "solana:devnet", + "displayName": "Solana Devnet", + "chainId": "devnet", + "networkType": "solana", + "logoUrl": "https://raw.githubusercontent.com/TrainProtocol/icons/main/networks/solana.png", + "nativeTokenAddress": "11111111111111111111111111111111", + "tokens": [ + { + "symbol": "SOL", + "contract": "11111111111111111111111111111111", + "decimals": 9, + "priceSymbol": "SOLUSDT", + "logoUrl": "https://raw.githubusercontent.com/TrainProtocol/icons/main/networks/solana.png" + } + ] } ], "solvers": [ From d3bfff248b55afcb57058e4e37da4eff4363fc06 Mon Sep 17 00:00:00 2001 From: Aren Date: Thu, 12 Mar 2026 20:06:49 +0400 Subject: [PATCH 2/2] Refactor OrderEndpoints to enrich transaction networks with caip2Id values. Removed unused slug property from NetworkConfig. Updated order response handling to utilize enriched transaction data. --- .../StationAPI/Configuration/StationConfig.cs | 1 - .../StationAPI/Endpoints/OrderEndpoints.cs | 66 ++++++++++++------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/csharp/src/StationAPI/Configuration/StationConfig.cs b/csharp/src/StationAPI/Configuration/StationConfig.cs index 4ce3f2d0..96349fbe 100644 --- a/csharp/src/StationAPI/Configuration/StationConfig.cs +++ b/csharp/src/StationAPI/Configuration/StationConfig.cs @@ -11,7 +11,6 @@ public class StationConfig public class NetworkConfig { public string Caip2Id { get; set; } = null!; - public string Slug { get; set; } = null!; public string DisplayName { get; set; } = null!; public string ChainId { get; set; } = null!; public string NetworkType { get; set; } = null!; diff --git a/csharp/src/StationAPI/Endpoints/OrderEndpoints.cs b/csharp/src/StationAPI/Endpoints/OrderEndpoints.cs index 36aada1c..bc5a92ba 100644 --- a/csharp/src/StationAPI/Endpoints/OrderEndpoints.cs +++ b/csharp/src/StationAPI/Endpoints/OrderEndpoints.cs @@ -60,7 +60,8 @@ private static async Task GetOrderAsync( }); } - var slugToCaip2Id = BuildSlugToCaip2Id(config); + var enrichedOrder = EnrichTransactionNetworks(orderJson.Value); + var response = new OrderStatusResponse { Solver = new SolverProfileResponse @@ -70,7 +71,7 @@ private static async Task GetOrderAsync( Description = solver.Description, LogoUrl = solver.LogoUrl }, - Order = EnrichOrderWithCaip2Id(orderJson.Value, slugToCaip2Id) + Order = enrichedOrder }; return Results.Ok(new ApiResponse { Data = response }); @@ -142,8 +143,6 @@ await SseWriter.WriteEventAsync(context.Response, "error", LogoUrl = solver.LogoUrl }; - var slugToCaip2Id = BuildSlugToCaip2Id(config); - // Subscribe to webhook-pushed events var reader = hub.Subscribe(hashlock); @@ -153,7 +152,8 @@ await SseWriter.WriteEventAsync(context.Response, "error", var orderJson = await solverClient.GetOrderAsync(solverId, hashlock, ct); if (orderJson is not null) { - var orderResponse = new OrderStatusResponse { Solver = profile, Order = EnrichOrderWithCaip2Id(orderJson.Value, slugToCaip2Id) }; + var enrichedOrder = EnrichTransactionNetworks(orderJson.Value); + var orderResponse = new OrderStatusResponse { Solver = profile, Order = enrichedOrder }; var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var json = JsonSerializer.Serialize(orderResponse, jsonOptions); await SseWriter.WriteEventRawAsync(context.Response, "order", json, ct); @@ -232,31 +232,47 @@ private static bool IsTerminalEventType(string eventType, JsonElement data) return null; } - private static IReadOnlyDictionary BuildSlugToCaip2Id(StationConfig config) + /// + /// Replaces transaction network slugs with caip2Id values derived from + /// the order's source/destination network fields (which are already caip2Id). + /// + private static JsonElement? EnrichTransactionNetworks(JsonElement orderElement) { - return config.Networks - .Where(n => !string.IsNullOrEmpty(n.Slug)) - .ToDictionary(n => n.Slug, n => n.Caip2Id, StringComparer.OrdinalIgnoreCase); - } + var node = JsonNode.Parse(orderElement.GetRawText()); + if (node is not JsonObject order) return orderElement; - private static JsonElement EnrichOrderWithCaip2Id( - JsonElement orderJson, - IReadOnlyDictionary slugToCaip2Id) - { - var node = JsonNode.Parse(orderJson.GetRawText()); - if (node is JsonObject order && order["transactions"] is JsonArray transactions) + var sourceCaip2Id = order["source"]?["network"]?.GetValue(); + var destCaip2Id = order["destination"]?["network"]?.GetValue(); + + if (order["transactions"] is not JsonArray transactions) + return orderElement; + + // Build slug → caip2Id map using unambiguous transaction types + var slugToCaip2Id = new Dictionary(); + foreach (var tx in transactions) { - foreach (var txNode in transactions) + if (tx is not JsonObject txObj) continue; + var type = txObj["type"]?.GetValue(); + var slug = txObj["network"]?.GetValue(); + if (slug is null) continue; + + if (type is "UserHTLCLock" or "UserHTLCRefund" && sourceCaip2Id is not null) + slugToCaip2Id.TryAdd(slug, sourceCaip2Id); + else if (type is "HTLCLock" or "HTLCRefund" or "CloseSolverLock" && destCaip2Id is not null) + slugToCaip2Id.TryAdd(slug, destCaip2Id); + } + + // Replace network slugs with caip2Id + foreach (var tx in transactions) + { + if (tx is not JsonObject txObj) continue; + var slug = txObj["network"]?.GetValue(); + if (slug is not null && slugToCaip2Id.TryGetValue(slug, out var caip2Id)) { - if (txNode is JsonObject tx) - { - var network = tx["network"]?.GetValue(); - if (network != null && slugToCaip2Id.TryGetValue(network, out var caip2Id)) - tx["caip2Id"] = caip2Id; - } + txObj["network"] = caip2Id; } } - return JsonSerializer.Deserialize(node?.ToJsonString() ?? orderJson.GetRawText()); + return JsonSerializer.Deserialize(node.ToJsonString()); } -} +} \ No newline at end of file