DEV Community

loading...
Cover image for How to make a cryptocurrency Telegram bot with Rust and Teloxide

How to make a cryptocurrency Telegram bot with Rust and Teloxide

Steadylearner
I am a full stack developer searching for jobs. (Rust, Python, JavaScript, Haskell, Golang, React, Solidity, Polkadot)
Updated on ・7 min read

In this post, you will learn how to make a simple cryptocurrency Telegram chat bot similar to the cover of this post.

The Rust programming language will be used mainly for this post.

🦀 Why Rust?

Rust is blazingly fast and memory-efficient: with no runtime or garbage collector, it can power performance-critical services, run on embedded devices, and easily integrate with other languages.

While the code used here is very simple, hope it can be helpful to you to start with the language. You can find code used for this post here.

We will use Teloxide and binance-rs. Please, spend time to read the documentations before you read on.

There are a few Rust Telegram framework but I decided to use Teloxide because you can get help from its authors easily with its Telegram channel.

If you don't have Rust in your machine yet, please follow the instruction of the official Rust website.

You can also optionally install cargo edit and cargo watch to help you develop better.

$cargo install cargo-edit
$cargo install cargo-watch
Enter fullscreen mode Exit fullscreen mode

I referred it before I write this post. If you are familiar with JavaScript, it can be helpful to read that first.

To test this tutorial, you need at least a Telegram account.

You can also create an account at Binance if you want to use its API more later.

Table of Contents

  1. Set up a Telegram bot with BotFather.
  2. Prepare the development environment to use Teloxide and binance-rs.
  3. Build your Telegram bot to find a cryptocurrency price with Binance.
  4. Conclusion

1. Set up Telegram bot with BotFather

To make a Telegram bot, we need to make an API token and set it up.

First, please visit the BotFather page

Then, use the /help command there. It will show the commands you can use with it.

botfather commands by /help

There are many of them but you will need only a few to start with.

CREATE, LIST, DELETE
/newbot - create a new bot
/mybots - list your bots
/deletebot - delete a bot

UPDATE META DATA
/setdescription - change bot description
/setabouttext - change bot about info
/setuserpic - change bot profile photo
Enter fullscreen mode Exit fullscreen mode

You can start your bot with /newbot command and follow its instruction. Use binance_bot for your botname or use another if you want.

You will have a token if you see the message similar to this.

Telegram bot API token from botfather

Save it well and we will use it later at .env file we will make.

Then, you can use /setuserpic to save your bot image.

botfather /setuserpic command

I used Binance image. But, you can use others.

Congratulations, if you could make it to this point, you are ready to write a Telegram bot with whatever programming language.

2. Prepare the development environment to use Teloxide and binance-rs

Previously, we could set up a Telegram bot and get a API token to remotely control it with the Rust code we will write.

In this part, we will install Teloxide and binance-rs crates and set up a minimal development environment.

Use this commands first to start a Rust project.

$cargo new binance_bot
Enter fullscreen mode Exit fullscreen mode

This will create a few files in binance_bot folder. Edit your Cargo.toml file in it with this.

[package]
name = "binance_bot"
version = "0.1.0"
edition = "2018"

[dependencies]
# 1.
dotenv = "0.15.0"
dotenv_codegen = "0.15.0"

# 2. 
teloxide = { version = "0.4", features = ["frunk", "macros", "auto-send"] } 

log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version =  "1.3", features = ["rt-multi-thread", "macros"] }

binance = { git = "https://github.com/wisespace-io/binance-rs.git" }
Enter fullscreen mode Exit fullscreen mode

It won't be that different from what REAMD.md file from Teloxide gives you. But, there are some differences you need to know.

1. We will use dotenv instead of manually setting TELOXIDE_TOKEN different from the command given by its documentation.

# Unix-like
$ export TELOXIDE_TOKEN=<Your token here>

# Windows
$ set TELOXIDE_TOKEN=<Your token here>
Enter fullscreen mode Exit fullscreen mode

