Building a Price-Tracking and Automated Trading Bot in Rust

Tushar Bhatia
3 min readDec 27, 2024

--

In this article, I’m excited to share a project I’ve been working on: a Rust-based bot for tracking cryptocurrency prices and performing automated swaps on Ethereum. Whether you’re a blockchain developer or just starting with Rust, this bot offers a practical way to apply your skills to a real-world problem. I’ll walk you through the core components of the bot, explain the architecture, and share how you can extend it.

Link to GitHub: https://github.com/tushar1698/Rust-for-Web3/tree/main/Price_and_Swap_Bot

Why Build This Bot?

The motivation was simple: I wanted to create something that not only tracks live prices of crypto pairs like ETH/Link but also executes swaps when certain conditions are met. This bot is designed to interact with Uniswap-like decentralized exchanges, making it versatile and useful in both testnets and mainnets.

The Core Features

1. Fetch real-time prices of tokens.

2. Perform token swaps automatically based on your preferences.

3. Flexible configuration for easy customization.

4. Modular design to add more token pairs or extend functionality seamlessly.

The Architecture

The bot is structured in a modular way to keep things clean and manageable:

config: Handles user configuration (e.g., RPC URL, private key, token pairs).

client: Sets up the Ethereum client for blockchain interactions.

price_feed: Fetches token prices using oracles like Chainlink.

trade: Executes swaps on Uniswap-style decentralized exchanges.

  • main.rs: The entry point tying everything together.

Let’s Dive Into the Code

1. Setting Up the Ethereum Client

The bot starts by setting up an Ethereum client using your RPC URL and private key stored in the .env file. Here’s the client module:

use ethers::prelude::*;
use std::sync::Arc;

pub async fn create_client(
rpc_url: String,
private_key: String,
) -> Result<Arc<SignerMiddleware<Provider<Http>, Wallet<SigningKey>>>, Box<dyn std::error::Error>> {
let provider = Provider::<Http>::try_from(rpc_url.clone())?;
let wallet = Wallet::from_str(&private_key)?.with_chain_id(11155111); // Sepolia Testnet
let client = SignerMiddleware::new(provider, wallet);
Ok(Arc::new(client))
}

This sets up the foundation to interact with Ethereum, enabling us to send transactions and query on-chain data.

2. Fetching Real-Time Prices

To get the live prices, we use Chainlink price feeds. Here’s a snippet from the price_feed module:

pub async fn get_latest_price(
client: Arc<SignerMiddleware<Provider<Http>, Wallet<SigningKey>>>,
price_feed_address: Address,
) -> Result<U256, Box<dyn std::error::Error>> {
let abi = include_bytes!("../abis/PriceFeed_ABI.json");
let abi = ethers::abi::Abi::load(abi.as_ref())?;
let contract = Contract::new(price_feed_address, abi, client);
let price: U256 = contract.method("latestAnswer", ())?.call().await?;
Ok(price)
}

This fetches the current price of the specified token pair from a price oracle contract.

3. Executing Swaps

The bot supports token swaps on decentralized exchanges. Here’s how the trade module looks:

pub struct TradeExecutor {
pub client: Arc<SignerMiddleware<Provider<Http>, Wallet<SigningKey>>>,
pub router_address: Address,
pub recipient: Address,
}

impl TradeExecutor {
pub async fn execute_swap(
&self,
token_in: Address,
token_out: Address,
amount_in: U256,
min_amount_out: U256,
) -> Result<TxHash, Box<dyn std::error::Error>> {
let abi = include_bytes!("../abis/Uniswap_Router_ABI.json");
let abi = ethers::abi::Abi::load(abi.as_ref())?;
let contract = Contract::new(self.router_address, abi, self.client.clone());

let tx = contract
.method::<_, TxHash>(
"swapExactTokensForTokens",
(amount_in, min_amount_out, vec![token_in, token_out], self.recipient, deadline),
)?
.send()
.await?;
Ok(tx)
}
}

4. Configuration Made Simple

All sensitive data like RPC URLs and private keys are stored in a .env file. Here’s an example:

RPC_URL=https://sepolia.infura.io/v3/YOUR_PROJECT_ID
PRIVATE_KEY=<YOUR_PRIVATE_KEY>
LINK_ETH_PRICE_FEED=0x42585eD362B3f1BCa95c640FdFf35Ef899212734
UNISWAP_ROUTER=0xE592427A0AEce92De3Edee1F18E0157C05861564

The config module then loads this data:

pub fn load_config() -> Config {
dotenv::dotenv().ok();
Config {
rpc_url: std::env::var("RPC_URL").expect("RPC_URL not found in .env"),
private_key: std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY not found in .env"),
link_eth_price_feed: std::env::var("LINK_ETH_PRICE_FEED").unwrap(),
uniswap_router: std::env::var("UNISWAP_ROUTER").unwrap(),
}
}

Customizing the Bot

Adding new functionality, such as supporting more tokens, is straightforward:

1. Add the token contract addresses to the .env file.

2. Update the config and price_feed modules accordingly.

You can even extend the TradeExecutor struct to include more swap methods for ETH-based pairs or other assets.

Lessons Learned

Building this bot taught me how to:

• Use ethers-rs to interact with Ethereum.

• Handle smart contract calls in Rust.

• Write modular, maintainable code.

  • Work with Chainlink oracles and Uniswap-like DEXs.

Final Thoughts

This project is a great starting point for anyone looking to dive into blockchain development with Rust. Feel free to clone the repository and experiment with the code. Remember, use it at your own risk — this is for educational purposes!

If you found this helpful or have ideas for improvement, let me know in the comments or reach out. Let’s build together!

--

--

Tushar Bhatia
Tushar Bhatia

Written by Tushar Bhatia

Not Your Regular NERD Blockchain Security Expert | DeFi Innovator | Advanced Solidity Developer | Foundry Lover | Solana Enthusiast

No responses yet