Decentralized exchanges processed $876 billion in spot volume in Q2 2025 alone, yet most of that volume still executes as market orders. Limit orders -- the ability to buy or sell at a specific price rather than the current one -- remain the most requested feature traders want from DEXs. The crypto trading bot market is now valued at over $47 billion, driven by demand for automated execution without centralized custody. If you have ever watched a token dump right after you manually swapped at the top, a limit order bot solves that exact problem.
This guide shows you how to build a limit order bot in Python that polls prices using swapapi.dev and executes swaps when your target price is reached. The API is free, requires no API key, supports 46 EVM chains, and returns executable calldata in a single GET request.
What You'll Need
- Python 3.10+
- web3.py for wallet management and transaction submission
- requests for HTTP calls to the swap API
- A wallet with a private key (use a dedicated bot wallet, never your main wallet)
- Native gas tokens on your target chain (ETH, BNB, etc.)
- No API keys -- swapapi.dev requires zero authentication
Install the dependencies:
pip install web3 requests
Step 1: Understand How On-Chain Limit Orders Work
Traditional centralized exchanges maintain an order book where your limit order sits until a counterparty matches it. On-chain, there is no persistent order book. Instead, a limit order bot continuously polls the current swap price and executes when the price meets your threshold. The DEX-to-CEX spot volume ratio doubled to 13.6% by January 2026, meaning more liquidity is available on-chain than ever before -- making programmatic limit orders increasingly viable.
The flow is simple:
- Define a target price (e.g., "buy WETH when it drops below 2,400 USDC")
- Poll the swap API at regular intervals to get the current price
- When the quoted price meets your condition, execute the swap
Try fetching a live price quote right now:
curl "https://api.swapapi.dev/v1/swap/1?tokenIn=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&tokenOut=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2&amount=2400000000&sender=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
That requests a quote for swapping 2,400 USDC into WETH on Ethereum. The response includes the current expectedAmountOut, which you use to calculate the effective price.
Step 2: Configure the Limit Order Parameters
Create a file called limit_order_bot.py. Start with the configuration block that defines your order.
CONFIG = {
"chain_id": 1,
"token_in": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"token_out": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"amount": "2400000000",
"target_price": 2400.0,
"direction": "buy_below",
"max_slippage": 0.005,
"poll_interval": 30,
"rpc_url": "https://cloudflare-eth.com",
}
This configuration watches the USDC-to-WETH price and triggers a buy when 2,400 USDC gets you 1 WETH or more -- effectively buying WETH below $2,400. The amount is in USDC's smallest unit (6 decimals), so 2400000000 equals 2,400 USDC.
To run on a different chain, change chain_id and the token addresses. Here are addresses for the most popular chains:
| Chain | chainId | WETH Address | USDC Address |
|---|---|---|---|
| Ethereum | 1 | 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
| Arbitrum | 42161 | 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1 |
0xaf88d065e77c8cC2239327C5EDb3A432268e5831 |
| Base | 8453 | 0x4200000000000000000000000000000000000006 |
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
Step 3: Build the Price Polling Function
The core of any limit order bot is the price feed. Rather than relying on a separate oracle, you query the swap API directly. This gives you the actual executable price including DEX liquidity and routing -- not just a theoretical mid-price. Over 9.7 million unique wallets interacted with DEXs by mid-2025, ensuring deep liquidity pools across major pairs.
import requests
import time
API_BASE = "https://api.swapapi.dev/v1/swap"
def get_swap_quote(config):
url = f"{API_BASE}/{config['chain_id']}"
params = {
"tokenIn": config["token_in"],
"tokenOut": config["token_out"],
"amount": config["amount"],
"sender": config["sender"],
"maxSlippage": config["max_slippage"],
}
resp = requests.get(url, params=params, timeout=15)
return resp.json()
The API returns a response envelope with success, data, and timestamp. The key fields for price calculation are expectedAmountOut and the token decimals.
def calculate_price(quote, token_in_decimals, token_out_decimals):
amount_in = int(quote["data"]["amountIn"])
amount_out = int(quote["data"]["expectedAmountOut"])
human_in = amount_in / (10 ** token_in_decimals)
human_out = amount_out / (10 ** token_out_decimals)
return human_in / human_out
For the USDC-to-WETH example, if expectedAmountOut is "1000000000000000000" (1 WETH) and amountIn is "2400000000" (2,400 USDC), the price is 2,400 / 1 = 2,400 USDC per WETH.
Step 4: Implement the Threshold Logic
The direction of your limit order determines when to trigger. A "buy below" order executes when the price drops to or below your target. A "sell above" order executes when the price rises to or above your target.
def should_execute(current_price, target_price, direction):
if direction == "buy_below":
return current_price <= target_price
elif direction == "sell_above":
return current_price >= target_price
return False
The crypto trading bot market is growing at a 15.5% CAGR through 2033, largely because threshold-based execution like this eliminates the need to watch charts manually.
Step 5: Execute the Swap On-Chain
When the price condition is met, you submit the transaction using the calldata from the API response. The API returns a complete tx object with to, data, value, and gasPrice -- ready to sign and send.
from web3 import Web3
def execute_swap(tx_data, config, private_key):
w3 = Web3(Web3.HTTPProvider(config["rpc_url"]))
account = w3.eth.account.from_key(private_key)
nonce = w3.eth.get_transaction_count(account.address)
gas_estimate = w3.eth.estimate_gas({
"from": tx_data["from"],
"to": tx_data["to"],
"data": tx_data["data"],
"value": int(tx_data["value"]),
})
tx = {
"to": tx_data["to"],
"data": tx_data["data"],
"value": int(tx_data["value"]),
"gas": int(gas_estimate * 1.2),
"gasPrice": tx_data["gasPrice"],
"nonce": nonce,
"chainId": config["chain_id"],
}
signed = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
return w3.to_hex(tx_hash)
Two critical safeguards in this function: the gas estimate multiplied by 1.2 adds a 20% buffer for complex multi-hop routes, and estimate_gas itself acts as a dry-run -- if it fails, the swap would revert on-chain, so you should not submit.
Try simulating a swap execution with eth_call before sending real value:
curl -X POST https://cloudflare-eth.com -H "Content-Type: application/json" -d '{"method":"eth_call","params":[{"from":"0x...","to":"0x...","data":"0x...","value":"0x0"},"latest"],"id":1,"jsonrpc":"2.0"}'
Step 6: Wire Up the Main Loop
Now combine everything into a polling loop that checks the price, evaluates the condition, and executes when the threshold is hit.
import os
def run_limit_order_bot():
private_key = os.environ["BOT_PRIVATE_KEY"]
w3 = Web3(Web3.HTTPProvider(CONFIG["rpc_url"]))
account = w3.eth.account.from_key(private_key)
CONFIG["sender"] = account.address
token_in_decimals = 6
token_out_decimals = 18
print(f"Limit order bot started")
print(f"Target: {CONFIG['direction']} at {CONFIG['target_price']}")
while True:
try:
quote = get_swap_quote(CONFIG)
if not quote.get("success"):
print(f"API error: {quote.get('error', {}).get('message')}")
time.sleep(CONFIG["poll_interval"])
continue
status = quote["data"]["status"]
if status == "NoRoute":
print("No route found, retrying...")
time.sleep(CONFIG["poll_interval"])
continue
price = calculate_price(quote, token_in_decimals, token_out_decimals)
print(f"Current price: {price:.2f} USDC/WETH")
if should_execute(price, CONFIG["target_price"], CONFIG["direction"]):
print(f"Target hit! Executing swap...")
tx_hash = execute_swap(quote["data"]["tx"], CONFIG, private_key)
print(f"Swap submitted: {tx_hash}")
break
except Exception as e:
print(f"Error: {e}")
time.sleep(CONFIG["poll_interval"])
The bot polls every 30 seconds (configurable via poll_interval). The swap API allows approximately 30 requests per minute per IP, so a 30-second interval stays well within limits. When the price condition is met, it fetches a fresh quote and executes immediately -- the API calldata is valid for 30 seconds, so freshness matters.
Step 7: Add Safety Checks and ERC-20 Approval
Before your bot can swap ERC-20 tokens, the sender wallet must approve the DEX router to spend the input token. This is a one-time setup per token-router pair.
ERC20_APPROVE_ABI = '[{"inputs":[{"name":"spender","type":"address"},{"name":"amount","type":"uint256"}],"name":"approve","outputs":[{"type":"bool"}],"type":"function"}]'
def approve_token(w3, token_address, spender, private_key):
account = w3.eth.account.from_key(private_key)
contract = w3.eth.contract(address=token_address, abi=ERC20_APPROVE_ABI)
max_uint256 = 2**256 - 1
tx = contract.functions.approve(spender, max_uint256).build_transaction({
"from": account.address,
"nonce": w3.eth.get_transaction_count(account.address),
})
signed = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
w3.eth.wait_for_transaction_receipt(tx_hash)
return w3.to_hex(tx_hash)
Add a price impact check before execution to avoid swapping into low-liquidity pools:
def is_safe_to_execute(quote):
impact = quote["data"].get("priceImpact", 0)
if impact < -0.05:
print(f"Price impact too high: {impact:.2%}")
return False
if quote["data"]["status"] == "Partial":
print("Partial fill only -- skipping")
return False
return True
Limit Order Bot vs. CEX Limit Orders
| Feature | CEX Limit Order | On-Chain Limit Order Bot |
|---|---|---|
| Custody | Exchange holds your funds | You hold your private keys |
| Fees | 0.1-0.5% maker fee | Gas cost only |
| Tokens | Limited listings | Any token on 46 EVM chains |
| Setup | Create account, KYC, deposit | Deploy script, fund wallet |
| Uptime | Exchange managed | Self-hosted or cron/VPS |
| Transparency | Opaque matching engine | Verifiable on-chain execution |
| API key | Required | Not required with swapapi.dev |
Running in Production
For a production deployment, run the bot on a VPS or as a systemd service. Store the private key in an environment variable, never in the source code.
BOT_PRIVATE_KEY=0xYOUR_KEY python limit_order_bot.py
For multiple limit orders across chains, spawn separate bot instances per order. Each instance polls independently and executes against the target chain's liquidity. With 46 supported chains on swapapi.dev, you can run limit orders on Ethereum, Arbitrum, Base, BSC, Polygon, and dozens more from a single API.
Frequently Asked Questions
What is a limit order bot in crypto?
A limit order bot is an automated script that monitors token prices on decentralized exchanges and executes a swap when the price reaches a specific threshold. Unlike centralized exchange limit orders that sit in an order book, an on-chain limit order bot polls current prices and submits a transaction when the condition is met. This approach preserves self-custody since your funds stay in your wallet until execution.
How often should my limit order bot check prices?
A poll interval of 15-30 seconds balances responsiveness with API rate limits. The swap API at swapapi.dev allows approximately 30 requests per minute per IP, so a 30-second interval is safe for a single bot. If you run multiple bots from the same IP, increase the interval proportionally. For volatile markets, 15 seconds catches faster price movements.
Can I run a limit order bot on multiple chains simultaneously?
Yes. Since swapapi.dev supports 46 EVM chains with a single API endpoint, you can run separate bot instances for different chains. Each instance uses its own chain_id and token addresses. The API call format is identical across all chains -- only the path parameter changes.
What happens if the swap fails after the price target is hit?
The bot calls estimate_gas before submitting, which acts as a dry run. If the estimate fails (insufficient balance, missing approval, or expired quote), the bot logs the error and continues polling instead of wasting gas on a reverted transaction. Always ensure your wallet has sufficient gas tokens and that the ERC-20 approval is in place before starting the bot.
Is building a limit order bot crypto-legal?
Automated trading on public DEXs is generally permitted since you are simply submitting transactions to public smart contracts. However, regulations vary by jurisdiction. Consult local laws regarding automated trading, and always report trading activity for tax purposes.
Get Started
Build your limit order bot today using swapapi.dev -- a free DEX aggregator API with no API keys, no accounts, and support for 46 EVM chains.
Explore the full API specification at the OpenAPI spec, or browse the interactive docs at swapapi.dev/docs.
Test a price quote right now:
curl "https://api.swapapi.dev/v1/swap/42161?tokenIn=0xaf88d065e77c8cC2239327C5EDb3A432268e5831&tokenOut=0x82aF49447D8a07e3bd95BD0d56f35241523fBab1&amount=2500000000&sender=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
Top comments (0)