Make a cryptocurrency price fetcher with Rust

Make a cryptocurrency price fetcher with Rust

I have been learning Rust lately and soaking it up like a sponge. I figured one day, why not make a simple terminal application that fetches price data from the public Kraken REST API. In the process of making this program, I'll show off using one of my favorite error handling crates to simplify things.

The crates needed for this program are as follows:

[dependencies]
color-eyre = "0.6.2"
reqwest = { version = "0.11.14", features = ["json"] }
serde = { version = "1.0.156", features = ["derive"] }
serde_json = "1.0.91"
tokio = { version = "1.26.0", features = ["full"] }

Color-eyre handles error reports for me in a colorful way, so I tend to go with it whenever I'm not using the Anyhow crate. Color-eyre stays consistent with error reports and help to improve the already excellent Rust compiler in its errors. The reqwest crate is listed here with JSON functionality since that is the shape of the data we are working with here. Reqwest is the HTTP client that handles the work for us when it comes to communicating with the REST API. I'm going to be using the tokio crate along with it as the asynchronous runtime, meaning we won't have to use the synchronous blocking client that reqwest provides already.

The serde and serde_json crates are very important for this project, since without them I cannot serialize/de-serialize data to and from JSON. Today, I'll be using these crates in order to work with a typed Rust data structure (struct). This will ensure that the data conforms to a particular structure and shape in order to get the work done and ensure that once the program is compiled that there are no errors. Serde_json is used alongside reqwest so that any valid JSON value is assigned to a variable. Afterwards, with serde the data is then taken from that variable and given a shape of struct Ticker. The value is interpreted as an instance of the type Ticker via serde.

use color_eyre::eyre::Result;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Ticker {
    a: Vec<String>,
    b: Vec<String>,
    c: Vec<String>,
    v: Vec<String>,
    p: Vec<String>,
    t: Vec<i32>,
    l: Vec<String>,
    h: Vec<String>,
    o: String,
}

//...

The above Rust code shows the use of the serde crate with the derive feature enabled. The struct Ticker defines the shape of the JSON that our program works with. The Serialize and Deserialize derive macros will work with this data structure since they are implemented for the collection Vec<T> along with the std type String. I do not need to use the Debug derive macro here because I will be converting this String type into a f64, which impl std::fmt::Display .

#[tokio::main]
async fn main() -> Result<()> {

    color_eyre::install()?;

    let data: serde_json::Value = reqwest::Client::new()
        .get("https://api.kraken.com/0/public/Ticker?pair=XMRUSD")
        .send()
        .await?
        .json()
        .await?;

    let ticker_str = serde_json::to_string(&data["result"]["XXMRZUSD"])?;

    let ticker: Ticker = serde_json::from_str(&ticker_str)?;

    let xmr_data = ticker.a[0].to_string();

    let money = xmr_data.parse::<f64>()?;

    println!("Monero's value in Dollars for today is: ${}", money);

    Ok(())
}

Since I decided to use the tokio crate's asynchronous runtime rather than the builtin blocking one that reqwest includes, I included the tokio::main macro in order to simplify things. The main function returns a Result<T> , where T is the unit type in Rust. This means that on success, Ok(()) is used here before the function leaves scope, allowing me to use the ? operator in order to deal with any errors.

Color_eyre is initiated in the first line of code within the main() function and reqwest is used to make the HTTP request to the endpoint of the asset (XMR) that I want the price information of. I interpret the returned data as an instance of type Ticker , as explained earlier, and then I serialize the data to a JSON string. If I had used the from_value() method here, then I would've needed to cheat the borrow checker with a .clone() in order to move out of the value - this is avoided by using to_string() and from_string() instead. The specific String value is then converted to a 64-bit floating point number in order to be printed to the terminal as money.

Thanks for reading my first article!

References

Did you find this article valuable?

Support Charles Martel by becoming a sponsor. Any amount is appreciated!