From ee2ffb67fa776972472220dcc59af97a6d22f31a Mon Sep 17 00:00:00 2001 From: Mihail Jianu Date: Thu, 28 Aug 2025 12:21:58 +0200 Subject: [PATCH 1/2] http outcalls --- .../.devcontainer/devcontainer.json | 20 + rust/http_outcalls/BUILD.md | 113 +++ rust/http_outcalls/Cargo.lock | 676 ++++++++++++++++++ rust/http_outcalls/Cargo.toml | 3 + rust/http_outcalls/README.md | 64 ++ rust/http_outcalls/backend/Cargo.toml | 14 + rust/http_outcalls/backend/backend.did | 5 + rust/http_outcalls/backend/lib.rs | 93 +++ rust/http_outcalls/dfx.json | 21 + rust/http_outcalls/rust-toolchain.toml | 2 + 10 files changed, 1011 insertions(+) create mode 100644 rust/http_outcalls/.devcontainer/devcontainer.json create mode 100644 rust/http_outcalls/BUILD.md create mode 100644 rust/http_outcalls/Cargo.lock create mode 100644 rust/http_outcalls/Cargo.toml create mode 100644 rust/http_outcalls/README.md create mode 100644 rust/http_outcalls/backend/Cargo.toml create mode 100644 rust/http_outcalls/backend/backend.did create mode 100644 rust/http_outcalls/backend/lib.rs create mode 100644 rust/http_outcalls/dfx.json create mode 100644 rust/http_outcalls/rust-toolchain.toml diff --git a/rust/http_outcalls/.devcontainer/devcontainer.json b/rust/http_outcalls/.devcontainer/devcontainer.json new file mode 100644 index 000000000..94ff98193 --- /dev/null +++ b/rust/http_outcalls/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "ICP Dev Environment", + "image": "ghcr.io/dfinity/icp-dev-env-slim:21", + "forwardPorts": [4943, 5173], + "portsAttributes": { + "4943": { + "label": "dfx", + "onAutoForward": "ignore" + }, + "5173": { + "label": "vite", + "onAutoForward": "openBrowser" + } + }, + "customizations": { + "vscode": { + "extensions": ["dfinity-foundation.vscode-motoko"] + } + } +} diff --git a/rust/http_outcalls/BUILD.md b/rust/http_outcalls/BUILD.md new file mode 100644 index 000000000..24cfcb754 --- /dev/null +++ b/rust/http_outcalls/BUILD.md @@ -0,0 +1,113 @@ +# Continue building locally + +Projects deployed through ICP Ninja are temporary; they will only be live for 20 minutes before they are removed. The command-line tool `dfx` can be used to continue building your ICP Ninja project locally and deploy it to the mainnet. + +To migrate your ICP Ninja project off of the web browser and develop it locally, follow these steps. + +### 1. Install developer tools. + +You can install the developer tools natively or use Dev Containers. + +#### Option 1: Natively install developer tools + +> Installing `dfx` natively is currently only supported on macOS and Linux systems. On Windows, it is recommended to use the Dev Containers option. + +1. Install `dfx` with the following command: + +``` + +sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)" + +``` + +> On Apple Silicon (e.g., Apple M1 chip), make sure you have Rosetta installed (`softwareupdate --install-rosetta`). + +2. [Install NodeJS](https://nodejs.org/en/download/package-manager). + +3. For Rust projects, you will also need to: + +- Install [Rust](https://doc.rust-lang.org/cargo/getting-started/installation.html#install-rust-and-cargo): `curl https://sh.rustup.rs -sSf | sh` + +- Install [candid-extractor](https://crates.io/crates/candid-extractor): `cargo install candid-extractor` + +4. For Motoko projects, you will also need to: + +- Install the Motoko package manager [Mops](https://docs.mops.one/quick-start#2-install-mops-cli): `npm i -g ic-mops` + +Lastly, navigate into your project's directory that you downloaded from ICP Ninja. + +#### Option 2: Dev Containers + +Continue building your projects locally by installing the [Dev Container extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) for VS Code and [Docker](https://docs.docker.com/engine/install/). + +Make sure Docker is running, then navigate into your project's directory that you downloaded from ICP Ninja and start the Dev Container by selecting `Dev-Containers: Reopen in Container` in VS Code's command palette (F1 or Ctrl/Cmd+Shift+P). + +> Note that local development ports (e.g. the ports used by `dfx` or `vite`) are forwarded from the Dev Container to your local machine. In the VS code terminal, use Ctrl/Cmd+Click on the displayed local URLs to open them in your browser. To view the current port mappings, click the "Ports" tab in the VS Code terminal window. + +### 2. Start the local development environment. + +``` +dfx start --background +``` + +### 3. Create a local developer identity. + +To manage your project's canisters, it is recommended that you create a local [developer identity](https://internetcomputer.org/docs/building-apps/getting-started/identities) rather than use the `dfx` default identity that is not stored securely. + +To create a new identity, run the commands: + +``` + +dfx identity new IDENTITY_NAME + +dfx identity use IDENTITY_NAME + +``` + +Replace `IDENTITY_NAME` with your preferred identity name. The first command `dfx start --background` starts the local `dfx` processes, then `dfx identity new` will create a new identity and return your identity's seed phase. Be sure to save this in a safe, secure location. + +The third command `dfx identity use` will tell `dfx` to use your new identity as the active identity. Any canister smart contracts created after running `dfx identity use` will be owned and controlled by the active identity. + +Your identity will have a principal ID associated with it. Principal IDs are used to identify different entities on ICP, such as users and canisters. + +[Learn more about ICP developer identities](https://internetcomputer.org/docs/building-apps/getting-started/identities). + +### 4. Deploy the project locally. + +Deploy your project to your local developer environment with: + +``` +npm install +dfx deploy + +``` + +Your project will be hosted on your local machine. The local canister URLs for your project will be shown in the terminal window as output of the `dfx deploy` command. You can open these URLs in your web browser to view the local instance of your project. + +### 5. Obtain cycles. + +To deploy your project to the mainnet for long-term public accessibility, first you will need [cycles](https://internetcomputer.org/docs/building-apps/getting-started/tokens-and-cycles). Cycles are used to pay for the resources your project uses on the mainnet, such as storage and compute. + +> This cost model is known as ICP's [reverse gas model](https://internetcomputer.org/docs/building-apps/essentials/gas-cost), where developers pay for their project's gas fees rather than users pay for their own gas fees. This model provides an enhanced end user experience since they do not need to hold tokens or sign transactions when using a dapp deployed on ICP. + +> Learn how much a project may cost by using the [pricing calculator](https://internetcomputer.org/docs/building-apps/essentials/cost-estimations-and-examples). + +Cycles can be obtained through [converting ICP tokens into cycles using `dfx`](https://internetcomputer.org/docs/building-apps/developer-tools/dfx/dfx-cycles#dfx-cycles-convert). + +### 6. Deploy to the mainnet. + +Once you have cycles, run the command: + +``` + +dfx deploy --network ic + +``` + +After your project has been deployed to the mainnet, it will continuously require cycles to pay for the resources it uses. You will need to [top up](https://internetcomputer.org/docs/building-apps/canister-management/topping-up) your project's canisters or set up automatic cycles management through a service such as [CycleOps](https://cycleops.dev/). + +> If your project's canisters run out of cycles, they will be removed from the network. + +## Additional examples + +Additional code examples and sample applications can be found in the [DFINITY examples repo](https://github.com/dfinity/examples). diff --git a/rust/http_outcalls/Cargo.lock b/rust/http_outcalls/Cargo.lock new file mode 100644 index 000000000..481dc2238 --- /dev/null +++ b/rust/http_outcalls/Cargo.lock @@ -0,0 +1,676 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backend" +version = "0.1.0" +dependencies = [ + "candid", + "ic-cdk", +] + +[[package]] +name = "binread" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" +dependencies = [ + "binread_derive", + "lazy_static", + "rustversion", +] + +[[package]] +name = "binread_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "candid" +version = "0.10.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaac522d18020d5fbc8320ecb12a9b13b2137ae31133da2d42fa256a825507c4" +dependencies = [ + "anyhow", + "binread", + "byteorder", + "candid_derive", + "hex", + "ic_principal", + "leb128", + "num-bigint", + "num-traits", + "paste", + "pretty", + "serde", + "serde_bytes", + "stacker", + "thiserror 1.0.69", +] + +[[package]] +name = "candid_derive" +version = "0.10.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a1b4fddbd462182050989068d53604a91a3d0f117c3c8316c6818023df00add" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "cc" +version = "1.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "ic-cdk" +version = "0.19.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae467e7e2a17920af8dce2eb5bae39a3257a3e9bc3c2bdeab1d2d413022748ce" +dependencies = [ + "candid", + "ic-cdk-executor", + "ic-cdk-macros", + "ic-error-types", + "ic-management-canister-types", + "ic0", + "serde", + "serde_bytes", + "slotmap", + "thiserror 2.0.16", +] + +[[package]] +name = "ic-cdk-executor" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f4ee8930fd2e491177e2eb7fff53ee1c407c13b9582bdc7d6920cf83109a2d" +dependencies = [ + "ic0", + "slotmap", +] + +[[package]] +name = "ic-cdk-macros" +version = "0.19.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d541e2bc038034bd53a22ac494397df5e16fe1e01cbcc7a9cdb30560e859b2" +dependencies = [ + "candid", + "darling", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "ic-error-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbeeb3d91aa179d6496d7293becdacedfc413c825cac79fd54ea1906f003ee55" +dependencies = [ + "serde", + "strum", + "strum_macros", +] + +[[package]] +name = "ic-management-canister-types" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300adbb554ca7d6003f6080b978f5f53730d93cc9d6c8223199cbcfd166b7b42" +dependencies = [ + "candid", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic0" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8877193e1921b5fd16accb0305eb46016868cd1935b05c05eca0ec007b943272" + +[[package]] +name = "ic_principal" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" +dependencies = [ + "crc32fast", + "data-encoding", + "serde", + "sha2", + "thiserror 1.0.69", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pretty" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac98773b7109bc75f475ab5a134c9b64b87e59d776d31098d8f346922396a477" +dependencies = [ + "arrayvec", + "typed-arena", + "unicode-width", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +dependencies = [ + "cc", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "stacker" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.106", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/rust/http_outcalls/Cargo.toml b/rust/http_outcalls/Cargo.toml new file mode 100644 index 000000000..d1e49e317 --- /dev/null +++ b/rust/http_outcalls/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["backend"] +resolver = "2" diff --git a/rust/http_outcalls/README.md b/rust/http_outcalls/README.md new file mode 100644 index 000000000..1338bdeac --- /dev/null +++ b/rust/http_outcalls/README.md @@ -0,0 +1,64 @@ +# HTTP Outcalls in Rust + +This project demonstrates how to use the HTTP outcalls feature on the Internet Computer. It contains a single Rust canister with two methods that show how to make both replicated and non-replicated HTTP GET requests to external services. + +## Prerequisites + +- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/getting-started/install). +- [x] Clone this project's repository. + +## Step 1: Deploy the canister + +Navigate into the project's root directory and run the following commands to start a local replica and deploy the canister: + +```shell +dfx start --background --clean && dfx deploy +``` + +This will deploy a canister named `http_outcalls`. + +## Step 2: Call the canister methods + +Once deployed, you can test the canister's functions from the command line. + +### Replicated Call with a Transform (`get_typicode_post`) + +This method makes a call that goes through consensus to fetch a blog post. It uses a transform function to ensure the response is deterministic. + +```shell +# Call the method with a post ID (e.g., 1), which calls out to https://jsonplaceholder.typicode.com/posts/1 +dfx canister call http_outcalls get_typicode_post '(1: nat64)' +``` + +**Expected Output:** +You will see the JSON body of the post, indicating a successful call. +``` +( + Ok "{ + \"userId\": 1, + \"id\": 1, + \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\", + \"body\": \"quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto\" +}", +) +``` + +### Non-Replicated Call (`get_bitcoin_price`) + +This method makes a non-replicated call to fetch volatile data—in this case, the price of Bitcoin. + +```shell +dfx canister call http_outcalls get_bitcoin_price +``` + +**Expected Output:** +You will see a JSON response with the current price of Bitcoin in USD. The price will vary each time you call it. +``` +( + Ok "{\"bitcoin\":{\"usd\"::112973}}", +) +``` + +## Security considerations and best practices + +If you base your application on this example, it is recommended that you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/building-apps/security/overview) for developing on ICP. This example may not implement all the best practices. \ No newline at end of file diff --git a/rust/http_outcalls/backend/Cargo.toml b/rust/http_outcalls/backend/Cargo.toml new file mode 100644 index 000000000..f2a398d9f --- /dev/null +++ b/rust/http_outcalls/backend/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "backend" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] +path = "lib.rs" + +[dependencies] +candid = "0.10.10" +ic-cdk = "0.19.0-beta.1" diff --git a/rust/http_outcalls/backend/backend.did b/rust/http_outcalls/backend/backend.did new file mode 100644 index 000000000..5782b8635 --- /dev/null +++ b/rust/http_outcalls/backend/backend.did @@ -0,0 +1,5 @@ +type Result = variant { Ok : text; Err : text }; +service : { + get_bitcoin_price : () -> (Result); + get_typicode_post : (nat64) -> (Result); +} diff --git a/rust/http_outcalls/backend/lib.rs b/rust/http_outcalls/backend/lib.rs new file mode 100644 index 000000000..1c8dd64bf --- /dev/null +++ b/rust/http_outcalls/backend/lib.rs @@ -0,0 +1,93 @@ +use ic_cdk::{api::canister_self, management_canister::{http_request as canister_http_outcall, HttpMethod, HttpRequestArgs, HttpRequestResult, TransformArgs, TransformContext, TransformFunc}}; + +#[ic_cdk::update] +// A function to get any typicode post, with threshold consensus +async fn get_typicode_post(id: u64) -> Result { + + // A transform function is a canister query method that processes the raw HTTP response + // before it's returned to the main async function. + // + // Its primary purpose for replicated calls is to ensure **determinism**. Every replica + // in the subnet must agree on the exact same final response to reach consensus. + // HTTP responses often contain non-deterministic data (e.g., 'Date' or 'Server' + // headers can vary slightly between provider nodes). + // + // This function strips that variable data, ensuring the response is identical + // across all replicas, allowing consensus to be achieved. + // + // In our case, when calling out to typicode, simply dropping all headers is enough to ensure determinism + let transform = Some(TransformContext { + function: TransformFunc(candid::Func { + principal: canister_self(), + method: "drop_headers".to_string(), + }), + context: vec![], + }); + + // Create actual HTTP request: + let arg = HttpRequestArgs { + url: format!("https://jsonplaceholder.typicode.com/posts/{}", id), + // The maximum size (in bytes) of the response before, or after transformation. + // If "None" is used, it defaults to 2MB. + max_response_bytes: Some(10000), + method: HttpMethod::GET, + headers: vec![], + body: None, + transform, + // As (after dropping the headers) the typicode response is always the same for a given ID, we want this request to be replicated (and obtain threshold consensus) + is_replicated: Some(true), + }; + + // Make the actual outcall. + canister_http_outcall(&arg) + .await + .map_err(|e| e.to_string()) + .map(|response| { + String::from_utf8(response.body).unwrap_or_else(|_| "Invalid UTF-8".to_string()) + }) +} + +// Transform query is only used by the http_outcalls, so it doesn't need to be part of the canister's interface +#[ic_cdk::query(hidden=true)] +fn drop_headers( + args: TransformArgs, +) -> HttpRequestResult { + // Drop all headers, keep everything else the same + HttpRequestResult { + status: args.response.status, + body: args.response.body, + headers: vec![], + } +} + +#[ic_cdk::update] +// A function to generate retrieve the price of Bitcoin in USD. +// Importantly, such a request is fast moving / non deterministic, meaning a replicated outcall might not reach consensus. +// We therefore showcase how to make a non-replicated outcalls. +async fn get_bitcoin_price() -> Result { + + // Create actual HTTP request: + let arg = HttpRequestArgs { + url: "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd".to_string(), + // The maximum size (in bytes) of the response before, or after transformation. + // If "None" is used, it defaults to 2MB. + max_response_bytes: Some(10000), + method: HttpMethod::GET, + headers: vec![], + body: None, + // No need to transform the response any longer, as it is not replicated + transform: None, + // The request is not replicated. This means that a single node will attempt the outcall. This is ideal for fast moving data (such as eg crypto prices), non idempotent operations and so on. + is_replicated: Some(false), + }; + + // Make the actual outcall. + canister_http_outcall(&arg) + .await + .map_err(|e| e.to_string()) + .map(|response| { + String::from_utf8(response.body).unwrap_or_else(|_| "Invalid UTF-8".to_string()) + }) +} + +ic_cdk::export_candid!(); diff --git a/rust/http_outcalls/dfx.json b/rust/http_outcalls/dfx.json new file mode 100644 index 000000000..2f8287990 --- /dev/null +++ b/rust/http_outcalls/dfx.json @@ -0,0 +1,21 @@ +{ + "canisters": { + "http_outcalls": { + "candid": "backend/backend.did", + "type": "custom", + "shrink": true, + "gzip": true, + "wasm": "target/wasm32-unknown-unknown/release/backend.wasm", + "build": [ + "cargo build --target wasm32-unknown-unknown --release -p backend", + "candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did" + ], + "metadata": [ + { + "name": "candid:service" + } + ] + } + }, + "output_env_file": ".env" +} \ No newline at end of file diff --git a/rust/http_outcalls/rust-toolchain.toml b/rust/http_outcalls/rust-toolchain.toml new file mode 100644 index 000000000..990104f05 --- /dev/null +++ b/rust/http_outcalls/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +targets = ["wasm32-unknown-unknown"] From 8fc8073c7151f32d22e606c5361f08ef6f490b62 Mon Sep 17 00:00:00 2001 From: Mihail Jianu Date: Thu, 28 Aug 2025 16:14:42 +0200 Subject: [PATCH 2/2] comments --- rust/http_outcalls/README.md | 2 ++ rust/http_outcalls/backend/lib.rs | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/rust/http_outcalls/README.md b/rust/http_outcalls/README.md index 1338bdeac..b18becee4 100644 --- a/rust/http_outcalls/README.md +++ b/rust/http_outcalls/README.md @@ -47,6 +47,8 @@ You will see the JSON body of the post, indicating a successful call. This method makes a non-replicated call to fetch volatile data—in this case, the price of Bitcoin. +⚠️ **Important Security Note:** This call is *non-replicated*, meaning it is performed by a single replica and bypasses consensus. While this is necessary for volatile data, it means a single malicious replica could theoretically return a false or manipulated response. For production systems, data from such calls should not be trusted for high-value transactions without further verification inside the canister. + ```shell dfx canister call http_outcalls get_bitcoin_price ``` diff --git a/rust/http_outcalls/backend/lib.rs b/rust/http_outcalls/backend/lib.rs index 1c8dd64bf..2ed8a3860 100644 --- a/rust/http_outcalls/backend/lib.rs +++ b/rust/http_outcalls/backend/lib.rs @@ -77,7 +77,17 @@ async fn get_bitcoin_price() -> Result { body: None, // No need to transform the response any longer, as it is not replicated transform: None, - // The request is not replicated. This means that a single node will attempt the outcall. This is ideal for fast moving data (such as eg crypto prices), non idempotent operations and so on. + // The request is not replicated. A single, randomly chosen replica node will make the outcall. + // This is ideal for fetching fast-moving data (like crypto prices) where consensus + // among replicas would likely fail due to small differences in timing. + // + // SECURITY WARNING: Because the response is not verified by consensus, a single malicious + // replica could tamper with the response. Do not trust the result for critical + // decisions without additional verification within your canister. + // + // Common mitigation strategies include: + // 1. Making multiple non-replicated calls to different, trusted APIs and validating their responses. + // 2. Using data sources that provide out-of-band verification, such as cryptographic signatures. is_replicated: Some(false), };