Building a Uniswap Bot: Complete Code Guide to Automated Trading and Arbitrage

June 2, 2026

A developer watching Uniswap’s liquidity pools recognizes an opportunity: price discrepancies between Ethereum, Arbitrum, Optimism, and Base create profitable windows that close in seconds. Manual execution is impossible. Monitoring gas costs, slippage, and execution timing requires automation. A Uniswap bot, written in JavaScript or TypeScript using Web3.js or Ethers.js, can watch pools in real time, calculate profit margins across networks, and execute transactions when conditions align—all without requiring permission from a centralized exchange or even an intermediary.

Building such a bot means understanding how the Uniswap protocol works at the contract level, how to read pool state, how to construct and simulate transactions before broadcasting them, and how to manage gas costs so that profits exceed the fees paid to the network. The protocol’s immutable smart contracts operate on a simple formula—the constant product rule x*y=k—but exploiting that model requires precision in code, discipline in risk management, and honest accounting of when a “profitable” trade actually destroys capital.

Uniswap protocol architecture showing AMM pools, token swaps, and multi-network deployment across Ethereum, Arbitrum, Optimism, Base, and Polygon

Understanding Uniswap’s automated market maker mechanics

Uniswap operates as a permissionless liquidity protocol where anyone can deposit token pairs into a smart contract pool and earn fees on every trade that executes against that pool. The protocol does not require KYC, account creation, or approval from any centralized entity. Instead, it uses the constant product formula: if a pool holds 1,000 units of token A and 10,000 units of token B, then x*y=k means that 1,000 × 10,000 = 10,000,000 is the invariant. When a trader swaps 100 units of token A into the pool, they receive token B in an amount that keeps the product constant. Specifically, the pool must contain enough of token B so that (1,100) × (remaining B) = 10,000,000. Solving for remaining B gives approximately 9,090.91, so the trader receives about 909.09 units of token B, with the difference (909.09 units) representing the fee paid to liquidity providers and any slippage.

The version 3 upgrade introduced concentrated liquidity, allowing liquidity providers to specify price ranges where their capital is active. A provider can deposit funds that only function between $1.00 and $1.10 per token, concentrating their capital and earning higher fees in that range but earning nothing if the price moves outside it. This change increased capital efficiency but also fragmented liquidity across many discrete price ranges. For a bot, concentrated liquidity means that the same pool may have wildly different available liquidity at different price points, which directly affects slippage and whether a large swap is even possible at a given price tier.

Uniswap V3 also introduced multiple fee tiers: 0.05%, 0.30%, and 1.00% on most pairs, with a 0.01% tier available for stablecoin pairs. The fee tier is embedded in the pool address itself; swapping USDC/ETH at 0.05% and USDC/ETH at 0.30% are two separate contracts with potentially different liquidity depths and price curves. A bot scanning for arbitrage opportunities must check all fee tiers, because a profitable spread may exist on one fee pair but not another, or a better opportunity might exist at a higher-fee tier if the liquidity there is deep enough to absorb a large order.

The final detail that affects bot strategy is the time-weighted average price (TWAP) oracle built into the protocol. Uniswap records the cumulative product of prices at each block, allowing downstream contracts and off-chain bots to calculate average prices over any historical period. A bot can query the TWAP to detect when a pool has deviated significantly from its historical average, which may signal a trade opportunity or a sudden liquidity event worth investigating before committing capital.

Setting up Web3.js and Ethers.js for real-time monitoring

Any Uniswap bot begins with a connection to one or more blockchain networks and the ability to read contract state without incurring transaction costs. Web3.js and Ethers.js both provide this functionality, though Ethers.js has become the standard choice for new projects due to its lighter bundle size, cleaner API, and better TypeScript support. The first step is establishing a connection to a node provider—either a public endpoint like Alchemy, Infura, or a private node if you run one locally.

