From 54b98306d642d0796e51ddac7c94ecfeb79458ef Mon Sep 17 00:00:00 2001 From: Jessie Mongeon <133128541+jessiemongeon1@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:12:26 -0600 Subject: [PATCH] fix: Outdated Dev Ladder instructions/code (#3933) * fix dev ladder * fix * fix * fix * fix * fix * add sub update for quill doc * fix --- .../level-2/2.1-storage-persistence.mdx | 4 +- .../level-2/2.3-third-party-canisters.mdx | 13 +- .../level-2/2.4-intro-candid.mdx | 12 +- .../level-2/2.5-unit-testing.mdx | 8 +- .../level-3/3.1-package-managers.mdx | 4 +- .../level-3/3.2-https-outcalls.mdx | 586 +----------------- .../level-3/3.3-certified-data.mdx | 8 +- .../level-3/3.4-intro-to-agents.mdx | 110 +--- .../level-3/3.5-identities-and-auth.mdx | 151 +---- submodules/quill | 2 +- submodules/samples | 2 +- 11 files changed, 79 insertions(+), 821 deletions(-) diff --git a/docs/tutorials/developer-journey/level-2/2.1-storage-persistence.mdx b/docs/tutorials/developer-journey/level-2/2.1-storage-persistence.mdx index c2e42f03e2..e385565e12 100644 --- a/docs/tutorials/developer-journey/level-2/2.1-storage-persistence.mdx +++ b/docs/tutorials/developer-journey/level-2/2.1-storage-persistence.mdx @@ -108,7 +108,7 @@ You will be prompted to select the language that your backend canister will use. `dfx` versions `v0.17.0` and newer support this `dfx new` interactive prompt. [Learn more about `dfx v0.17.0`](/blog/2024/02/14/news-and-updates/update#dfx-v0170). ::: -Then, select a frontend framework for your frontend canister. Select 'No frontend canister': +Then, select a frontend framework for your frontend canister. Select 'None': ``` ? Select a frontend framework: › @@ -117,7 +117,7 @@ Then, select a frontend framework for your frontend canister. Select 'No fronten Vue Vanilla JS No JS template -❯ No frontend canister +❯ None ``` Lastly, you can include extra features to be added to your project: diff --git a/docs/tutorials/developer-journey/level-2/2.3-third-party-canisters.mdx b/docs/tutorials/developer-journey/level-2/2.3-third-party-canisters.mdx index 16861819eb..c24e04d2c7 100644 --- a/docs/tutorials/developer-journey/level-2/2.3-third-party-canisters.mdx +++ b/docs/tutorials/developer-journey/level-2/2.3-third-party-canisters.mdx @@ -56,7 +56,7 @@ You will be prompted to select the language that your backend canister will use. `dfx` versions `v0.17.0` and newer support this `dfx new` interactive prompt. [Learn more about `dfx v0.17.0`](/blog/2024/02/14/news-and-updates/update#dfx-v0170). ::: -Then, select a frontend framework for your frontend canister. Select 'No frontend canister': +Then, select a frontend framework for your frontend canister. Select 'None': ``` ? Select a frontend framework: › @@ -65,7 +65,7 @@ Then, select a frontend framework for your frontend canister. Select 'No fronten Vue Vanilla JS No JS template -❯ No frontend canister +❯ None ``` Lastly, you can include extra features to be added to your project: @@ -152,14 +152,7 @@ Next, you'll set the `init` arguments for the dependency canisters using the `df dfx deps init ``` -This will show that our II canister requires an `init` argument: - -```bash -WARN: The following canister(s) require an init argument. Please run `dfx deps init ` to set them individually: -rdmx6-jaaaa-aaaaa-aaadq-cai (internet_identity) -``` - -This output shows you that the Internet Identity canister requires an `init` argument, but it doesn't give you any additional information. For more information, run the command `dfx deps init rdmx6-jaaaa-aaaaa-aaadq-cai`, which will provide an error message that shows you more details: +The Internet Identity canister requires an `init` argument. For more information, run the command `dfx deps init rdmx6-jaaaa-aaaaa-aaadq-cai`, which will provide an error message: ```bash Error: Canister rdmx6-jaaaa-aaaaa-aaadq-cai (internet_identity) requires an init argument. The following info might be helpful: diff --git a/docs/tutorials/developer-journey/level-2/2.4-intro-candid.mdx b/docs/tutorials/developer-journey/level-2/2.4-intro-candid.mdx index 1c8e6f9821..bb6d6a9b3c 100644 --- a/docs/tutorials/developer-journey/level-2/2.4-intro-candid.mdx +++ b/docs/tutorials/developer-journey/level-2/2.4-intro-candid.mdx @@ -234,7 +234,7 @@ You will be prompted to select the language that your backend canister will use. `dfx` versions `v0.17.0` and newer support this `dfx new` interactive prompt. [Learn more about `dfx v0.17.0`](/blog/2024/02/14/news-and-updates/update#dfx-v0170). ::: -Then, select a frontend framework for your frontend canister. Select 'No frontend canister': +Then, select a frontend framework for your frontend canister. Select 'None': ``` ? Select a frontend framework: › @@ -243,7 +243,7 @@ Then, select a frontend framework for your frontend canister. Select 'No fronten Vue Vanilla JS No JS template -❯ No frontend canister +❯ None ``` Lastly, you can include extra features to be added to your project: @@ -278,7 +278,13 @@ Save this file. Then, compile and deploy the project with the command: dfx deploy ``` -Recall that the Motoko compiler will automatically generate the Candid service description file based off of this code. This Candid file can be found at `src/declarations/candid_example_backend/candid_example_backend.did` and will contain the following: +To generate the Candid service description file based off of this code, run the command: + +``` +dfx generate +``` + +The generated Candid file can be found at `src/declarations/candid_example_backend/candid_example_backend.did` and will contain the following: ```candid title="src/declarations/candid_example_backend/candid_example_backend.did" service : { diff --git a/docs/tutorials/developer-journey/level-2/2.5-unit-testing.mdx b/docs/tutorials/developer-journey/level-2/2.5-unit-testing.mdx index f1dbe306d5..e14a7a78fd 100644 --- a/docs/tutorials/developer-journey/level-2/2.5-unit-testing.mdx +++ b/docs/tutorials/developer-journey/level-2/2.5-unit-testing.mdx @@ -176,7 +176,7 @@ You will be prompted to select the language that your backend canister will use. `dfx` versions `v0.17.0` and newer support this `dfx new` interactive prompt. [Learn more about `dfx v0.17.0`](/blog/2024/02/14/news-and-updates/update#dfx-v0170). ::: -Then, select a frontend framework for your frontend canister. Select 'No frontend canister': +Then, select a frontend framework for your frontend canister. Select 'None': ``` ? Select a frontend framework: › @@ -185,7 +185,7 @@ Then, select a frontend framework for your frontend canister. Select 'No fronten Vue Vanilla JS No JS template -❯ No frontend canister +❯ None ``` Lastly, you can include extra features to be added to your project: @@ -366,10 +366,6 @@ test("Should contain a candid interface", async () => { }); ``` -:::info -Need the full `src/tests/e2e_tests_backend.test.ts` file? [Check out the repo containing the full project for this tutorial](https://github.com/krpeacock/sample-canister-e2e). -::: - This test simply checks that our canister's metadata contains a Candid interface. Then, run the test again with the `npm test` command. This time, the output should reflect 2 successful tests: diff --git a/docs/tutorials/developer-journey/level-3/3.1-package-managers.mdx b/docs/tutorials/developer-journey/level-3/3.1-package-managers.mdx index 6dbfb4e5b1..aea8483acb 100644 --- a/docs/tutorials/developer-journey/level-3/3.1-package-managers.mdx +++ b/docs/tutorials/developer-journey/level-3/3.1-package-managers.mdx @@ -81,7 +81,7 @@ You will be prompted to select the language that your backend canister will use. `dfx` versions `v0.17.0` and newer support this `dfx new` interactive prompt. [Learn more about `dfx v0.17.0`](/blog/2024/02/14/news-and-updates/update#dfx-v0170). ::: -Then, select a frontend framework for your frontend canister. Select 'No frontend canister': +Then, select a frontend framework for your frontend canister. Select 'None': ``` ? Select a frontend framework: › @@ -90,7 +90,7 @@ Then, select a frontend framework for your frontend canister. Select 'No fronten Vue Vanilla JS No JS template -❯ No frontend canister +❯ None ``` Lastly, you can include extra features to be added to your project: diff --git a/docs/tutorials/developer-journey/level-3/3.2-https-outcalls.mdx b/docs/tutorials/developer-journey/level-3/3.2-https-outcalls.mdx index e6d789527e..00a5145e5e 100644 --- a/docs/tutorials/developer-journey/level-3/3.2-https-outcalls.mdx +++ b/docs/tutorials/developer-journey/level-3/3.2-https-outcalls.mdx @@ -98,7 +98,7 @@ You will be prompted to select the language that your backend canister will use. `dfx` versions `v0.17.0` and newer support this `dfx new` interactive prompt. [Learn more about `dfx v0.17.0`](/blog/2024/02/14/news-and-updates/update#dfx-v0170). ::: -Then, select a frontend framework for your frontend canister. Select 'No frontend canister': +Then, select a frontend framework for your frontend canister. Select 'None': ``` ? Select a frontend framework: › @@ -107,7 +107,7 @@ Then, select a frontend framework for your frontend canister. Select 'No fronten Vue Vanilla JS No JS template -❯ No frontend canister +❯ None ``` Lastly, you can include extra features to be added to your project: @@ -129,142 +129,14 @@ cd https_get Then, open the `src/https_get_backend/main.mo` file in your code editor and replace the existing content with: -```motoko no-repl title="src/https_get_backend/main.mo" -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"; +```motoko no-repl file=../../../references/samples/motoko/send_http_get/src/send_http_get_backend/main.mo#L1-L5 ``` This piece of code imports the libraries that you'll be using. Each of these libraries is part of the Motoko base package, as indicated by 'mo:base'. -Next, insert the following line below these import statements: +Now let's insert the code to define our actor. This actor will contain two functions; a function to transform the response that you'll receive from our `GET` response, and a function that sends our `GET` request. The code has been annotated with notes describing in detail what each piece does: -```motoko no-repl title="src/https_get_backend/main.mo" -import Types "Types"; -``` - -This line imports the custom types stored in `Types.mo`. You'll create this file later. - -Following the `import Types "Types";` line, now let's insert the code to define our actor. This actor will contain two functions; a function to transform the response that you'll receive from our `GET` response, and a function that sends our `GET` request. The code has been annotated with notes describing in detail what each piece does: - -```motoko no-repl title="src/https_get_backend/main.mo" -actor { - - // Create a function that transform's the raw content into an HTTP payload. - 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; - }; - - // This function sends our GET request - - public func get_icp_usd_exchange() : async Text { - - //First, declare the management canister - let ic : Types.IC = actor ("aaaaa-aa"); - - //Next, you need to set the arguments for our GET request - - // Start with 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(end_timestamp) # "&granularity=" # Nat64.toText(ONE_MINUTE); - - // Prepare headers for the system http_request call. - - let request_headers = [ - { name = "Host"; value = host # ":443" }, - { name = "User-Agent"; value = "exchange_rate_canister" }, - ]; - - // Next, you define a function to transform the request's context from a blob datatype to an array. - - let transform_context : Types.TransformContext = { - function = transform; - context = Blob.fromArray([]); - }; - - // Finally, define 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; - }; - - // Now, you need to add some cycles to your call, since cycles to pay for the call must be transferred with the call. - // 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(20_949_972_000); - - // Now that you have the HTTP request and cycles to send with the call, you can make the HTTP request and await the response. - - let http_response : Types.HttpResponsePayload = await ic.http_request(http_request); - - // Once you have the response, you need to decode it. The body of the HTTP response should come back as [Nat8], which needs to be decoded into readable text. - // To do this, you: - // 1. Convert the [Nat8] into a Blob - // 2. Use Blob.decodeUtf8() method to convert the Blob to a ?Text optional - // 3. 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 }; - }; - - // Finally, you can return the 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 - }; - -}; +```motoko no-repl file=../../../references/samples/motoko/send_http_get/src/send_http_get_backend/main.mo#L8-L115 ``` The code above is annotated with detailed notes and explanations. Take a moment to read through the code's content and annotations to fully understand the code's functionality. @@ -273,9 +145,7 @@ A few important points to note are: - `get_icp_usd_exchange()` is an update call. All methods that make HTTPS outcalls must be update calls because they go through consensus, even if the HTTPS outcall is a `GET`. -- The code above adds `20_949_972_000` cycles. This is typically enough for `GET` requests, but this may need to change depending on your use case. - -- The code above imports `Types.mo` to separate the custom types from the actor file as a best practice. +- The code above adds `230_949_972_000` cycles. This is typically enough for `GET` requests, but this may need to change depending on your use case. ### The `transform` function @@ -293,164 +163,9 @@ This error occurs when the replicas on the subnet don't all return the same valu Once you've inserted these snippets of code into the `src/https_get_backend/main.mo` file, save the file. The file should look like this: -```motoko no-repl title="src/https_get_backend/main.mo" -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 Types "Types"; - -actor { - - 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 { - - let ic : Types.IC = actor ("aaaaa-aa"); - - 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(end_timestamp) # "&granularity=" # Nat64.toText(ONE_MINUTE); - - let request_headers = [ - { name = "Host"; value = host # ":443" }, - { name = "User-Agent"; value = "exchange_rate_canister" }, - ]; - - let transform_context : Types.TransformContext = { - function = transform; - context = Blob.fromArray([]); - }; - - 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; - }; - - Cycles.add(20_949_972_000); - - let http_response : Types.HttpResponsePayload = await ic.http_request(http_request); - - 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 }; - }; - - decoded_text - }; -} -``` - -Next, create the `src/https_get_backend/Types.mo` file with the command: - -```bash -touch `src/https_get_backend/Types.mo` -``` - -Then, open the `src/https_get_backend/Types.mo` file in your code editor and insert the following content: - -```motoko title="src/https_get_backend/Types.mo" -module Types { - - public type Timestamp = Nat64; - - // First, define the Type that describes the Request arguments for an HTTPS outcall. - - public type HttpRequestArgs = { - url : Text; - max_response_bytes : ?Nat64; - headers : [HttpHeader]; - body : ?[Nat8]; - method : HttpMethod; - transform : ?TransformRawResponseFunction; - }; - - public type HttpHeader = { - name : Text; - value : Text; - }; - - public type HttpMethod = { - #get; - #post; - #head; - }; - - public type HttpResponsePayload = { - status : Nat; - headers : [HttpHeader]; - body : [Nat8]; - }; - - // HTTPS outcalls have an optional "transform" key. These two types help describe it. - // The transform function can transform the body in any way, add or remove headers, or modify headers. - // This Type defines a function called 'TransformRawResponse', which is used above. - - public type TransformRawResponseFunction = { - function : shared query TransformArgs -> async HttpResponsePayload; - context : Blob; - }; - - // This Type defines the arguments the transform function needs. - public type TransformArgs = { - response : HttpResponsePayload; - context : Blob; - }; - - public type CanisterHttpResponsePayload = { - status : Nat; - headers : [HttpHeader]; - body : [Nat8]; - }; - - public type TransformContext = { - function : shared query TransformArgs -> async HttpResponsePayload; - context : Blob; - }; - - - // Lastly, declare the management canister which you use to make the HTTPS outcall. - public type IC = actor { - http_request : HttpRequestArgs -> async HttpResponsePayload; - }; - -} +```motoko no-repl file=../../../references/samples/motoko/send_http_get/src/send_http_get_backend/main.mo ``` -The code above is annotated with detailed notes and explanations. Take a moment to read through the code's content and annotations to fully understand the code's functionality. - Now, you can deploy the canister locally with the commands: ```bash @@ -502,127 +217,12 @@ cd https_post Then, open the `src/https_post_backend/main.mo` file in your code editor and replace the existing content with: -```motoko no-repl title="src/https_post_backend/main.mo" -import Debug "mo:base/Debug"; -import Blob "mo:base/Blob"; -import Cycles "mo:base/ExperimentalCycles"; -import Array "mo:base/Array"; -import Nat8 "mo:base/Nat8"; -import Text "mo:base/Text"; - -import Types "Types"; +```motoko no-repl file=../../../references/samples/motoko/send_http_get/src/send_http_get_backend/main.mo#L1-L4 ``` -This code is the same way you started your HTTP `GET` code. Now, you'll define an actor using that contain two functions; a function to transform the response that you'll receive from our `POST` response, and a function that sends our `POST` request. The code has been annotated with notes describing in detail what each piece does: - -```motoko no-repl title="src/https_post_backend/main.mo" -actor { - - // This function is used to transform the response received from our POST request - - 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; - }; - - // This function sends our POST request - public func send_http_post_request() : async Text { - - // First, declare the management canister - let ic : Types.IC = actor ("aaaaa-aa"); - - // Next, setup the URL and its query parameters. This portion uses a free API. - let host : Text = "putsreq.com"; - let url = "https://putsreq.com/aL1QS5IbaQd4NTqN3a81"; - - // Then, prepare headers for the system http_request call. Note that idempotency keys should be unique, so you create a function that generates them. - - let idempotency_key: Text = generateUUID(); - let request_headers = [ - { name = "Host"; value = host # ":443" }, - { name = "User-Agent"; value = "http_post_sample" }, - { name= "Content-Type"; value = "application/json" }, - { name= "Idempotency-Key"; value = idempotency_key } - ]; - - // Since the the request body is an array of [Nat8], next you do the following: - // 1. Write a JSON string - // 2. Convert ?Text optional into a Blob, which is an intermediate representation before you cast it as an array of [Nat8] - // 3. Convert the Blob into an array [Nat8] - - let request_body_json: Text = "{ \"name\" : \"Grogu\", \"force_sensitive\" : \"true\" }"; - let request_body_as_Blob: Blob = Text.encodeUtf8(request_body_json); - let request_body_as_nat8: [Nat8] = Blob.toArray(request_body_as_Blob); // e.g [34, 34,12, 0] - - - // Then you transform the content - let transform_context : Types.TransformContext = { - function = transform; - context = Blob.fromArray([]); - }; - - // Next, you make the POST request - let http_request : Types.HttpRequestArgs = { - url = url; - max_response_bytes = null; //optional for request - headers = request_headers; - //note: type of `body` is ?[Nat8] so you pass it here as "?request_body_as_nat8" instead of "request_body_as_nat8" - body = ?request_body_as_nat8; - method = #post; - transform = ?transform_context; - }; - - // Now, you need to add some cycles to your call, since cycles to pay for the call must be transferred with the call. - // 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(21_850_258_000); - - // Now that you have the HTTP request and cycles to send with the call, you can make the HTTP request and await the response. - - let http_response : Types.HttpResponsePayload = await ic.http_request(http_request); - - // Once you have the response, you need to decode it. The body of the HTTP response should come back as [Nat8], which needs to be decoded into readable text. - // To do this, you: - // 1. Convert the [Nat8] into a Blob - // 2. Use Blob.decodeUtf8() method to convert the Blob to a ?Text optional - // 3. Use Motoko syntax "Let... else" to unwrap what is returned from Text.decodeUtf8() - - 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 }; - }; - - // Finally, you can return the response of the body. - let result: Text = decoded_text # ". See more info of the request sent at at: " # url # "/inspect"; - result - }; - - // As a final step, you define a private helper function that generates a universally unique identifier. In this exercise, it returns a constant but in practice it should return a unique identifier. - - func generateUUID() : Text { - "UUID-123456789"; - } -}; +Now, you'll define an actor using that contain two functions; a function to transform the response that you'll receive from our `POST` response, and a function that sends our `POST` request. The code has been annotated with notes describing in detail what each piece does: + +```motoko no-repl file=../../../references/samples/motoko/send_http_get/src/send_http_get_backend/main.mo#L6-L106 ``` :::info @@ -633,171 +233,9 @@ The code above is annotated with detailed notes and explanations. Take a moment Once you've inserted these snippets of code into the `src/https_post_backend/main.mo` file, save the file. The file should look like this: -```motoko title="src/https_post_backend/main.mo" -import Debug "mo:base/Debug"; -import Blob "mo:base/Blob"; -import Cycles "mo:base/ExperimentalCycles"; -import Array "mo:base/Array"; -import Nat8 "mo:base/Nat8"; -import Text "mo:base/Text"; - -import Types "Types"; - -actor { - - - 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 send_http_post_request() : async Text { - - let ic : Types.IC = actor ("aaaaa-aa"); - - let host : Text = "putsreq.com"; - let url = "https://putsreq.com/aL1QS5IbaQd4NTqN3a81"; - - let idempotency_key: Text = generateUUID(); - let request_headers = [ - { name = "Host"; value = host # ":443" }, - { name = "User-Agent"; value = "http_post_sample" }, - { name= "Content-Type"; value = "application/json" }, - { name= "Idempotency-Key"; value = idempotency_key } - ]; - - let request_body_json: Text = "{ \"name\" : \"Grogu\", \"force_sensitive\" : \"true\" }"; - let request_body_as_Blob: Blob = Text.encodeUtf8(request_body_json); - let request_body_as_nat8: [Nat8] = Blob.toArray(request_body_as_Blob); // e.g [34, 34,12, 0] - - let transform_context : Types.TransformContext = { - function = transform; - context = Blob.fromArray([]); - }; - - let http_request : Types.HttpRequestArgs = { - url = url; - max_response_bytes = null; //optional for request - headers = request_headers; - //note: type of `body` is ?[Nat8] so you pass it here as "?request_body_as_nat8" instead of "request_body_as_nat8" - body = ?request_body_as_nat8; - method = #post; - transform = ?transform_context; - }; - - Cycles.add(21_850_258_000); - - let http_response : Types.HttpResponsePayload = await ic.http_request(http_request); - - 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 }; - }; - - // Finally, you can return the response of the body. - let result: Text = decoded_text # ". See more info of the request sent at: " # url # "/inspect"; - result - }; - - func generateUUID() : Text { - "UUID-123456789"; - } -}; +```motoko no-repl file=../../../references/samples/motoko/send_http_get/src/send_http_get_backend/main.mo ``` -Next, create the `src/https_post_backend/Types.mo` file with the command: - -```bash -touch `src/https_post_backend/Types.mo` -``` - -Open the `src/https_post_backend/Types.mo` file in your code editor and insert the following content: - -```motoko title="src/https_post_backend/Types.mo" -module Types { - - // First, define the Type that describes the Request arguments for an HTTPS outcall. - public type HttpRequestArgs = { - url : Text; - max_response_bytes : ?Nat64; - headers : [HttpHeader]; - body : ?[Nat8]; - method : HttpMethod; - transform : ?TransformRawResponseFunction; - }; - - public type HttpHeader = { - name : Text; - value : Text; - }; - - public type HttpMethod = { - #get; - #post; - #head; - }; - - public type HttpResponsePayload = { - status : Nat; - headers : [HttpHeader]; - body : [Nat8]; - }; - - // HTTPS outcalls have an optional "transform" key. These two types help describe it. - // The transform function can transform the body in any way, add or remove headers, or modify headers. - // This Type defines a function called 'TransformRawResponse', which is used above. - - public type TransformRawResponseFunction = { - function : shared query TransformArgs -> async HttpResponsePayload; - context : Blob; - }; - - // This Type defines the arguments the transform function needs. - public type TransformArgs = { - response : HttpResponsePayload; - context : Blob; - }; - - public type CanisterHttpResponsePayload = { - status : Nat; - headers : [HttpHeader]; - body : [Nat8]; - }; - - public type TransformContext = { - function : shared query TransformArgs -> async HttpResponsePayload; - context : Blob; - }; - - - // Lastly, declare the management canister which you use to make the HTTPS outcall. - public type IC = actor { - http_request : HttpRequestArgs -> async HttpResponsePayload; - }; - -} -``` - -The code above is annotated with detailed notes and explanations. Take a moment to read through the code's content and annotations to fully understand the code's functionality. - Now, you can deploy the canister locally with the commands: ```bash diff --git a/docs/tutorials/developer-journey/level-3/3.3-certified-data.mdx b/docs/tutorials/developer-journey/level-3/3.3-certified-data.mdx index eb4ae0ea39..de9ebb5275 100644 --- a/docs/tutorials/developer-journey/level-3/3.3-certified-data.mdx +++ b/docs/tutorials/developer-journey/level-3/3.3-certified-data.mdx @@ -199,12 +199,12 @@ git clone https://github.com/krpeacock/certified-cache cd certified-cache ``` -Then, install the following packages with Mops: +Upgrade your Node.js version to [v22.12.0](https://nodejs.org/en/download). Then, install the following packages with Mops: ```bash -mops install base@0.8.0 -mops install sha2@0.0.2 -mops install ic-certification@0.1.1 +mops install base@0.12.1 +mops install sha2@0.1.0 +mops install ic-certification@0.1.3 ``` ### Creating an HTTP request diff --git a/docs/tutorials/developer-journey/level-3/3.4-intro-to-agents.mdx b/docs/tutorials/developer-journey/level-3/3.4-intro-to-agents.mdx index aab45fa174..04c77571b6 100644 --- a/docs/tutorials/developer-journey/level-3/3.4-intro-to-agents.mdx +++ b/docs/tutorials/developer-journey/level-3/3.4-intro-to-agents.mdx @@ -104,9 +104,42 @@ cd examples/motoko/random_maze npm install ``` +### Creating the JavaScript agent + +View the content of the `src/declarations/random_maze/index.js` file: + +```javascript file=../../../references/samples/motoko/random_maze/src/declarations/random_maze/index.js +``` + +In this code, the constructor first creates an `HTTPAgent` which wraps the JavaScript API, then uses it to encode calls through the public API. If the deployment is on the mainnet, the root key of the replica is fetched. Then, an actor is created using the automatically generated Candid interface for the canister and is passed the canister ID and the `HTTPAgent`. + +:::caution +This example uses `fetchRootKey`. It is not recommended that dapps deployed on the mainnet call this function from the [ICP JavaScript agent](https://www.npmjs.com/package/@dfinity/agent), since using `fetchRootKey` on the mainnet poses severe security concerns for the dapp that's making the call. It is recommended to put it behind a condition so that it only runs locally. + +This API call will fetch a root key for verification of update calls from a single replica, so it’s possible for that replica to respond with a malicious key. A verified mainnet root key is already embedded into the [ICP JavaScript agent](https://www.npmjs.com/package/@dfinity/agent), so this only needs to be called on your local replica, which will have a different key from mainnet that the ICP JavaScript agent does not know ahead of time. +::: + +Now our actor is set up to call all of the defined service methods; in this instance, there is just the `generate` method. + +Let's deploy our canisters with the command: + +```bash +dfx deploy +``` + +Then, open the URL for the `random_maze_assets` canister in your web browser: + +``` +URLs: + Frontend canister via browser + random_maze_assets: http://avqkn-guaaa-aaaaa-qaaea-cai.localhost:4943/ + Backend canister via Candid interface: + random_maze: http://asrmz-lmaaa-aaaaa-qaaeq-cai.localhost:4943/?id=by6od-j4aaa-aaaaa-qaadq-cai +``` + ### Generating Candid declarations -For this example, you'll use an example project that takes a variable size input and generates a random maze using that size. For example, if `6` is entered, a 6x6 maze will be generated. Recall that Motoko projects have the ability to autogenerate the project's Candid files. Let's start with generating those Candid files with the command: +This example's code takes a variable size input and generates a random maze using that size. For example, if `6` is entered, a 6x6 maze will be generated. Generate the project's Candid files with the command: ```bash dfx generate @@ -164,81 +197,6 @@ In this file, the `idlFactory` function is defined. This handles the structuring In this example, the `idlFactory` represents a service with a `generate` method using the same two arguments you defined before (type `Nat` and type `Text`), though it includes a third argument of an empty array, which represents additional annotations that the function may be tagged with, such as 'query'. -### Creating the JavaScript agent - -Now, let's put all of these different pieces together in the `src/declarations/random_maze/index.js` file: - -```javascript title="src/declarations/random_maze/index.js" -import { Actor, HttpAgent } from "@dfinity/agent"; - -// Imports and re-exports candid interface -import { idlFactory } from "./random_maze.did.js"; -export { idlFactory } from "./random_maze.did.js"; - -/* CANISTER_ID is replaced by webpack based on node environment - * Note: canister environment variable will be standardized as - * process.env.CANISTER_ID_ - * beginning in dfx 0.15.0 - */ -export const canisterId = - process.env.CANISTER_ID_RANDOM_MAZE || - process.env.RANDOM_MAZE_CANISTER_ID; - -export const createActor = (canisterId, options = {}) => { - const agent = options.agent || new HttpAgent({ ...options.agentOptions }); - - if (options.agent && options.agentOptions) { - console.warn( - "Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent." - ); - } - - // Fetch root key for certificate validation during development - if (process.env.DFX_NETWORK !== "ic") { - agent.fetchRootKey().catch((err) => { - console.warn( - "Unable to fetch root key. Check to ensure that your local replica is running" - ); - console.error(err); - }); - } - - // Creates an actor with using the candid interface and the HttpAgent - return Actor.createActor(idlFactory, { - agent, - canisterId, - ...options.actorOptions, - }); -}; - -export const random_maze = createActor(canisterId); -``` - -In this code, the constructor first creates an `HTTPAgent` which wraps the JavaScript API, then uses it to encode calls through the public API. If the deployment is on the mainnet, the root key of the replica is fetched. Then, an actor is created using the automatically generated Candid interface for the canister and is passed the canister ID and the `HTTPAgent`. - -:::caution -This example uses `fetchRootKey`. It is not recommended that dapps deployed on the mainnet call this function from the [ICP JavaScript agent](https://www.npmjs.com/package/@dfinity/agent), since using `fetchRootKey` on the mainnet poses severe security concerns for the dapp that's making the call. It is recommended to put it behind a condition so that it only runs locally. - -This API call will fetch a root key for verification of update calls from a single replica, so it’s possible for that replica to respond with a malicious key. A verified mainnet root key is already embedded into the [ICP JavaScript agent](https://www.npmjs.com/package/@dfinity/agent), so this only needs to be called on your local replica, which will have a different key from mainnet that the ICP JavaScript agent does not know ahead of time. -::: - -Now our actor is set up to call all of the defined service methods; in this instance, there is just the `generate` method. - -Let's deploy our canisters with the command: - -```bash -dfx deploy -``` - -Then, open the URL for the `random_maze_assets` canister in your web browser: - -``` -URLs: - Frontend canister via browser - random_maze_assets: http://avqkn-guaaa-aaaaa-qaaea-cai.localhost:4943/ - Backend canister via Candid interface: - random_maze: http://asrmz-lmaaa-aaaaa-qaaeq-cai.localhost:4943/?id=by6od-j4aaa-aaaaa-qaadq-cai -``` ### Using the agent diff --git a/docs/tutorials/developer-journey/level-3/3.5-identities-and-auth.mdx b/docs/tutorials/developer-journey/level-3/3.5-identities-and-auth.mdx index 6fd7e2758b..176c6455c5 100644 --- a/docs/tutorials/developer-journey/level-3/3.5-identities-and-auth.mdx +++ b/docs/tutorials/developer-journey/level-3/3.5-identities-and-auth.mdx @@ -119,14 +119,7 @@ npm install To start, let's take a look at the backend canister, which uses a simple 'Who am I?' service in the `src/whoami/main.mo` file: -```motoko title="src/whoami/main.mo" -import Principal "mo:base/Principal"; - -actor { - public shared (msg) func whoami() : async Principal { - msg.caller - }; -}; +```motoko no-repl file=../../../references/samples/archive/motoko/auth_client_demo/src/whoami/main.mo ``` In this code, the actor has one method, `whoami`, which responds with the message's caller. You'll use this to test if you make a request from the frontend using an `AnonymousIdentity` or an authenticated Internet Identity. @@ -135,130 +128,35 @@ In this code, the actor has one method, `whoami`, which responds with the messag Next, let's take a look at the assets for the frontend of the application. This demo project comes with a few different options for the frontend, such as React and Vue, but for this guide you'll stick with using the vanilla JavaScript option. If you take a look at the `src/auth_client_demo_assets/vanilla/index.ts` file, you can see that first, you import the `AuthClient` from `@dfinity/auth-client`: -```typescript title="src/auth_client_demo_assets/vanilla/index.ts" -import { AuthClient } from "@dfinity/auth-client"; -import { handleAuthenticated, renderIndex } from "./views"; +```typescript file=../../../references/samples/archive/motoko/auth_client_demo/src/auth_client_demo_assets/vanilla/index.ts#L1-L3 ``` + Then, you define some variables to define a day in nanoseconds: -```typescript title="src/auth_client_demo_assets/vanilla/index.ts" -// One day in nanoseconds -const days = BigInt(1); -const hours = BigInt(24); -const nanoseconds = BigInt(3600000000000); +```typescript file=../../../references/samples/archive/motoko/auth_client_demo/src/auth_client_demo_assets/vanilla/index.ts#L4-L7 ``` Next, you'll set some default options, including the `loginOptions`, which will allow the `identityProvider` to be either the II mainnet canister if you are using the local environmental variable `DFX_NETWORK` set to `ic`, or the locally deployed II canister. Your local replica will not accept signatures from the mainnet canister. -```typescript title="src/auth_client_demo_assets/vanilla/index.ts" -export const defaultOptions = { - createOptions: { - idleOptions: { - // Set to true if you do not want idle functionality - disableIdle: true, - }, - }, - loginOptions: { - identityProvider: - process.env.DFX_NETWORK === "ic" - ? "https://identity.ic0.app/#authorize" - : `http://localhost:4943?canisterId=rdmx6-jaaaa-aaaaa-aaadq-cai#authorize`, - // Maximum authorization expiration is 8 days - maxTimeToLive: days * hours * nanoseconds, - }, -}; +```typescript file=../../../references/samples/archive/motoko/auth_client_demo/src/auth_client_demo_assets/vanilla/index.ts#L9-L23 ``` -Depending on your web browser, you may need to change the `http://localhost:4943?canisterId=rdmx6-jaaaa-aaaaa-aaadq-cai#authorize` value: - -- Chrome, Firefox: `http://.localhost:4943#authorize` - -- Safari: `http://localhost:4943?canisterId=#authorize` - Now, let's initialize the AuthClient. Then, you check to see if the user has previously logged in. If so, their previous identity is verified: -```typescript title="src/auth_client_demo_assets/vanilla/index.ts" -const init = async () => { - const authClient = await AuthClient.create(defaultOptions.createOptions); - - if (await authClient.isAuthenticated()) { - handleAuthenticated(authClient); - } - renderIndex(); - setupToast(); -}; +```typescript file=../../../references/samples/archive/motoko/auth_client_demo/src/auth_client_demo_assets/vanilla/index.ts#L43-L51 ``` Lastly, you define a function that listens for the button on the frontend to be clicked and initialize the client: -```typescript title="src/auth_client_demo_assets/vanilla/index.ts" -async function setupToast() { - const status = document.getElementById("status"); - const closeButton = status?.querySelector("button"); - closeButton?.addEventListener("click", () => { - status?.classList.add("hidden"); - }); -} - - -init(); +```typescript file=../../../references/samples/archive/motoko/auth_client_demo/src/auth_client_demo_assets/vanilla/index.ts#L53-L61 ``` ### Pulling the Internet Identity canister Now, if you noticed you didn't create a canister for the Internet Identity service. That's because, if you take a look at this project's `dfx.json` file, you **pull** the Internet Identity canister from the mainnet. This is an example of using the `dfx deps` functionality, which you covered in [2.3: Using third-party canisters](/docs/current/tutorials/developer-journey/level-2/2.3-third-party-canisters). -```json title="dfx.json" -{ - "canisters": { - "whoami": { - "main": "src/whoami/main.mo", - "type": "motoko", - "declarations": { - "node_compatibility": true - }, - "pullable": { - "dependencies": [], - "wasm_url": "https://github.com/krpeacock/auth-client-demo/releases/latest/download/whoami.wasm", - "wasm_hash": "a5af74d01aec228c5a717dfb43f773917e1a9138e512431aafcd225ad0001a8b", - "init_guide": "null" - } - }, - "internet-identity": { - "type": "pull", - "id": "rdmx6-jaaaa-aaaaa-aaadq-cai" - }, - "auth_client_demo_assets": { - "dependencies": [ - "whoami", - "internet-identity" - ], - "source": [ - "src/auth_client_demo_assets/vanilla" - ], - "type": "assets" - }, - "svelte": { - "dependencies": [ - "whoami", - "internet-identity" - ], - "source": [ - "src/auth_client_demo_assets/svelte/build" - ], - "type": "assets" - } - }, - "defaults": { - "build": { - "args": "", - "packtool": "" - } - }, - "output_env_file": ".env", - "version": 2 -} +```json file=../../../references/samples/archive/motoko/auth_client_demo/dfx.json ``` Now let's deploy this project locally. First, pull the II canister using `dfx deps`, then deploy the project: @@ -274,40 +172,9 @@ $ Fetching dependencies of canister rdmx6-jaaaa-aaaaa-aaadq-cai... Found 1 dependencies: rdmx6-jaaaa-aaaaa-aaadq-cai Pulling canister rdmx6-jaaaa-aaaaa-aaadq-cai... -WARN: Canister rdmx6-jaaaa-aaaaa-aaadq-cai has different hash between on chain and download. -on chain: a3b50a3b35c487b9e5e9cd0174845d83fe15d5bb5d78e507647ed2272777568f -download: 197d0423178a6ae2785c924b962648e2b47e6ccb2a25e98d9220d9f3ce5eebf7 -``` - -Open the `deps/pull.json` file and modify the `wasm_hash` value with the `download` value from the previous output: - -```json title="deps/pull.json" -{ - "canisters": { - "rdmx6-jaaaa-aaaaa-aaadq-cai": { - "name": "ii", - "wasm_hash": "197d0423178a6ae2785c924b962648e2b47e6ccb2a25e98d9220d9f3ce5eebf7", - "init_guide": "Use '(null)' for sensible defaults. See the candid interface for more details.", - "init_arg": null, - "candid_args": "(opt InternetIdentityInit)", - "gzip": true - } - } -} -``` - -Then, you need to initialize the canister. By running the `dfx deps init` command, it'll show that our II canister requires an init argument: - -```bash -dfx deps init -``` - -```bash -WARN: The following canister(s) require an init argument. Please run `dfx deps init ` to set them individually: -rdmx6-jaaaa-aaaaa-aaadq-cai (internet_identity) ``` -This output shows you that the Internet Identity canister requires an init argument, but doesn't include what that init argument is. For more information, run the command `dfx deps init rdmx6-jaaaa-aaaaa-aaadq-cai`, which will provide an error message that includes more information: +The Internet Identity canister requires an init argument. For more information, run the command `dfx deps init rdmx6-jaaaa-aaaaa-aaadq-cai`, which will provide an error message that includes more information: ```bash Error: Canister rdmx6-jaaaa-aaaaa-aaadq-cai (internet_identity) requires an init argument. The following info might be helpful: diff --git a/submodules/quill b/submodules/quill index a2309c2f2e..01c8e8af38 160000 --- a/submodules/quill +++ b/submodules/quill @@ -1 +1 @@ -Subproject commit a2309c2f2e76d11bca8db5b1c85eeaa66aa8b0ec +Subproject commit 01c8e8af38b18d3103c0485850309930e14c380a diff --git a/submodules/samples b/submodules/samples index 0c50a6c661..59b4cff230 160000 --- a/submodules/samples +++ b/submodules/samples @@ -1 +1 @@ -Subproject commit 0c50a6c6612c1b90d27fb4e8d0cd9950067e6e9a +Subproject commit 59b4cff230689e69d332450f38284b1ba3df0563