2. If you don't include macros to teloxide features, some of its examples won't work with 'can't find derive macro' etc.

Then, create a .env file with $touch .env and include your token to it.

TELOXIDE_TOKEN=<YOUR TOKEN FROM THE PREVIOUS PART>
Enter fullscreen mode Exit fullscreen mode

src/main.rs file shoud be made for you already.

Paste this code adapted from the official example to use .env file to it.

use dotenv::dotenv;
use std::env;

use teloxide::prelude::*;

#[tokio::main]
async fn main() {
    run().await;
}

async fn run() {
  dotenv().ok(); // Read .env and set env variables with this

  teloxide::enable_logging!();
  log::info!("Starting dices_bot...");

  let bot = Bot::from_env().auto_send();

  teloxide::repl(bot, |message| async move {
    println!("dice");
    message.answer_dice().await?;
    respond(())
  })
  .await;
  // INFO  binance_bot > Starting dices_bot...
}
Enter fullscreen mode Exit fullscreen mode

Use $cargo run --release and wait a little bit before the Rust compiler ends its job.

Then, while it is working at your console, visit your Telegram bot you made and type whatever you want. It will show somewhat similar to this.

Teloxide dice example

Hope you could make it. You verified that you can use Teloxide crate in your machine. In the next part, we will use binance-rs along with it. You can edit your main.rs file with this to see binance-rs crate will work at your development environment.

use binance::api::*;
use binance::market::*;

fn main() {
    let market: Market = Binance::new(None, None);

    // Latest price for ALL symbols
    // match market.get_all_prices() {
    //     Ok(answer) => println!("{:#?}", answer),
    //     Err(e) => println!("Error: {:#?}", e),
    // }

    match market.get_price("BTCUSDT") {
        Ok(answer) => println!("{:#?}", answer),
        Err(e) => println!("Error: {:#?}", e),
    }
}
Enter fullscreen mode Exit fullscreen mode

Test the example code above with $cargo run --release command. It should show current Bitcoin price for USDT token at your console.

I used Macbook air with M1 chip to compile it. But it failed to compile with arm64 architecture.

Search how to use i386 arch instead if you find an issue with it. You can see which arch your machine uses with $arch.

3. Build a Telegram bot to find a cryptocurrency price with Binance.

In this part, we will modify the commands example from Teloxide.

Please, read and test its code first. Then, you can modify your main.rs file similar to this.

use dotenv::dotenv;
use std::{env, error::Error};

use teloxide::{payloads::SendMessageSetters, prelude::*};
use teloxide::utils::command::BotCommand;
use teloxide::{utils::markdown::link};
use teloxide::types::ParseMode::MarkdownV2;

use binance::api::*;
use binance::market::*;

fn to_uppercase(string: &str) -> String {
    string.chars().map(|c| c.to_ascii_uppercase()).collect()
}

// Command examples

// /help
// /register
// /price BTC
// /price BTC USDT
// /price btc sudt
// /price BNB BTC
// /price bnb btc
#[derive(BotCommand)]
#[command(rename = "lowercase", description = "These commands are supported:")]
// 1.
enum Command {
  #[command(description = "display this text.")]
  Help,
  #[command(description = "show a Binance sign up page.")]
  Register,
  #[command(description = "show a cryptcurrency price in USDT by default.")]
  Price(String),
}