The contract ABIs (Application Binary Interfaces) for Uniswap’s core contracts are publicly available in the Uniswap GitHub repositories. For V3, the key contracts are the Swap Router (handles token swaps), the Factory (tracks all pools), and the individual Pool contracts (hold the liquidity and pricing data). A bot typically queries the Pool contract to read reserve amounts, fee tier, current tick (representing the current price), and total liquidity. The format looks like this in Ethers.js:

“`javascript
const poolAbi = require(‘@uniswap/v3-core/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json’).abi;
const poolContract = new ethers.Contract(poolAddress, poolAbi, provider);
const slot0 = await poolContract.slot0();
const liquidity = await poolContract.liquidity();
const reserves = { sqrtPriceX96: slot0.sqrtPriceX96, tick: slot0.tick, liquidity };
“` The `slot0` object contains the current price (encoded as sqrtPriceX96 to avoid floating-point precision issues), the current tick, and the observation index. The `liquidity` value is the total active liquidity in the pool at the current price. Both values change whenever someone executes a swap, so a real-time bot must either poll the contract repeatedly or listen for events emitted when swaps occur.

Event listening is more efficient than polling. When a trade executes on Uniswap, the pool emits a `Swap` event containing the input token, output token, amount in, amount out, and new price. A bot can subscribe to these events in real time, parse them, and decide within milliseconds whether to execute a response trade. The following pattern establishes this listener:

“`javascript
poolContract.on(‘Swap’, async (sender, amount0, amount1, sqrtPriceX96, liquidity, tick, event) => {
console.log(`Swap detected: ${amount0} of token0 for ${amount1} of token1`);
const potentialArbitrage = calculateOpportunity(amount0, amount1, sqrtPriceX96);
if (potentialArbitrage.profitUSD > gasEstimateUSD) {
await executeArbitrageSwap(potentialArbitrage);
}
});
“` Filtering events by topic or address ensures the bot only processes relevant swaps rather than processing every event on the network, which would overwhelm most bots instantly.

Calculating prices, slippage, and arbitrage opportunities

The sqrtPriceX96 value returned from the pool is not immediately intuitive. It represents the square root of the price multiplied by 2^96, which was chosen to avoid floating-point arithmetic and maintain precision across the wide range of token decimals used in the blockchain ecosystem. To convert it back into a human-readable price, you must reverse the operation: divide by 2^96, square the result, and then account for the decimal difference between the two tokens. For a USDC/ETH pair where USDC has 6 decimals and ETH has 18 decimals, the formula becomes:

“`javascript
const sqrtPrice = sqrtPriceX96 / (2 ** 96);
const price = sqrtPrice ** 2;
const adjustedPrice = price * (10 ** (6 – 18)); // Adjust for decimals
“` With the current price known, a bot can now check whether the same token pair exists on another network or at a different fee tier and compare prices. If USDC/ETH trades at $1,800 on Ethereum’s 0.05% fee pool but only $1,795 on Optimism, a bot could theoretically buy ETH on Optimism for $1,795 and sell it on Ethereum for $1,800, capturing a $5 spread per ETH. However, this arbitrage is only profitable if the cost of moving capital between networks (bridge fees, slippage on the bridge, time delay) plus gas costs on both swaps is less than $5 per ETH.

Slippage is another critical factor. The constant product formula means that large swaps hit worse prices than small ones, because moving a large amount of one token into the pool changes the price for all subsequent units. A bot planning to swap 100 ETH cannot assume it will receive the price shown for a single unit; it will receive progressively worse prices as the pool’s reserves shift. Libraries like the DEX protocol‘s SDK provide a `route` class that simulates swaps and calculates the actual output including slippage. Alternatively, a bot can call the `quoteExactInputSingle` function on the Swap Router to get a quote:

