How to Build a Crypto Trading Bot with a Swap API
This guide walks you through building a production-ready crypto trading bot using swapapi — the only DEX aggregator API that requires no API key or authentication.
What You'll Build
A trading bot that:
- Fetches optimal swap routes across 46 EVM chains
- Validates safety constraints before executing
- Signs and submits transactions automatically
- Handles errors and retries with exponential backoff
Prerequisites
- Node.js 18+ or Python 3.9+
- A wallet with private key (for testing, use a small amount)
- Basic knowledge of TypeScript or Python
Step 1: Choose Your Strategy
Common automated trading strategies:
DCA (Dollar-Cost Averaging): Buy a fixed amount at regular intervals regardless of price.
Rebalancing: Maintain target portfolio weights by swapping when allocations drift.
Arbitrage: Exploit price differences between DEXs (requires monitoring multiple sources).
Signal-based: Execute trades based on technical indicators or external signals.
For this guide, we'll build a simple DCA bot that buys ETH with USDC every hour.
Step 2: Set Up the Project
Remember: swapapi requires no API key. You can start making requests immediately.
TypeScript Setup
npm init -y
npm install viem
Python Setup
pip install web3 requests
Step 3: Fetch Swap Quotes
The core of your bot: fetching optimal swap routes and executable calldata.
TypeScript
import { createWalletClient, http, parseEther } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
async function getSwapQuote(params: {
chainId: number;
tokenIn: string;
tokenOut: string;
amount: string;
sender: string;
}) {
const url = `https://api.swapapi.dev/v1/swap/${params.chainId}?` +
new URLSearchParams({
tokenIn: params.tokenIn,
tokenOut: params.tokenOut,
amount: params.amount,
sender: params.sender,
});
const res = await fetch(url);
const { success, data } = await res.json();
if (!success) throw new Error("API request failed");
if (data.status === "NoRoute") throw new Error("No swap route found");
return data;
}
Python
import requests
def get_swap_quote(chain_id: int, token_in: str, token_out: str,
amount: str, sender: str):
url = f"https://api.swapapi.dev/v1/swap/{chain_id}"
params = {
"tokenIn": token_in,
"tokenOut": token_out,
"amount": amount,
"sender": sender
}
response = requests.get(url, params=params)
result = response.json()
if not result["success"]:
raise Exception("API request failed")
if result["data"]["status"] == "NoRoute":
raise Exception("No swap route found")
return result["data"]
Step 4: Add Safety Checks
Never execute trades without validating safety constraints.
Price Impact Check
function validateTrade(data: any, maxPriceImpact: number = -0.05) {
// Reject if price impact exceeds 5%
if (data.priceImpact < maxPriceImpact) {
throw new Error(`Price impact ${data.priceImpact} exceeds limit ${maxPriceImpact}`);
}
// Check for partial fills
if (data.status === "Partial") {
throw new Error("Only partial fill available");
}
return true;
}
Maximum Trade Size
const MAX_TRADE_SIZE = parseEther("1"); // 1 ETH max
function validateAmount(amount: string) {
if (BigInt(amount) > MAX_TRADE_SIZE) {
throw new Error("Trade size exceeds maximum");
}
}
Simulate Before Submitting
Use eth_call to simulate the transaction before broadcasting:
import { createPublicClient, http } from 'viem';
const publicClient = createPublicClient({
chain: base,
transport: http(),
});
async function simulateTx(tx: any) {
try {
await publicClient.call({
account: tx.from,
to: tx.to,
data: tx.data,
value: BigInt(tx.value),
});
return true;
} catch (error) {
console.error("Simulation failed:", error);
return false;
}
}
Step 5: Execute Trades
Once validated, sign and submit the transaction.
TypeScript with Viem
async function executeTrade(privateKey: string, chainId: number) {
const account = privateKeyToAccount(privateKey as `0x${string}`);
const sender = account.address;
// Get quote
const quote = await getSwapQuote({
chainId,
tokenIn: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
tokenOut: "0x4200000000000000000000000000000000000006", // WETH on Base
amount: "10000000", // 10 USDC (6 decimals)
sender,
});
// Validate
validateTrade(quote);
// Simulate
const canExecute = await simulateTx(quote.tx);
if (!canExecute) throw new Error("Transaction simulation failed");
// Execute
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
});
const hash = await walletClient.sendTransaction({
to: quote.tx.to as `0x${string}`,
data: quote.tx.data as `0x${string}`,
value: BigInt(quote.tx.value),
gas: BigInt(quote.tx.gas),
});
return hash;
}
Python with Web3.py
from web3 import Web3
import time
def execute_trade(private_key: str, chain_id: int, rpc_url: str):
w3 = Web3(Web3.HTTPProvider(rpc_url))
account = w3.eth.account.from_key(private_key)
sender = account.address
# Get quote
quote = get_swap_quote(
chain_id=chain_id,
token_in="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
token_out="0x4200000000000000000000000000000000000006",
amount="10000000",
sender=sender
)
# Build transaction
tx = {
'from': sender,
'to': quote['tx']['to'],
'data': quote['tx']['data'],
'value': int(quote['tx']['value']),
'gas': int(quote['tx']['gas']),
'gasPrice': w3.eth.gas_price,
'nonce': w3.eth.get_transaction_count(sender),
'chainId': chain_id
}
# Sign and send
signed_tx = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
return tx_hash.hex()
Step 6: Production Hardening
Error Handling with Exponential Backoff
async function executeWithRetry(fn: () => Promise<any>, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Gas Management
async function getOptimalGasPrice() {
const block = await publicClient.getBlock();
// Use base fee + 10% buffer
const baseFee = block.baseFeePerGas || BigInt(0);
const priorityFee = parseGwei("0.1");
return baseFee + priorityFee;
}
Rate Limit Awareness
let lastRequestTime = 0;
const MIN_REQUEST_INTERVAL = 2000; // 2 seconds
async function throttledRequest(fn: () => Promise<any>) {
const now = Date.now();
const timeSinceLastRequest = now - lastRequestTime;
if (timeSinceLastRequest < MIN_REQUEST_INTERVAL) {
await new Promise(resolve =>
setTimeout(resolve, MIN_REQUEST_INTERVAL - timeSinceLastRequest)
);
}
lastRequestTime = Date.now();
return fn();
}
Logging and Monitoring
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'trades.log' }),
new winston.transports.Console()
]
});
// Log every trade
logger.info('Trade executed', {
txHash,
tokenIn: quote.tokenFrom.symbol,
tokenOut: quote.tokenTo.symbol,
amountIn: quote.amountIn,
amountOut: quote.expectedAmountOut,
priceImpact: quote.priceImpact,
timestamp: new Date().toISOString()
});
Step 7: Schedule Your Bot
Use cron or a scheduler to run your strategy:
// Run every hour
import cron from 'node-cron';
cron.schedule('0 * * * *', async () => {
console.log('Running DCA strategy...');
try {
const txHash = await executeTrade(process.env.PRIVATE_KEY!, 8453);
console.log(`Trade executed: ${txHash}`);
} catch (error) {
console.error('Trade failed:', error);
}
});
Complete Example
Here's a minimal but complete trading bot:
import { createWalletClient, http } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import cron from 'node-cron';
const PRIVATE_KEY = process.env.PRIVATE_KEY!;
const CHAIN_ID = 8453; // Base
async function runDCA() {
const account = privateKeyToAccount(PRIVATE_KEY as `0x${string}`);
// Get quote
const res = await fetch(
`https://api.swapapi.dev/v1/swap/${CHAIN_ID}?` +
`tokenIn=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&` +
`tokenOut=0x4200000000000000000000000000000000000006&` +
`amount=10000000&` + // 10 USDC
`sender=${account.address}`
);
const { success, data } = await res.json();
if (!success || data.status !== "Successful") {
throw new Error("Failed to get quote");
}
// Execute
const wallet = createWalletClient({
account,
chain: base,
transport: http(),
});
const hash = await wallet.sendTransaction({
to: data.tx.to as `0x${string}`,
data: data.tx.data as `0x${string}`,
value: BigInt(data.tx.value),
gas: BigInt(data.tx.gas),
});
console.log(`DCA executed: ${hash}`);
}
// Run every hour
cron.schedule('0 * * * *', runDCA);
console.log('DCA bot started. Running every hour.');
Security Best Practices
- Never commit private keys — Use environment variables
- Start small — Test with minimal amounts
- Monitor gas costs — Set maximum gas limits
- Use multisig for large amounts — Require multiple signatures
- Have a kill switch — Emergency stop function
- Log everything — Audit trail for all trades
