-
Notifications
You must be signed in to change notification settings - Fork 318
/
lib.rs
171 lines (146 loc) · 5.71 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//1. IMPORT IC MANAGEMENT CANISTER
//This includes all methods and types needed
use ic_cdk::api::management_canister::http_request::{
http_request, CanisterHttpRequestArgument, HttpHeader, HttpMethod, HttpResponse, TransformArgs,
TransformContext,
};
use ic_cdk_macros::{self, query, update};
use serde::{Serialize, Deserialize};
use serde_json::{self, Value};
// This struct is legacy code and is not really used in the code.
#[derive(Serialize, Deserialize)]
struct Context {
bucket_start_time_index: usize,
closing_price_index: usize,
}
//Update method using the HTTPS outcalls feature
#[ic_cdk::update]
async fn get_icp_usd_exchange() -> String {
//2. SETUP ARGUMENTS FOR HTTP GET request
// 2.1 Setup the URL and its query parameters
type Timestamp = u64;
let start_timestamp: Timestamp = 1682978460; //May 1, 2023 22:01:00 GMT
let seconds_of_time: u64 = 60; //we start with 60 seconds
let host = "api.pro.coinbase.com";
let url = format!(
"https://{}/products/ICP-USD/candles?start={}&end={}&granularity={}",
host,
start_timestamp.to_string(),
start_timestamp.to_string(),
seconds_of_time.to_string()
);
// 2.2 prepare headers for the system http_request call
//Note that `HttpHeader` is declared in line 4
let request_headers = vec![
HttpHeader {
name: "Host".to_string(),
value: format!("{host}:443"),
},
HttpHeader {
name: "User-Agent".to_string(),
value: "exchange_rate_canister".to_string(),
},
];
// This struct is legacy code and is not really used in the code. Need to be removed in the future
// The "TransformContext" function does need a CONTEXT parameter, but this implementation is not necessary
// the TransformContext(transform, context) below accepts this "context", but it does nothing with it in this implementation.
// bucket_start_time_index and closing_price_index are meaninglesss
let context = Context {
bucket_start_time_index: 0,
closing_price_index: 4,
};
//note "CanisterHttpRequestArgument" and "HttpMethod" are declared in line 4
let request = CanisterHttpRequestArgument {
url: url.to_string(),
method: HttpMethod::GET,
body: None, //optional for request
max_response_bytes: None, //optional for request
// transform: None, //optional for request
transform: Some(TransformContext::new(transform, serde_json::to_vec(&context).unwrap())),
headers: request_headers,
};
//3. MAKE HTTPS REQUEST AND WAIT FOR RESPONSE
//Note: in Rust, `http_request()` already sends the cycles needed
//so no need for explicit Cycles.add() as in Motoko
match http_request(request).await {
//4. DECODE AND RETURN THE RESPONSE
//See:https://docs.rs/ic-cdk/latest/ic_cdk/api/management_canister/http_request/struct.HttpResponse.html
Ok((response,)) => {
//if successful, `HttpResponse` has this structure:
// pub struct HttpResponse {
// pub status: Nat,
// pub headers: Vec<HttpHeader>,
// pub body: Vec<u8>,
// }
//We need to decode that Vec<u8> that is the body into readable text.
//To do this, we:
// 1. Call `String::from_utf8()` on response.body
// 3. We use a switch to explicitly call out both cases of decoding the Blob into ?Text
let str_body = String::from_utf8(response.body)
.expect("Transformed response is not UTF-8 encoded.");
//The API response will looks like this:
// ("[[1682978460,5.714,5.718,5.714,5.714,243.5678]]")
//Which can be formatted as this
// [
// [
// 1682978460, <-- start/timestamp
// 5.714, <-- low
// 5.718, <-- high
// 5.714, <-- open
// 5.714, <-- close
// 243.5678 <-- volume
// ],
// ]
//Return the body as a string and end the method
str_body
}
Err((r, m)) => {
let message =
format!("The http_request resulted into error. RejectionCode: {r:?}, Error: {m}");
//Return the error as a string and end the method
message
}
}
}
// Strips all data that is not needed from the original response.
#[query]
fn transform(raw: TransformArgs) -> HttpResponse {
let headers = vec![
HttpHeader {
name: "Content-Security-Policy".to_string(),
value: "default-src 'self'".to_string(),
},
HttpHeader {
name: "Referrer-Policy".to_string(),
value: "strict-origin".to_string(),
},
HttpHeader {
name: "Permissions-Policy".to_string(),
value: "geolocation=(self)".to_string(),
},
HttpHeader {
name: "Strict-Transport-Security".to_string(),
value: "max-age=63072000".to_string(),
},
HttpHeader {
name: "X-Frame-Options".to_string(),
value: "DENY".to_string(),
},
HttpHeader {
name: "X-Content-Type-Options".to_string(),
value: "nosniff".to_string(),
},
];
let mut res = HttpResponse {
status: raw.response.status.clone(),
body: raw.response.body.clone(),
headers,
..Default::default()
};
if res.status == 200 {
res.body = raw.response.body;
} else {
ic_cdk::api::print(format!("Received an error from coinbase: err = {:?}", raw));
}
res
}