“`javascript
const SwapRouter = require(‘@uniswap/swap-router-contracts/artifacts/SwapRouter02.json’).abi;
const routerContract = new ethers.Contract(routerAddress, SwapRouter, provider);
const quoteParams = {
tokenIn: tokenA,
tokenOut: tokenB,
fee: 3000, // 0.30% fee tier
amountIn: ethers.parseUnits(‘100’, 18),
sqrtPriceLimitX96: 0 // No price limit, accept any slippage
};
const quotedAmountOut = await routerContract.quoteExactInputSingle(quoteParams);
“` The bot receives the exact amount of tokenB it would receive for 100 tokenA, accounting for slippage and the fee. If that amount is lower than expected, the spread to another market may not be profitable after all.

Constructing and simulating transactions before broadcasting

Once a bot identifies a profitable opportunity, it must construct the transaction carefully. Broadcasting a transaction that fails after consuming gas is wasted capital. Instead, the bot should simulate the transaction locally using `eth_call` to confirm it will succeed before submitting it to the network. In Ethers.js, this involves calling a contract function without actually submitting it, which returns the result without spending gas:

“`javascript
const swapParams = {
tokenIn: tokenAAddress,
tokenOut: tokenBAddress,
fee: 3000,
recipient: botAddress,
deadline: Math.floor(Date.now() / 1000) + 60, // 60-second deadline
amountIn: ethers.parseUnits(’50’, 18),
amountOutMinimum: ethers.parseUnits(‘27.5’, 6), // Allow 1% slippage
sqrtPriceLimitX96: 0
};
const tx = await routerContract.exactInputSingle.populateTransaction(swapParams);
const gasEstimate = await provider.estimateGas(tx);
const gasPrice = await provider.getGasPrice();
const totalGasCost = gasEstimate * gasPrice;
console.log(`Estimated gas cost: ${ethers.formatUnits(totalGasCost, 18)} ETH`);
if (expectedProfit < ethers.formatUnits(totalGasCost, 18)) { console.log('Opportunity not profitable after gas; skipping.'); return; } const receipt = await botSigner.sendTransaction(tx); ``` The `amountOutMinimum` parameter sets a floor below which the swap will revert. Setting it to 99% of the quoted amount protects the bot from slippage that would wipe out the profit. The `deadline` parameter prevents the transaction from executing hours later if it gets stuck in the mempool, which would mean the price has likely moved and the arbitrage opportunity has closed.

Signed transactions are essential. A bot cannot call contract functions directly; it must sign transactions with its private key and submit them through the network. In Ethers.js, a Signer object handles this. The bot should never hardcode its private key; instead, it should load it from an environment variable or a secure key management system:

“`javascript
const botPrivateKey = process.env.BOT_PRIVATE_KEY;
const botSigner = new ethers.Wallet(botPrivateKey, provider);
const signedTx = await botSigner.sendTransaction(tx);
await signedTx.wait(); // Wait for confirmation
“` Once sent, the transaction enters the mempool where it competes with other pending transactions. The bot can set a custom gas price to prioritize execution, but higher gas prices increase costs and reduce net profit. A bot that consistently overpays for gas will not be profitable even if its price signals are correct.

Multi-network arbitrage and cross-chain strategies

Uniswap operates not only on Ethereum mainnet but also on Arbitrum, Optimism, Base, and Polygon. A sophisticated bot can monitor all networks simultaneously and execute arbitrage across them. However, moving capital between networks introduces a new bottleneck: bridges. Some bridges are fast (taking seconds to minutes), while others are slow (hours or days). The bot must account for bridge fees, which can be 0.05% to 0.50% depending on the bridge and direction. If the spread between networks is only 0.30%, bridge costs may exceed the profit.

One approach is to keep capital split across multiple networks. A bot might maintain 10 ETH on Ethereum, 10 ETH on Arbitrum, and 10 ETH on Optimism. When a price discrepancy emerges between Ethereum and Arbitrum, the bot swaps its Arbitrum capital, captures the local profit, and holds the result rather than bridging back immediately. Over time, the bot accumulates tokens in profitable locations and rebalances only when the cost of bridging is justified by larger spreads.

