Skip to main content

How to Build a Crypto Trading Bot with a Swap API

· 6 min read
swapapi
swapapi Team

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

tip

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

  1. Never commit private keys — Use environment variables
  2. Start small — Test with minimal amounts
  3. Monitor gas costs — Set maximum gas limits
  4. Use multisig for large amounts — Require multiple signatures
  5. Have a kill switch — Emergency stop function
  6. Log everything — Audit trail for all trades