C# is closing in on Java. The TIOBE Index shows C# at 7.39% and climbing (+2.94 YoY), narrowing the gap to just 1.32 points. Stack Overflow's 2025 Developer Survey puts C# at 27.8% usage among all respondents and 29.9% among professionals, while ASP.NET Core holds 21.3% among professional developers. The overlap with finance is significant: over 40% of large enterprises run mission-critical ASP.NET applications, and ASP.NET adoption is strongest in finance, healthcare, and regulated industries. With the global blockchain market surpassing $100 billion in 2026 and 81% of the largest businesses deploying blockchain, the demand for on-chain integrations in .NET backends is real.
This guide shows you how to add token swap functionality to a C# application using a free REST API that requires no SDK, no API key, and no account. You send an HttpClient GET request, receive executable calldata, and broadcast it to the blockchain. Every code sample uses System.Text.Json and targets .NET 8+.
What You'll Need
- .NET 8 SDK or later (download)
- ASP.NET Core minimal API or controller project
- System.Text.Json (included with .NET) for deserialization
- A wallet address for the
senderparameter (the API returns calldata scoped to this address) - No API key -- the swap API is free and supports 46 EVM chains
Create a new project if you don't have one:
dotnet new webapi -n SwapApi --use-minimal-apis
cd SwapApi
Step 1: Define Response Record Types
The swap API returns a JSON envelope with success, data, and timestamp fields. Map these to C# records for immutable, type-safe deserialization. The API serializes BigInt values as strings to avoid precision loss, so AmountIn, ExpectedAmountOut, and MinAmountOut are all string properties.
using System.Text.Json.Serialization;
public record SwapResponse(
[property: JsonPropertyName("success")] bool Success,
[property: JsonPropertyName("data")] SwapData? Data,
[property: JsonPropertyName("error")] SwapError? Error,
[property: JsonPropertyName("timestamp")] string Timestamp
);
public record SwapData(
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("tokenFrom")] TokenInfo? TokenFrom,
[property: JsonPropertyName("tokenTo")] TokenInfo? TokenTo,
[property: JsonPropertyName("swapPrice")] double? SwapPrice,
[property: JsonPropertyName("priceImpact")] double? PriceImpact,
[property: JsonPropertyName("amountIn")] string? AmountIn,
[property: JsonPropertyName("expectedAmountOut")] string? ExpectedAmountOut,
[property: JsonPropertyName("minAmountOut")] string? MinAmountOut,
[property: JsonPropertyName("tokens")] List<TokenInfo>? Tokens,
[property: JsonPropertyName("tx")] SwapTransaction? Tx,
[property: JsonPropertyName("rpcUrl")] string? RpcUrl,
[property: JsonPropertyName("rpcUrls")] List<string>? RpcUrls
);
public record TokenInfo(
[property: JsonPropertyName("address")] string Address,
[property: JsonPropertyName("symbol")] string Symbol,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("decimals")] int Decimals
);
public record SwapTransaction(
[property: JsonPropertyName("from")] string From,
[property: JsonPropertyName("to")] string To,
[property: JsonPropertyName("data")] string Data,
[property: JsonPropertyName("value")] string Value,
[property: JsonPropertyName("gasPrice")] long GasPrice
);
public record SwapError(
[property: JsonPropertyName("code")] string Code,
[property: JsonPropertyName("message")] string Message
);
Every field from the API maps to a strongly-typed property. Nullable types handle the fact that NoRoute responses omit most fields, and Partial responses adjust AmountIn and ExpectedAmountOut to reflect the partial fill.
Step 2: Build the SwapService with HttpClient
Use IHttpClientFactory for proper connection pooling and lifetime management. The service constructs the query string, sends the request, and returns a typed result. With over 7 million .NET developers building production systems, HttpClient best practices matter -- never instantiate HttpClient directly in hot paths.
using System.Text.Json;
public class SwapService
{
private readonly HttpClient _httpClient;
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true
};
public SwapService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.swapapi.dev");
_httpClient.Timeout = TimeSpan.FromSeconds(15);
}
public async Task<SwapResponse> GetSwapAsync(
int chainId,
string tokenIn,
string tokenOut,
string amount,
string sender,
double maxSlippage = 0.005)
{
var url = $"/v1/swap/{chainId}"
+ $"?tokenIn={tokenIn}"
+ $"&tokenOut={tokenOut}"
+ $"&amount={amount}"
+ $"&sender={sender}"
+ $"&maxSlippage={maxSlippage}";
var response = await _httpClient.GetAsync(url);
var json = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
var errorResult = JsonSerializer.Deserialize<SwapResponse>(
json, JsonOptions);
return errorResult
?? throw new HttpRequestException(
$"API returned {response.StatusCode}: {json}");
}
return JsonSerializer.Deserialize<SwapResponse>(json, JsonOptions)
?? throw new JsonException("Failed to deserialize swap response");
}
}
Register the service in Program.cs using the typed client pattern:
builder.Services.AddHttpClient<SwapService>();
This gives you automatic connection pooling, DNS rotation, and testability through DI -- standard patterns in the 29.9% of professional developers who ship C# to production.
Step 3: Handle All Response Statuses
The API returns three distinct statuses, all with HTTP 200. Your code must check data.status to distinguish them. This is where most integrations fail: treating every 200 as a success without inspecting the status field.
public static class SwapResultHandler
{
public static string ProcessSwapResult(SwapResponse response)
{
if (!response.Success)
{
return $"Error: [{response.Error?.Code}] {response.Error?.Message}";
}
return response.Data?.Status switch
{
"Successful" => FormatSuccessful(response.Data),
"Partial" => FormatPartial(response.Data),
"NoRoute" => "No swap route found. Try a different pair or chain.",
_ => $"Unknown status: {response.Data?.Status}"
};
}
private static string FormatSuccessful(SwapData data)
{
var amountOut = FormatTokenAmount(
data.ExpectedAmountOut!, data.TokenTo!.Decimals);
var minOut = FormatTokenAmount(
data.MinAmountOut!, data.TokenTo!.Decimals);
return $"Swap: {data.TokenFrom!.Symbol} -> {data.TokenTo.Symbol}\n"
+ $"Expected output: {amountOut} {data.TokenTo.Symbol}\n"
+ $"Minimum output: {minOut} {data.TokenTo.Symbol}\n"
+ $"Price impact: {data.PriceImpact:P2}\n"
+ $"Router: {data.Tx!.To}";
}
private static string FormatPartial(SwapData data)
{
return $"Partial fill available.\n"
+ FormatSuccessful(data)
+ "\nOnly part of the requested amount can be filled.";
}
public static decimal FormatTokenAmount(string rawAmount, int decimals)
{
var value = decimal.Parse(rawAmount);
return value / (decimal)Math.Pow(10, decimals);
}
}
Three statuses to handle:
| Status | Meaning |
tx present |
Action |
|---|---|---|---|
Successful |
Full route found | Yes | Execute the transaction |
Partial |
Liquidity only covers part of the amount | Yes | Execute partial or adjust amount |
NoRoute |
No route exists for this pair | No | Try different pair, amount, or chain |
Step 4: Add a Price Impact Safety Check
On-chain swaps can suffer significant slippage on low-liquidity pairs. The API returns a priceImpact field (negative means unfavorable). In enterprise systems -- where financial applications handle millions of daily transactions -- you need guardrails.
public class SwapValidator
{
private const double MaxAcceptableImpact = -0.05; // -5%
public static bool IsSafeToExecute(SwapData data)
{
if (data.Status != "Successful" && data.Status != "Partial")
return false;
if (data.PriceImpact.HasValue && data.PriceImpact < MaxAcceptableImpact)
return false;
if (data.Tx == null)
return false;
return true;
}
public static string GetRejectionReason(SwapData data)
{
if (data.PriceImpact.HasValue && data.PriceImpact < MaxAcceptableImpact)
return $"Price impact {data.PriceImpact:P2} exceeds threshold";
if (data.Tx == null)
return "No transaction data available";
return "Unknown";
}
}
Step 5: Wire Up the Minimal API Endpoint
Expose a swap quote endpoint using ASP.NET Core minimal APIs. This gives your frontend or other services a clean internal API that wraps the external call with validation and error handling.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient<SwapService>();
var app = builder.Build();
app.MapGet("/api/swap", async (
int chainId,
string tokenIn,
string tokenOut,
string amount,
string sender,
double maxSlippage,
SwapService swapService) =>
{
try
{
var result = await swapService.GetSwapAsync(
chainId, tokenIn, tokenOut, amount, sender, maxSlippage);
if (!result.Success)
{
return Results.BadRequest(new
{
error = result.Error?.Code,
message = result.Error?.Message
});
}
if (result.Data?.Status == "NoRoute")
{
return Results.Ok(new
{
status = "NoRoute",
message = "No swap route found for this pair"
});
}
if (!SwapValidator.IsSafeToExecute(result.Data!))
{
return Results.UnprocessableEntity(new
{
status = "Rejected",
reason = SwapValidator.GetRejectionReason(result.Data!)
});
}
return Results.Ok(new
{
status = result.Data!.Status,
tokenFrom = result.Data.TokenFrom?.Symbol,
tokenTo = result.Data.TokenTo?.Symbol,
expectedOutput = SwapResultHandler.FormatTokenAmount(
result.Data.ExpectedAmountOut!,
result.Data.TokenTo!.Decimals),
minOutput = SwapResultHandler.FormatTokenAmount(
result.Data.MinAmountOut!,
result.Data.TokenTo!.Decimals),
priceImpact = result.Data.PriceImpact,
tx = result.Data.Tx
});
}
catch (HttpRequestException ex)
{
return Results.StatusCode(502);
}
catch (JsonException)
{
return Results.StatusCode(500);
}
});
app.Run();
Step 6: Add Retry Logic for Upstream Errors
The swap API may return UPSTREAM_ERROR (502) during transient routing failures or RATE_LIMITED (429) when exceeding approximately 30 requests per minute. Use Polly or the built-in Microsoft.Extensions.Http.Resilience package in .NET 8 for production-grade retry policies.
using Microsoft.Extensions.Http.Resilience;
builder.Services.AddHttpClient<SwapService>()
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 3;
options.Retry.Delay = TimeSpan.FromSeconds(2);
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(15);
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(45);
});
This handles transient 502 errors with exponential backoff. For 429 rate limits, the built-in handler respects Retry-After headers automatically.
Step 7: Test with a Real Swap Quote
Call the Ethereum endpoint to swap 1 ETH to USDC. Use the native token address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for ETH and the USDC contract 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48. The amount is 1 ETH in wei: 1000000000000000000 (18 decimals).
curl "http://localhost:5000/api/swap?chainId=1&tokenIn=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE&tokenOut=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&amount=1000000000000000000&sender=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&maxSlippage=0.005"
For Arbitrum, use chain ID 42161 with the Arbitrum token addresses. WETH is 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1 and USDC is 0xaf88d065e77c8cC2239327C5EDb3A432268e5831.
Supported Chains Quick Reference
The API supports 46 EVM chains. Here are the most used networks with key token addresses:
| Chain | ID | Native | USDC | USDT |
|---|---|---|---|---|
| Ethereum | 1 | ETH |
0xA0b8...eB48 (6 dec) |
0xdAC1...1ec7 (6 dec) |
| Arbitrum | 42161 | ETH |
0xaf88...5831 (6 dec) |
0xFd08...cbb9 (6 dec) |
| Base | 8453 | ETH |
0x8335...2913 (6 dec) |
0xfde4...d2b2 (6 dec) |
| Polygon | 137 | POL |
0x3c49...8359 (6 dec) |
0xc213...e8F (6 dec) |
| BSC | 56 | BNB |
0x8AC7...580d (18 dec)
|
0x55d3...7955 (18 dec)
|
| Optimism | 10 | ETH |
0x0b2C...Ff85 (6 dec) |
0x94b0...e58 (6 dec) |
BSC decimal warning: USDC and USDT use 18 decimals on BSC, not 6. Passing 100000000 (6 decimals) when you mean 100 USDT on BSC gives you 0.0000000001 USDT. The correct amount for 100 USDT on BSC is 100000000000000000000 (18 decimals).
FAQ
What is a token swap API and how does it work with C#?
A token swap API is a REST endpoint that returns executable blockchain transaction data for exchanging one token for another through decentralized exchanges. In C#, you call the API with HttpClient, deserialize the JSON response into record types, and submit the returned tx object to the blockchain using a signing library like Nethereum.
Does the API require authentication or an SDK?
No. The swap API at api.swapapi.dev requires no API key, no account registration, and no proprietary SDK. You use standard HttpClient and System.Text.Json -- nothing beyond what ships with .NET 8.
How do I handle token decimal differences across chains?
Always check the decimals field in the API response for each token. The same stablecoin can have different decimals on different chains (USDT is 6 decimals on Ethereum but 18 on BSC). The expectedAmountOut is always in raw units, so divide by 10^decimals to get the human-readable value.
What is the rate limit for the swap API?
The API allows approximately 30 requests per minute per IP address. If you exceed this, the API returns a 429 status with a RATE_LIMITED error code. Use the retry pattern from Step 6 with exponential backoff.
Can I use this token swap API in C# for multiple chains?
Yes. Pass any supported chainId as a path parameter. The API supports 46 EVM chains including Ethereum, Arbitrum, Base, Polygon, BSC, and Optimism. The response format is identical across all chains.
Get Started
swapapi.dev provides a free token swap API with no API key, no account, and 46 supported EVM chains. Browse the OpenAPI spec for the full schema, or hit the Swagger UI to test requests in the browser. The complete endpoint reference, token addresses, and integration guides are at swapapi.dev/llms.txt.
Top comments (0)