Another strategy is token swap arbitrage, also called statistical arbitrage. The bot monitors prices of token pairs across Uniswap’s different fee tiers and networks. If USDC/ETH at 0.05% fee shows a price of $1,800 but USDC/ETH at 0.30% fee shows $1,798, the bot can buy in the 0.30% pool (accepting the 0.30% fee) and sell in the 0.05% pool, capturing the spread minus the net fee difference (0.25%) and gas costs. This requires no bridge and no external liquidity; both sides of the trade execute on the same network.

A third strategy is MEV-resistant execution. When a large swap is about to occur on Uniswap, other bots can see the pending transaction in the mempool and front-run it by submitting their own swap first, capturing the favorable price before the large order executes. The large order then faces worse slippage. Some bots use private mempools (like Flashbots Protect) to hide their transactions and protect against front-running. A bot executing its own arbitrage should also consider private transaction pools to avoid becoming a victim of front-running itself.

Gas optimization and profitability thresholds

Gas is the largest controllable cost in a Uniswap bot’s operations. On Ethereum mainnet, a simple swap costs 80,000 to 150,000 gas depending on pool state and complexity. At 50 Gwei gas price, that is $4 to $7.50 per swap. A bot that executes 100 trades per day at an average cost of $5 per trade spends $500 daily just on gas, or $182,500 annually. The bot must capture at least $500 in profit daily from its trades to break even, and much more to justify the infrastructure, slippage, and risk.

Gas optimization means choosing between execution strategies. A single swap that uses `exactInputSingle` costs less than a multi-hop swap using `exactInput`, which chains multiple pools together. A bot should prefer direct pairs over multi-hop routes whenever possible. Batching multiple swaps into a single transaction using a smart contract (rather than executing them sequentially) can save gas by reusing state across swaps. However, writing and deploying a custom contract adds complexity and may introduce new security risks.

The profitability threshold is the minimum spread required to justify execution. If the bot’s estimated costs are $5 in gas plus $0.10 in slippage, the gross profit must exceed $5.10 to make the trade worthwhile. Including a safety margin (because estimates can be inaccurate), the bot should require at least $7 or $8 in gross profit before committing capital. On high-volume pairs with tight spreads, this threshold means many opportunities will be rejected as unprofitable. On low-liquidity or exotic pairs, spreads may be wider but liquidity may be insufficient to absorb the bot’s order size.

Dynamic gas pricing is essential. During periods of network congestion, gas prices spike. A bot that uses a fixed gas price will either underbid and never get included, or overpay and eliminate profit margins. Monitoring the pending transaction pool and adjusting gas prices in real time (or resigning and resubmitting transactions with higher gas) is standard practice among professional bots. Ethers.js provides `getFeeData()` to read current gas prices from the network, and wallets can submit multiple versions of the same transaction with increasing gas prices if the first version does not confirm.

Risk management, slippage protection, and transaction failures

A bot executing trades must handle failure gracefully. If a transaction reverts because conditions changed, the bot should log the reason and adjust its parameters rather than repeatedly broadcasting identical transactions. Common failure modes include insufficient liquidity (the pool ran out of tokens), price impact too high (slippage exceeded the `amountOutMinimum`), and deadline expired (the transaction sat in the mempool too long).

The bot should also protect itself from oracle manipulation. If the bot relies on a single price feed and that feed is temporarily distorted (by a large whale swap or a flash loan attack), the bot may execute trades at prices that are terrible. Reading prices from multiple sources—Uniswap’s TWAP, on-chain oracles from other protocols, and off-chain price feeds—and comparing them reduces this risk. If all sources disagree sharply, the bot should pause rather than trade.