async fn responses_to_command(
    cx: UpdateWithCx<AutoSend<Bot>, Message>,
    command: Command,
) -> Result<(), Box<dyn Error + Send + Sync>> {
    let market: Market = Binance::new(None, None);

    match command {
        // 1.
        Command::Help => cx.answer(Command::descriptions()).send().await?, 
        // 2.
        Command::Register => {
            let register_link = link("https://accounts.binance.com/en/register?ref=SQ86TYC5", "Don't have a Binance account yet? You can register here\\.");

            cx.answer(register_link).parse_mode(MarkdownV2).send().await?
        },
        // 3.
        Command::Price(crpytocurrency) => {
            let mut iter = crpytocurrency.split_whitespace();

            if let Some(first_crypto_symbol) = iter.next() {

                let second_crypto_symbol = if let Some(second_crypto_symbol) = iter.next() {
                    println!("There was a second_crypto_symbol.");
                    second_crypto_symbol
                } else {
                    println!("There was no second_crypto_symbol. Use default.");
                    "USDT"
                };

                let target = to_uppercase(
                    &format!("{}{}", &first_crypto_symbol, &second_crypto_symbol)
                );

                match market.get_price(target) {
                    Ok(symbol_price) => {
                        println!("{:#?}", &symbol_price);
                        cx.answer(format!("The price you want is {:#?}. ", &symbol_price.price)).await?
                    },
                    Err(e) => {
                        eprint!("{:#?}", e);

                        cx.answer(format!("Something went wrong. Did you use the correct cryptocurrency pair?")).await?
                    },
                }
            } else {
                cx.answer("Cryptocurrency symbols were not specified. To start with, you can use /price ETH or /price ETH USDT.").await?
            }
        }
    };

    Ok(())
}

#[tokio::main]
async fn main() {
    run().await;
}

async fn run() {
    dotenv().ok();

    teloxide::enable_logging!();
    log::info!("Starting the_bot...");

    let bot = Bot::from_env().auto_send();
    let bot_name: String = "binance_bot".into();

    teloxide::commands_repl(bot, bot_name, responses_to_command).await;
}
Enter fullscreen mode Exit fullscreen mode

Use $cargo run --release and visit your bot.

Type /help there, then test a few commands similar to these below.

/help
/register

/price BTC
/price BTC USDT
/price btc usdt
/price BNB BTC
/price bnb btc
Enter fullscreen mode Exit fullscreen mode

Some of them will show results similar to these.

Bitcoin Price before

Bitcoin Price

(Well, it is interesting to see its historical drop while writing this post. First one is from when I wrote the code for this and the latter is from when I write this post.)

The payloads here will be command relevant parts and others are for set up. So, I will explain them only.

1. It shows the description texts you define from enum Command part. You need to edit there only.

2. It wasn't easy to find how to use markdown with Teloxide by reading its documentation. It will be helpful to know Telegram wants you to use \\ for some characters when you use markdown.

3. The user can send various inputs along with /price commands. You can see we can handle them easily with Rust API.

For example, user can send an empty input, input without second cryptocurrency symbol (it is handled by default USDT), input with more than 2 cryptocurrency symbols etc.

You could find the beauty of the Rust language if you thought the code to handle this with other programming languages.

If you have any question with the Teloxide, you can refer to its documenation.

4. Conclusion

In this post, we learnt how to set up Rust env to use Teloxide framework, hope you can make all of this part work without any issue. I included many images to help you follow the example better.

If you are familiar with Binance, you can write more commands to use its account relevant API also.

If you liked the post, please share it with others. I am plan to share more blockchain relevant stuffs. I am interested in ETH and POLKADOT.

If you need to hire a developer, you can contact me.

I will write a blog post to deploy this if many people find this post useful.

Thanks.

Discussion (20)

Collapse
scyart profile image
Scy

Hi, I got this error, is there any solution? Thank you so much.

thread 'tokio-runtime-worker' panicked at 'Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context.

Collapse
steadylearner profile image
Steadylearner Author

Did you use the exact same version of tokio crate used for this post?

