-
Notifications
You must be signed in to change notification settings - Fork 318
/
main.mo
148 lines (124 loc) · 5.23 KB
/
main.mo
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
import Debug "mo:base/Debug";
import Blob "mo:base/Blob";
import Cycles "mo:base/ExperimentalCycles";
import Error "mo:base/Error";
import Array "mo:base/Array";
import Nat8 "mo:base/Nat8";
import Nat64 "mo:base/Nat64";
import Text "mo:base/Text";
//import the custom types we have in Types.mo
import Types "Types";
//Actor
actor {
//This method sends a GET request to a URL with a free API we can test.
//This method returns Coinbase data on the exchange rate between USD and ICP
//for a certain day.
//The API response looks like this:
// [
// [
// 1682978460, <-- start timestamp
// 5.714, <-- lowest price during time range
// 5.718, <-- highest price during range
// 5.714, <-- price at open
// 5.714, <-- price at close
// 243.5678 <-- volume of ICP traded
// ],
// ]
//function to transform the response
public query func transform(raw : Types.TransformArgs) : async Types.CanisterHttpResponsePayload {
let transformed : Types.CanisterHttpResponsePayload = {
status = raw.response.status;
body = raw.response.body;
headers = [
{
name = "Content-Security-Policy";
value = "default-src 'self'";
},
{ name = "Referrer-Policy"; value = "strict-origin" },
{ name = "Permissions-Policy"; value = "geolocation=(self)" },
{
name = "Strict-Transport-Security";
value = "max-age=63072000";
},
{ name = "X-Frame-Options"; value = "DENY" },
{ name = "X-Content-Type-Options"; value = "nosniff" },
];
};
transformed;
};
public func get_icp_usd_exchange() : async Text {
//1. DECLARE IC MANAGEMENT CANISTER
//We need this so we can use it to make the HTTP request
let ic : Types.IC = actor ("aaaaa-aa");
//2. SETUP ARGUMENTS FOR HTTP GET request
// 2.1 Setup the URL and its query parameters
let ONE_MINUTE : Nat64 = 60;
let start_timestamp : Types.Timestamp = 1682978460; //May 1, 2023 22:01:00 GMT
let end_timestamp : Types.Timestamp = 1682978520;//May 1, 2023 22:02:00 GMT
let host : Text = "api.pro.coinbase.com";
let url = "https://" # host # "/products/ICP-USD/candles?start=" # Nat64.toText(start_timestamp) # "&end=" # Nat64.toText(start_timestamp) # "&granularity=" # Nat64.toText(ONE_MINUTE);
// 2.2 prepare headers for the system http_request call
let request_headers = [
{ name = "Host"; value = host # ":443" },
{ name = "User-Agent"; value = "exchange_rate_canister" },
];
// 2.2.1 Transform context
let transform_context : Types.TransformContext = {
function = transform;
context = Blob.fromArray([]);
};
// 2.3 The HTTP request
let http_request : Types.HttpRequestArgs = {
url = url;
max_response_bytes = null; //optional for request
headers = request_headers;
body = null; //optional for request
method = #get;
transform = ?transform_context;
};
//3. ADD CYCLES TO PAY FOR HTTP REQUEST
//The IC specification spec says, "Cycles to pay for the call must be explicitly transferred with the call"
//IC management canister will make the HTTP request so it needs cycles
//See: https://internetcomputer.org/docs/current/motoko/main/cycles
//The way Cycles.add() works is that it adds those cycles to the next asynchronous call
//"Function add(amount) indicates the additional amount of cycles to be transferred in the next remote call"
//See: https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request
Cycles.add(230_949_972_000);
//4. MAKE HTTPS REQUEST AND WAIT FOR RESPONSE
//Since the cycles were added above, we can just call the IC management canister with HTTPS outcalls below
let http_response : Types.HttpResponsePayload = await ic.http_request(http_request);
//5. DECODE THE RESPONSE
//As per the type declarations in `src/Types.mo`, the BODY in the HTTP response
//comes back as [Nat8s] (e.g. [2, 5, 12, 11, 23]). Type signature:
//public type HttpResponsePayload = {
// status : Nat;
// headers : [HttpHeader];
// body : [Nat8];
// };
//We need to decode that [Nat8] array that is the body into readable text.
//To do this, we:
// 1. Convert the [Nat8] into a Blob
// 2. Use Blob.decodeUtf8() method to convert the Blob to a ?Text optional
// 3. We use a switch to explicitly call out both cases of decoding the Blob into ?Text
let response_body: Blob = Blob.fromArray(http_response.body);
let decoded_text: Text = switch (Text.decodeUtf8(response_body)) {
case (null) { "No value returned" };
case (?y) { y };
};
//6. RETURN RESPONSE OF THE BODY
//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
// ],
// ]
decoded_text
};
};