Position sizing is another control. The bot should never commit all its capital to a single trade or market. If a trade goes wrong, the bot needs reserves to exit or rebalance. A common rule is to risk no more than 1% to 5% of total capital on a single trade. If a bot has $100,000 in capital, it might limit each trade to a position worth $5,000. This means many profitable opportunities will not use the full capital available, but it also means one bad trade cannot eliminate the entire fund.

Smart contract risks cannot be fully eliminated through bot design. Uniswap’s core contracts have been audited extensively and have been in production for years, so obvious bugs are rare. However, a custom bot that interacts with less-tested protocols, or newer tokens, faces greater risk. Testing on testnet (like Sepolia for Ethereum) before deploying on mainnet is essential. A bot should also have a kill switch—a way to pause trading instantly if something appears wrong—rather than running blindly until capital is lost.

Monitoring, logging, and adapting to market conditions

A production bot must log every trade, opportunity analyzed, transaction submitted, and result. Without good logging, the bot becomes a black box that either makes or loses money without clear explanation. Structured logs (using JSON) that include timestamp, opportunity ID, expected profit, actual profit, gas paid, and transaction hash enable post-mortem analysis and continuous improvement. If the bot consistently overestimates slippage, you can adjust parameters. If it misses certain types of opportunities, you can refine the detection logic.

Real-world markets change. Liquidity providers respond to incentives and may concentrate or withdraw capital. New pools launch with deeper liquidity, attracting volume away from existing pools. Gas prices fluctuate daily. A bot’s profitability assumptions may be accurate for one week but obsolete the next. A good bot includes mechanisms to adapt: recalibrating the gas price threshold weekly, monitoring liquidity depth in monitored pools and deprioritizing dry ones, and tracking realized profit over time to detect degradation.

Backtesting is valuable but limited. A bot can simulate historical price data and measure what profits it would have captured in the past. However, backtesting assumes the bot can execute instantly at any price, ignores transaction ordering and mempool competition, and does not account for the bot’s own market impact. A backtest showing $10,000 in daily profit does not guarantee $10,000 will be realized on mainnet. It is a sanity check, not a proof. Real capital reveals truth far more effectively than historical simulation.

Finally, a bot should be prepared to stop. If profitability degrades, if competition increases, or if the market structure changes in a way that eliminates opportunities, the bot should be retired rather than continue consuming capital and computing resources. The Uniswap protocol itself is immutable and will continue operating, but that does not mean every bot strategy remains viable forever. The best bot operators accept this and reallocate capital to new opportunities rather than doubling down on failing strategies.

Frequently asked questions

How much capital do I need to run a profitable Uniswap bot?

Capital requirements depend on the strategy. A bot catching large arbitrage opportunities may need only a few thousand dollars and execute a few profitable trades daily. A bot executing many small trades requires more capital to make the per-trade profit meaningful. Most viable bots require at least $10,000 to $50,000 to overcome gas costs and slippage while maintaining acceptable profit margins. Without sufficient capital, trading costs dominate and profitability is unlikely.

Can a bot be profitable on low-liquidity networks like Polygon?

Lower liquidity networks may offer wider spreads, making arbitrage easier to spot. However, deeper spreads also indicate higher slippage when executing large trades, which can eliminate theoretical profits. Additionally, low-liquidity networks have fewer opportunities overall, so the bot may trade infrequently. Gas is cheaper on Polygon than Ethereum, which helps, but the reduced volume typically offsets this advantage. Profitability depends on finding enough consistent opportunities to amortize operational costs.

What is the difference between a Uniswap bot and a MEV searcher?

A Uniswap bot monitors pools and executes swaps whenever conditions meet its profitability criteria. A MEV searcher identifies opportunities created by the mempool itself—detecting pending transactions from other users and inserting the bot’s trade ahead of them to capture profit. MEV searchers operate at higher latency requirements and face front-running competition from other searchers, but they can capture larger spreads when successful. A bot can employ MEV strategies, but not all bots do, and not all MEV strategies require Uniswap specifically.

Leave a Reply

Your email address will not be published. Required fields are marked *

2