tokio = { version = "1.3", features = ["rt-multi-thread", "macros"]

I asked the author of Teloxide and he told me no recent change currently. So, it could be relevant to the tokio crate.

Collapse
scyart profile image
Scy • Edited

Yes, I used your code completely, without any changes.
I have just confirmed that it is not a Tokio version issue, I used "=1.3.0", and I tried Ubuntu(x86), Windows, MacOS (M1), all with the same error :(

Thread Thread
steadylearner profile image
Steadylearner Author • Edited

What rust version you used? (I used rustup 1.24.1) I think it can be better to get help from the teloxide Telegram group.

Did you make the dice example work first?

If you can, please join the group t.me/teloxide and ask questions there. I think it can save your time much better. There are more experienced one and authors that can help you to solve that issue all long.

Thread Thread
scyart profile image
Scy

Ok, I'm using version 1.24.3, and I can use dice example as well as binance's example

Thread Thread
steadylearner profile image
Steadylearner Author • Edited

Ok, so is everything ok now?

Thread Thread
scyart profile image
Scy • Edited

No, I said the example of teloxide and the example of binance-rs. But if I copy your code directly, I get the same error xD
Really weird, I've tried all kinds of operating systems, I've tried to copy all the same conditions as yours and it still shows this error.
But thank you very much for your quick reply! I will try to test other possible causes :)

Thread Thread
steadylearner profile image
Steadylearner Author

Ok, there maybe something different between your machine and my laptop.

Maybe you can test without cargo.lock

Anyway, hope you can find the solution for that.

If there are something I can help more, please let me know.

Thread Thread
scyart profile image
Scy

I'm trying to figure out what the problem is, I've tried different computers and they have the same error.
Here is my code (with my Cargo.toml dependencies above),
I hope you can help me test it when you are free. I don't know if you can run it or if the same error message appears with me.
Thank you very much! I'm not in a hurry. You can take a look at it when you are free.
gist.github.com/cy3r0/f94fa4f0bc3d...

Thread Thread
steadylearner profile image
Steadylearner Author • Edited

I tested it at github.com/steadylearner/blockchai...

I think it is ok here. I included the result image there.

dev-to-uploads.s3.amazonaws.com/up...

dev-to-uploads.s3.amazonaws.com/up...

Thread Thread
scyart profile image
Scy

Thank you very much. That will be very helpful.
It's pretty clear now that the problem is the Rust version, since I'm also using the M1 MacBook Air.
I would like to ask what is your rustc version? I am 1.53

Thread Thread
steadylearner profile image
Steadylearner Author • Edited

It is 1.52.1 and cargo 1.54.0-nightly here.

github.com/rust-lang/rust/blob/mas...

You can read more here.

github.com/rust-lang/cargo

Thread Thread
scyart profile image
Scy

Hello there,
I found that it may be my own problem.
Because I have been using cargo run, I have succeeded after trying cargo run --release.
Actually I don't know the difference between the two, but I have succeeded. Thank you very much for your help.

Thread Thread
steadylearner profile image
Steadylearner Author

--release is for without debug relevant information.

Happy to hear that you could make that.

Collapse
shahram_ava profile image
shahram cohen

You are absolutely right, and it has nothing with version , the code above would not work at all, the error message is very clear.

stackoverflow.com/questions/654266...

stackoverflow.com/questions/625365...

stackoverflow.com/questions/617528....

I found a solution, I can help you on that.

Collapse
scyart profile image
Scy

wow, thank you!

Collapse
shahram_ava profile image
shahram cohen

@steadylearner It is impossible that the code above works. For the simple reason that @Scy has mentioned.

We need a async version of binance-re, thee are two repo, that they have done it, I made my own solution.

Collapse
steadylearner profile image
Steadylearner Author

Ok, can you share the code or repository that works?

Collapse
shahram_ava profile image
shahram cohen

I invited you to the github repo, I got the initial code from openlimits. Their code was not working and even if we could there were lot of mismatching in versions of libraries so I copied it and remove some unnecessary part of their code and fixed the versions. In long run I like to improve it as I really need it for my trading algos. My algos are not fully automatic so I need a telgram bot that generates some alerts for me.

Thank you for your initial code.

Thread Thread
steadylearner profile image
Steadylearner Author • Edited

Ok, I accepted, can you contact me with t.me/steadylearner and explain what can be done by me to help you?