Uniswap is a set of exchange protocols. Liquidity providers deposit tokens into pools. Traders use those pools to exchange tokens.
This guide explains the model. The JavaScript example runs locally. It does not connect to a wallet or send a transaction.
Pool reserves and fees
A basic v2 pool uses the constant-product model: x × y = k. Here, x and y are the two token reserves. The model describes the swap curve. In the real pool, fees stay in the reserves, so the product can increase.
The standard v2 swap fee is 0.3%. The quote uses 99.7% of the input amount. An input trade increases one reserve and reduces the other. Large trades have more price impact.
Price impact is the effect of your trade on the pool price. Slippage is a difference between a quote and the result at execution. A minimum output amount limits the result you will accept.
For the v2 equations, see the Uniswap v2 whitepaper.
Run a local quote
Save this example as quote.mjs. Run it with node quote.mjs on Node.js 20 or later. The inputs use raw token units. WETH has 18 decimals. USDC has 6 decimals.
The example pool has 1,000 WETH and 2,000,000 USDC. A 1 WETH input returns a quote of 1,992.013962 USDC. This value includes the v2 fee and integer rounding. It is an example reserve state, not a live market quote.
function quoteV2(amountIn, reserveIn, reserveOut) {
const values = [amountIn, reserveIn, reserveOut];
if (values.some((value) => typeof value !== "bigint" || value <= 0n)) {
throw new RangeError("Use positive bigint amounts and reserves");
}
const inputAfterFee = amountIn * 997n;
return (inputAfterFee * reserveOut) / (reserveIn * 1000n + inputAfterFee);
}
function minimumOutput(quotedOutput, slippageBps) {
if (
typeof quotedOutput !== "bigint" ||
quotedOutput <= 0n ||
typeof slippageBps !== "bigint" ||
slippageBps < 0n ||
slippageBps >= 10000n
) {
throw new RangeError("Use a positive quote and slippage below 10000 bps");
}
const minimum = (quotedOutput * (10000n - slippageBps)) / 10000n;
if (minimum === 0n) throw new RangeError("Minimum output must be positive");
return minimum;
}
function deadlineSeconds(nowMs = Date.now()) {
if (!Number.isSafeInteger(nowMs) || nowMs < 0) {
throw new RangeError("Use a non-negative timestamp in milliseconds");
}
return Math.floor(nowMs / 1000) + 1800;
}
const output = quoteV2(
1000000000000000000n,
1000000000000000000000n,
2000000000000n
);
console.log("Quote in raw USDC units:", output.toString());
console.log("Minimum at 50 bps:", minimumOutput(output, 50n).toString());
console.log("Deadline in seconds:", deadlineSeconds());Fifty basis points means 0.5%. This example applies that limit to the quote. Other SDKs can use different rounding or slippage conventions. Follow the convention of the router and SDK you select.
The example assumes standard tokens with no transfer tax or rebasing. Use a fresh on-chain quote for a real trade. The v2 equation cannot quote a v3 or v4 pool.
Protocol versions
- v1 (2018): ETH-to-token pools.
- v2 (2020): Token-to-token pools and flash swaps.
- v3 (2021): Liquidity positions with selected price ranges and pool fee tiers.
- v4 (2025): A singleton pool manager and hooks for custom pool behavior.
Uniswap v4 launched on 31 January 2025. See the launch announcement.
In v2, deposits normally follow the current reserve ratio. In v3, the amounts depend on the selected range and the current price. A position outside its range can hold only one token. Read Concentrated liquidity for the differences.
Build a v3 integration
Select a network first. Check token addresses, decimals, pool fees, and router deployments on that network.
On Ethereum mainnet, USDC uses 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 and 6 decimals. Other networks have other addresses. Confirm them in Circle's contract list.
A v3 swap flow has these steps:
- Read pool state and get a quote for the requested input.
- Construct a
Pool, aRoute, and aTradeusing the v3 SDK. - Set the recipient, slippage tolerance, and deadline.
- Use
SwapRouter.swapCallParametersto generate calldata and a value. - Check the allowance for the selected router. Approve only the amount required.
- Submit the transaction with the wallet signer. Wait for the receipt and check the result.
The SDK generates transaction parameters. It does not provide new SwapRouter(...).execute(...). Use the official v3 trading example, including its dependency versions and setup instructions.
For a liquidity position, use the SDK's NonfungiblePositionManager.addCallParameters and submit the resulting calldata to the deployed position manager. A Position object does not send a transaction through position.mint().
Keep provider APIs consistent. Ethers v6 uses BrowserProvider; ethers v5 examples use providers.Web3Provider. See the ethers migration guide.
Flash swap callbacks
A v2 flash swap lets a contract receive tokens before it pays the pair. The transaction must repay the required amount or satisfy the swap invariant. Otherwise, it reverts.
Validate the callback caller against the pair from the trusted factory. Validate the initiating sender when the strategy requires it. Token addresses supplied in callback data do not prove that the caller is a real pair.
For a same-token repayment with the standard fee, the minimum is rounded up from borrowed × 1000 / 997. Other repayment paths use the reserve equation. Do not use an undefined fee value or transfer tokens to an unchecked caller. Follow the official flash swap guide.
Oracles and liquidity limits
A v3 spot price can move within one transaction. Use a suitable observation window for a time-weighted price. Check that the pool has enough observation history and liquidity.
Ticks are signed. Negative tick averages need correct rounding. Converting sqrtPriceX96 by a direct square can overflow a 256-bit integer. Use the established oracle libraries and full-precision math. Account for token order and decimals. See the Uniswap oracle guide.
For a liquidity deposit, calculate both minimum token amounts from the current state and the accepted slippage. A zero minimum accepts any result for that token. Set a deadline in Unix seconds, not milliseconds.
Test the transaction flow
Use the selected integration's complete example and lock its dependency versions. Run a local fork before a funded transaction. Check quotes, allowances, expired deadlines, minimum output failures, callback validation, and the resulting balances.
Private transaction submission can reduce exposure to public transaction ordering. It does not guarantee that a trade has no MEV risk. Measure gas use before replacing ordinary contract code with assembly.
Arbitrage profit must include fees, gas, price impact, and execution failures. A price difference alone does not prove that a trade is profitable.