| name | uniswap-swap |
| description | Swap tokens on Uniswap V2/V3/V4 using Nethereum (.NET/C#). Use this skill whenever the user asks about token swaps, DEX trading, Uniswap integration, quoting prices, slippage protection, price impact, Universal Router, Permit2, or any DeFi swap operation with C# or .NET. |
| user-invocable | true |
Uniswap: Swap Tokens
Nethereum provides a complete Uniswap integration covering V2, V3, and V4. The web3.UniswapV4() extension gives you access to quoting, routing, pool discovery, and swap execution — all through typed C# services. This is the standard way to execute on-chain token swaps from .NET.
NuGet: Nethereum.Uniswap
dotnet add package Nethereum.Uniswap
Connect to Uniswap V4
The UniswapV4() extension on Web3 exposes the full service hierarchy. It defaults to Mainnet addresses but accepts any network's addresses:
var web3 = new Web3(new Account(privateKey), rpcUrl);
var uniswap = web3.UniswapV4();
var uniswap = web3.UniswapV4(UniswapV4Addresses.BaseSepolia);
The uniswap object provides: Pools (state, cache, discovery), Pricing (quoter, price calculator, slippage, price impact), Positions (liquidity management), UniversalRouter (swap execution), and Math (tick math).
Quote a Price
Before swapping, simulate the trade with the quoter — this is a read-only call, no gas cost:
var pathKeys = V4PathEncoder.EncodeMultihopExactInPath(new List<PoolKey> { pool }, eth);
var amountIn = Web3.Convert.ToWei(0.001);
var quoteParams = new QuoteExactParams
{
Path = pathKeys,
ExactAmount = amountIn,
ExactCurrency = eth
};
var quote = await uniswap.Pricing.Quoter.QuoteExactInputQueryAsync(quoteParams);
var quoteAmount = Web3.Convert.FromWei(quote.AmountOut, 6);
The V4PathEncoder encodes the swap route. For single-hop, pass one pool key. For multi-hop (e.g., ETH→DAI→USDC), pass multiple pool keys.
Execute a Swap
Swaps go through the Universal Router. The pattern is: build actions (swap + settle input + take output), then execute:
var v4ActionBuilder = uniswap.GetUniversalRouterV4ActionsBuilder();
v4ActionBuilder.AddCommand(new SwapExactIn
{
CurrencyIn = eth,
AmountIn = amountIn,
AmountOutMinimum = quote.AmountOut * 95 / 100,
Path = pathKeys.MapToActionV4()
});
v4ActionBuilder.AddCommand(new SettleAll { Currency = eth, Amount = amountIn });
v4ActionBuilder.AddCommand(new TakeAll { Currency = usdc, MinAmount = 0 });
var routerBuilder = new UniversalRouterBuilder();
routerBuilder.AddCommand(v4ActionBuilder.GetV4SwapCommand());
var executeFunction = routerBuilder.GetExecuteFunction(amountIn);
var receipt = await uniswap.UniversalRouter.ExecuteRequestAndWaitForReceiptAsync(executeFunction);
The three actions form the standard swap pattern: swap exchanges tokens in the pool, settle pays the input, take collects the output. AmountOutMinimum is your slippage protection.
Slippage and Price Impact
Nethereum provides calculators for both:
var slippage = uniswap.Pricing.SlippageCalculator;
var tolerance = new BigDecimal(0.5m);
var minAmountOut = slippage.CalculateMinimumAmountOut(expectedAmountOut, tolerance);
var maxAmountIn = slippage.CalculateMaximumAmountIn(expectedAmountIn, tolerance);
var impactCalc = uniswap.Pricing.PriceImpactCalculator;
var impact = impactCalc.CalculatePriceImpact(input, output, midPrice);
var level = impactCalc.ClassifyPriceImpact(impact);
var warning = impactCalc.GetPriceImpactWarning(level);
Price Calculations
Calculate human-readable prices from the pool's raw sqrtPriceX96 value:
var priceToken0InToken1 = uniswap.Pricing.PriceCalculator.CalculatePriceFromSqrtPriceX96(
sqrtPriceX96, decimals0: 18, decimals1: 6);
var priceToken1InToken0 = priceToken0InToken1 == 0 ? 0 : 1 / priceToken0InToken1;
Tick math conversions for working with price ranges:
var sqrtPriceX96 = uniswap.Math.Tick.GetSqrtRatioAtTick(tick);
var tick = uniswap.Math.Tick.GetTickAtSqrtRatio(sqrtPriceX96);
Pool Discovery
Find available pools for a token pair using on-chain Initialize events:
var poolCache = uniswap.Pools.Cache;
var pools = await poolCache.FindPoolsForTokenAsync(token: usdc, fromBlockNumber: start, toBlockNumber: end);
Or fetch a specific pool directly:
var pool = await poolCache.GetOrFetchPoolAsync(currency0: eth, currency1: usdc, fee: 500, tickSpacing: 10);
Find Best Path
For large trades or exotic pairs, route through intermediate tokens for better prices:
var pathFinder = uniswap.Pricing.QuotePricePathFinder;
var cachedPools = await poolCache.GetAllCachedPoolsAsync();
var bestDirect = await pathFinder.FindBestDirectPathAsync(
tokenIn: eth, tokenOut: usdc,
amountIn: Web3.Convert.ToWei(1),
candidatePools: cachedPools);
var bestPath = await pathFinder.FindBestPathAsync(
tokenIn: usdc, tokenOut: weth,
amountIn: Web3.Convert.ToWei(1000, UnitConversion.EthUnit.Mwei),
intermediateTokens: new[] { dai, eth },
candidatePools: cachedPools);
Token Approvals
Before swapping ERC-20 tokens (not native ETH), ensure the router has approval:
await uniswap.Accounts.Approvals.CheckAndApproveIfNeededAsync(
tokenAddress, owner, spender, requiredAmount);
var status = await uniswap.Accounts.Approvals.CheckApprovalAsync(
tokenAddress, owner, spender, requiredAmount);
if (!status.IsApproved)
{
await uniswap.Accounts.Approvals.ApproveAsync(
tokenAddress, spender, AccountApprovalService.GetMaxApprovalAmount());
}
Validate Balances
Before executing a swap, verify the sender has enough tokens:
var balanceResult = await uniswap.Accounts.Balances.ValidateBalanceAsync(
tokenAddress, owner, requiredAmount);
if (!balanceResult.HasSufficientBalance)
Console.WriteLine($"Need {balanceResult.Deficit} more tokens");
V3 Swaps with Permit2
For Uniswap V3, swaps use the same Universal Router but with Permit2 for gasless approvals. The flow: approve Permit2 once, then sign off-chain permits per swap:
var weth9Service = web3.Eth.ERC20.GetContractService(weth9);
await weth9Service.ApproveRequestAndWaitForReceiptAsync(permit2Address, IntType.MAX_INT256_VALUE);
var permit = new PermitSingle
{
Spender = universalRouterAddress,
SigDeadline = 2000000000,
Details = new PermitDetails
{
Amount = amountIn * 100000,
Expiration = 0,
Nonce = 0,
Token = weth9
}
};
var permitService = new Permit2Service(web3, permit2Address);
var signedPermit = await permitService.GetSinglePermitWithSignatureAsync(
permit, new EthECKey(privateKey));
var planner = new UniversalRouterBuilder();
planner.AddCommand(new Permit2PermitCommand
{
Permit = signedPermit.PermitRequest,
Signature = signedPermit.GetSignatureBytes()
});
planner.AddCommand(new V3SwapExactInCommand
{
AmountIn = amountIn,
AmountOutMinimum = quote.AmountOut - 10000,
Path = path,
Recipient = account.Address,
FundsFromPermit2OrUniversalRouter = true
});
var receipt = await universalRouterService.ExecuteRequestAndWaitForReceiptAsync(
planner.GetExecuteFunction(amountIn));
Error Handling
Uniswap reverts with custom errors that the router service can decode:
catch (SmartContractCustomErrorRevertException e)
{
var error = universalRouterService.FindCustomErrorException(e);
if (error != null) Console.WriteLine($"Uniswap error: {error.Message}");
}
For full documentation, see: https://docs.nethereum.com/docs/defi/guide-uniswap-swap