For developers aiming to build crypto trading agents that operate autonomously on DeFi protocols, blending Python’s flexibility with Solidity’s smart contract capabilities is a solid approach. Here, I’ll walk through a hands-on tutorial on building a DeFi trading bot that executes automated liquidation or swap strategies on Uniswap. This guide emphasizes real-world implementation, security best practices, and pragmatic tool choices.
If you’re looking for a build crypto trading agent tutorial focused on Solidity contracts integrated with a Python trading AI bot, you’re in the right place. Along the way, I’ll surface gotchas I’ve hit and explain why certain architectural choices matter.
For those interested in starting from scratch, this complements our onchain AI agent setup and framework comparison pages.
Before we start coding, ensure you have the following:
web3.py and relevant trading SDKs (like Freqtrade or Hummingbot)Install core dependencies for the Solidity environment:
npm install --save-dev hardhat @nomiclabs/hardhat-ethers ethers
And for Python:
pip install web3 eth-account
I used Hardhat 2.x in this build, which has a stable plugin ecosystem and a JS-friendly experience. This tutorial assumes some familiarity with Solidity and Python basics but stays focused on the integration.
The bot architecture has two main components:
This separation means your trading logic runs off-chain (in Python), but sensitive trade executions happen on-chain, reducing attack surfaces.
Key design decisions:
Let’s start with a simple Solidity contract to swap tokens via Uniswap V3.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
contract TradingBot {
ISwapRouter public immutable swapRouter;
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
constructor(address _swapRouter) {
swapRouter = ISwapRouter(_swapRouter);
owner = msg.sender;
}
function swapExactInputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountIn,
uint256 amountOutMinimum
) external onlyOwner returns (uint256 amountOut) {
// Transfer the amountIn tokens from the owner to this contract
require(
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn),
"Transfer failed"
);
// Approve the router to spend tokenIn
IERC20(tokenIn).approve(address(swapRouter), amountIn);
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
recipient: owner,
deadline: block.timestamp + 300,
amountIn: amountIn,
amountOutMinimum: amountOutMinimum,
sqrtPriceLimitX96: 0
});
amountOut = swapRouter.exactInputSingle(params);
}
}
interface IERC20 {
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
}
This contract lets the owner swap a precise input amount of one token to another via Uniswap V3. It restricts execution to owner only, preventing unauthorized calls.
Why this approach? By limiting logic on-chain, you reduce bot complexity and gas costs. The Python agent decides when to trigger the swap, the contract just executes.
Now, onto the Python side. This agent monitors price signals (for example, from TheGraph or on-chain oracles) and triggers swaps when conditions meet your strategy.
Below is a minimalist example to send a swap tx:
from web3 import Web3
from eth_account import Account
import json
import os
# Load config
rpc_url = os.getenv("RPC_URL") # Set your Goerli RPC URL
private_key = os.getenv("BOT_PRIVATE_KEY") # Bot wallet private key
w3 = Web3(Web3.HTTPProvider(rpc_url))
account = Account.from_key(private_key)
# Load compiled contract JSON
with open('./artifacts/contracts/TradingBot.sol/TradingBot.json') as f:
contract_json = json.load(f)
contract_address = '0xYourDeployedContractAddress'
contract = w3.eth.contract(address=contract_address, abi=contract_json['abi'])
# Prepare transaction
token_in = Web3.to_checksum_address("0xTokenInAddress")
token_out = Web3.to_checksum_address("0xTokenOutAddress")
fee = 3000 # 0.3% pool fee
amount_in = w3.to_wei(0.01, 'ether')
amount_out_minimum = 0 # For tutorial, always better set slippage
nonce = w3.eth.get_transaction_count(account.address)
tx = contract.functions.swapExactInputSingle(
token_in,
token_out,
fee,
amount_in,
amount_out_minimum
).buildTransaction({
'from': account.address,
'nonce': nonce,
'gas': 200000,
'gasPrice': w3.to_wei('30', 'gwei')
})
# Sign and send
signed_tx = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(f"Swap transaction sent: {tx_hash.hex()}")
# Optionally wait for receipt
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Transaction confirmed in block {receipt.blockNumber}")
In my experience, remember to set appropriate gas limits and monitor slippage carefully. Hardcoding amount_out_minimum to zero is a huge security risk — in production, calculate this based on price oracles or your own logic.
And one small trick: modularize triggers and execution in separate processes or threads to avoid bottlenecks.
While the contract uses exactInputSingle to execute swaps through Uniswap V3, customizing it to support multi-hop swaps or other pools requires familiarity with Uniswap’s router API.
The parameters:
tokenIn and tokenOut: ERC-20 addressesfee: the specific pool fee tier (e.g., 500, 3000, 10000)amountIn and amountOutMinimum: swap inputssqrtPriceLimitX96: optional price limit buffer (usually zero)This contract setup makes a straightforward swap, but advanced bot strategies might bundle multiple swaps or integrate flash swaps. That kind of complexity calls for more on-chain state management, which tends to increase development risk.
I usually recommend starting simple and iterating.
When building autonomous DeFi trading agents, security is always front and center.
More on wallet management and agent security is covered in our agent wallet security deep dive.
MEV (Miner Extractable Value) techniques, such as frontrunning and sandwich trading, can enhance bot profitability but require advanced tooling and a strong security mindset.
Integrating MEV strategies with your Python agent often means:
Both the smart contract and Python agent need to be designed for quick execution and fail-safe rollback mechanisms.
But keep in mind — MEV interactions significantly increase complexity and risk, especially on mainnet.
Choosing a foundation for your trading agent depends on your stack preference and feature needs.
| Feature | Freqtrade | Hummingbot |
|---|---|---|
| Language | Python | Python |
| Focus | Crypto spot & derivatives bot | AMM arbitrage, market making |
| On-chain integration | Limited (mostly centralized APIs) | Supports Uniswap & other AMMs |
| Community & Documentation | Large & active | Growing |
| License | MIT | Apache-2.0 |
| Maturity | Stable; production-ready | Mature, with DeFi focus |
If you want greater control over on-chain swaps and strategy layering, Hummingbot might be the better starting point. But if you prioritize quick iterations and broader asset types, Freqtrade offers a good playground.
Keep in mind many builders adapt these frameworks heavily or build minimal custom agents when using smart contracts for execution.
Some developer pain points I’ve encountered:
eth_estimateGastransferFrom failures: Confirm token approval and wallet balancesAnd a quick tip: logging full RPC request/response data helps identify subtle provider errors.
Building an autonomous DeFi trading bot combining Python and Solidity is fully achievable with solid fundamentals in both worlds. The key is clear separation of concerns: keep complex logic off-chain, rely on secure, minimal on-chain contracts for execution, and integrate trusted price oracles.
Start with the minimal swap contract and Python agent example here, then gradually add your strategy, security controls, and monitoring. I personally recommend exploring account abstraction (ERC-4337) in future iterations to simplify wallet management.
For further reading, our onchain AI agent setup and framework comparison pages provide complementary insights.
Happy bot building!