How to update this simple example for new HTTP_TRANSFORM

Hey! @qijing_yu

I often use the example apps and simplify them, and then change them to my use case.
I know that the most recent changes are to do with the transform function. Can you provide a tip on how I could just change that to allow this to keep working?

use candid::{CandidType, Principal};
use ic_cdk_macros::{self, query, update};
use ic_cdk::api::management_canister::http_request::{
    HttpHeader, HttpMethod, TransformFunc, TransformType,
};
use serde::{Deserialize, Serialize};
use serde_json::{self, Value};
use std::collections::{HashMap};

#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct CanisterHttpRequestArgs {
    pub url: String,
    pub max_response_bytes: Option<u64>,
    pub headers: Vec<HttpHeader>,
    pub body: Option<Vec<u8>>,
    pub method: HttpMethod,
    pub transform: Option<TransformType>,
}

#[derive(CandidType, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CanisterHttpResponsePayload {
    pub status: u128,
    pub headers: Vec<HttpHeader>,
    pub body: Vec<u8>,
}


// How many data points in each Coinbase API call. Maximum allowed is 300
pub const DATA_POINTS_PER_API: u64 = 200;
pub const MAX_RESPONSE_BYTES: u64 = 10 * 6 * DATA_POINTS_PER_API;

/*
A function to call IC http_request function with sample interval of REMOTE_FETCH_GRANULARITY seconds. Each API
call fetches DATA_POINTS_PER_API data points, which is equivalent of DATA_POINTS_PER_API minutes of data.
 */
#[update]
async fn get_rate() -> String {

    let host = "api.coinbase.com";
    let mut host_header = host.clone().to_owned();
    host_header.push_str(":443");
    // prepare system http_request call
    let request_headers = vec![
        HttpHeader {
            name: "Host".to_string(),
            value: host_header,
        },
        HttpHeader {
            name: "User-Agent".to_string(),
            value: "exchange_rate_canister".to_string(),
        },
    ];
    let url = format!("https://{host}/v2/prices/icp-usd/spot");
    ic_cdk::api::print(url.clone());

    let request = CanisterHttpRequestArgs {
        url: url,
        method: HttpMethod::GET,
        body: None,
        max_response_bytes: Some(MAX_RESPONSE_BYTES),
        transform: Some(TransformType::Function(TransformFunc(candid::Func {
            principal: ic_cdk::api::id(),
            method: "transform".to_string(),
        }))),
        headers: request_headers,
    };

    let body = candid::utils::encode_one(&request).unwrap();
    ic_cdk::api::print(format!("Making IC http_request call now."));

    match ic_cdk::api::call::call_raw(
        Principal::management_canister(),
        "http_request",
        &body[..],
        2_000_000_000,
    )
    .await
    {
        Ok(result) => {
            // decode the result
            let decoded_result: CanisterHttpResponsePayload =
                candid::utils::decode_one(&result).expect("IC http_request failed!");

            let decoded_body = String::from_utf8(decoded_result.body)
                .expect("Remote service response is not UTF-8 encoded.");
            let fetch = decode_body_to_rates(&decoded_body);
            return fetch.get("data").unwrap().get("amount").unwrap().to_string();
        }
        Err((r, m)) => {
            let message =
                format!("The http_request resulted into error. RejectionCode: {r:?}, Error: {m}");
            ic_cdk::api::print(message.clone());
            return message.clone().to_string();
        }
    }
}

fn decode_body_to_rates(body: &str) -> Value {
    let json: serde_json::Value =
        serde_json::from_str(&body).unwrap();
    return json
}

#[query]
async fn transform(raw: CanisterHttpResponsePayload) -> CanisterHttpResponsePayload {
    let mut sanitized = raw.clone();
    sanitized.headers = vec![];
    sanitized
}

fn main() {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_decode_body_to_rates() {
        let body = "{\"data\":{\"base\":\"ICP\",\"currency\":\"USD\",\"amount\":\"5.96\"}}";
        let fetched = decode_body_to_rates(body);
        println!("{:?}", fetched.get("data").unwrap().get("amount").unwrap());
        assert_eq!(*fetched.get("data").unwrap().get("amount").unwrap(), serde_json::json!("5.96"));
    }
}

I don’t know the specifics, but the diff for the update to the examples could give useful hints.

1 Like

I think I got it.

Also FYI in the public sample Dfinity uses api.pro.coinbase.com which does not work without an API key? Worked when I changed it back to api.coinbase.com

ust/exchange_rate/src/main.rs

Like this? Switch to free coinbase API by sesi200 · Pull Request #367 · dfinity/examples · GitHub

That’s what happens if people don’t write tests…

1 Like

Good morning @apotheosis @Severin! Thanks for answering the questions while it was night time at my side. Glad you figured out how to update your dapp to work with transform context.

WRT api.pro.coinbase.com, I did testing right before publishing sample dapp updates, and did another testing just now. I was able to download rates from Coinbase Pro without API key. https://api.pro.coinbase.com/products/ICP-USD/candles is accessible if you just hit the link without supplying an API key. The API you are trying to call: https://api.pro.coinbase.com/products/ICP-USD/spot on the other hand, shows an Route not found error though. Looking at the Pro API documentation it does look like they don’t have spot price served in Pro/Exchange section.

The PR-367 unfortunately breaks the sample dapp. Curling https://api.coinbase.com doesn’t seem to exist, and requests are being redirected to documentation website.

1 Like

Thanks!

Another that may be confusing to people…

ic-cdk = { path = "../../../cdk-rs/src/ic-cdk" }

I changed this to

ic-cdk = { path = "0.6.5" }
1 Like

Thanks for the correction @qijing_yu, I fixed it. And thank you for the dependencies thing @apotheosis, that’s also fixed.

The example is now also under a minimal e2e test that checks if deployment works properly.

Thanks all! My subnet was updated today and fiat to canister deploy payments were down for a very short time. Smooth transition :pray:

1 Like