From ae82ae13d341168c886ee9f1d39759fa65be034d Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 10 Jul 2025 13:41:48 +0200 Subject: [PATCH 1/2] feat: shorten routes example Signed-off-by: David Dal Busco --- ...generating-assets-with-hooks.mdx => generating-assets.mdx} | 0 .../rust/{mutating-docs-with-hooks.mdx => mutating-docs.mdx} | 0 sidebars.ts | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename docs/examples/functions/rust/{generating-assets-with-hooks.mdx => generating-assets.mdx} (100%) rename docs/examples/functions/rust/{mutating-docs-with-hooks.mdx => mutating-docs.mdx} (100%) diff --git a/docs/examples/functions/rust/generating-assets-with-hooks.mdx b/docs/examples/functions/rust/generating-assets.mdx similarity index 100% rename from docs/examples/functions/rust/generating-assets-with-hooks.mdx rename to docs/examples/functions/rust/generating-assets.mdx diff --git a/docs/examples/functions/rust/mutating-docs-with-hooks.mdx b/docs/examples/functions/rust/mutating-docs.mdx similarity index 100% rename from docs/examples/functions/rust/mutating-docs-with-hooks.mdx rename to docs/examples/functions/rust/mutating-docs.mdx diff --git a/sidebars.ts b/sidebars.ts index 354eb532..eca51cca 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -121,8 +121,8 @@ const sidebars: SidebarsConfig = { }, items: [ "examples/functions/rust/assertion", - "examples/functions/rust/mutating-docs-with-hooks", - "examples/functions/rust/generating-assets-with-hooks", + "examples/functions/rust/mutating-docs", + "examples/functions/rust/generating-assets", "examples/functions/rust/canister-calls" ] } From 95e3f0f68452fdc67531dd3b9343a7282f706d6e Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:43:28 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=84=20Update=20LLMs.txt=20snapshot?= =?UTF-8?q?=20for=20PR=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .llms-snapshots/llms-full.txt | 192 ++++++++++++++++++++++++++++++++++ .llms-snapshots/llms.txt | 5 +- 2 files changed, 195 insertions(+), 2 deletions(-) diff --git a/.llms-snapshots/llms-full.txt b/.llms-snapshots/llms-full.txt index 2ac46dc3..3336ab4c 100644 --- a/.llms-snapshots/llms-full.txt +++ b/.llms-snapshots/llms-full.txt @@ -3289,6 +3289,192 @@ These crates are used to build and extend serverless functions in Rust with Juno * [junobuild-shared](https://docs.rs/junobuild-shared): Shared types and helpers for Juno projects. Used by all containers including the Console. * [junobuild-storage](https://docs.rs/junobuild-storage): Storage helpers for working with assets and HTTP headers in Juno. +# Making Canister Calls in Rust Serverless Functions + +This example demonstrates how to use **Rust serverless functions** to perform canister calls (such as `transfer_from` on the ICP ledger) in response to Datastore events in your Juno **Satellite**. + +When a document is added to the `request` collection, a serverless function is triggered to: + +* Check if the user has enough ICP in their wallet +* Transfer ICP from the user's wallet to the Satellite using the ICRC ledger's `transfer_from` method +* Mark the request as `processed` if the transfer succeeds + +This pattern is useful for building workflows that require on-chain asset transfers or other canister calls in response to user actions. + +You can browse the source code here: [github.com/junobuild/examples/tree/main/rust/calls](https://github.com/junobuild/examples/tree/main/rust/calls) + +--- + +## Folder Structure + +``` +rust/calls/├── src/│ ├── satellite/ # Rust Satellite serverless function│ │ ├── src/│ │ │ ├── lib.rs # Main Rust logic for Satellite│ │ │ ├── services.rs # Helper logic for balance, transfer, status│ │ │ ├── types.rs # Data model for requests│ │ │ ├── ledger_icrc.rs # Ledger helper functions│ │ │ └── ...│ │ ├── satellite.did # Candid interface definition│ │ └── Cargo.toml # Rust package config│ ├── declarations/ # TypeScript declarations for Satellite│ ├── components/ # React frontend components│ ├── services/ # Frontend service logic│ ├── types/ # Frontend type definitions│ ├── main.tsx # Frontend entry│ └── ...├── juno.config.ts # Juno Satellite configuration├── package.json # Frontend dependencies└── +``` + +--- + +## Key Features + +* **Serverless Canister Calls**: Demonstrates how to perform ICRC ledger calls (e.g., `transfer_from`) from Rust serverless functions. +* **Atomic Request Processing**: Ensures that request status is only updated if the transfer succeeds. +* **Wallet Balance Checks**: Fails early if the user does not have enough ICP. +* **Minimal React UI**: A simple React frontend is included to test and demonstrate the logic. + +--- + +## Main Backend Components + +* **src/satellite/src/lib.rs**: The entry point for the Satellite serverless function. Triggers the canister call and updates request status on document set. +* **src/satellite/src/services.rs**: Helper logic for checking wallet balance, performing the transfer, and updating request status. +* **src/satellite/src/types.rs**: Data model for requests and status. +* **src/satellite/Cargo.toml**: Rust package configuration for the Satellite function. + +--- + +## Example: Canister Call on Document Set + +Here’s the actual Rust logic from `lib.rs` and `services.rs`: + +``` +// src/satellite/src/lib.rsmod env;mod ledger_icrc;mod services;mod types;mod utils;use crate::services::{assert_wallet_balance, set_request_processed, transfer_icp_from_wallet};use crate::types::RequestData;use crate::utils::icp_ledger_id;use ic_cdk::id;use icrc_ledger_types::icrc1::account::Account;use junobuild_macros::on_set_doc;use junobuild_satellite::{include_satellite, OnSetDocContext};use junobuild_utils::decode_doc_data;// Triggered when a new document is set in the "request" collection#[on_set_doc(collections = ["request"])]async fn on_set_doc(context: OnSetDocContext) -> Result<(), String> { // Init data let data: RequestData = decode_doc_data(&context.data.data.after.data)?; let request_amount = data.amount.value; let fee = data.fee.as_ref().map(|fee| fee.value); let ledger_id = icp_ledger_id()?; let from_account: Account = Account::from(context.caller); // Check current account balance assert_wallet_balance(&ledger_id, &from_account, &request_amount, &fee).await?; // Update request status to processed (atomic with transfer) set_request_processed(context.data.key, &data, &context.data.data.after.version)?; // Transfer from wallet to satellite let to_account: Account = Account::from(id()); transfer_icp_from_wallet( &ledger_id, &from_account, &to_account, &request_amount, &fee, ) .await?; Ok(())}include_satellite!(); +``` + +``` +// src/satellite/src/services.rs/// Asserts that the given account has enough balance to cover the amount and fee.pub async fn assert_wallet_balance( ledger_id: &Principal, from_account: &Account, amount: &u64, fee: &Option,) -> Result<(), String> { let balance = icrc_balance_of(&ledger_id, &from_account).await?; let total = amount.saturating_add(fee.unwrap_or(10_000u64)); if balance < total { return Err(format!("Balance {} is smaller than {}", balance, total)); } Ok(())}/// Transfers ICP from one account to another using `icrc2_transfer_from`.pub async fn transfer_icp_from_wallet( ledger_id: &Principal, from_account: &Account, to_account: &Account, amount: &u64, fee: &Option,) -> Result<(), String> { let result = icrc_transfer_from( &ledger_id, &from_account, &to_account, &Nat::from(amount.clone()), &fee.map(|fee| Nat::from(fee)), ) .await .map_err(|e| format!("Failed to call ICRC ledger icrc_transfer_from: {:?}", e)) .and_then(|result| { result.map_err(|e| format!("Failed to execute the transfer from: {:?}", e)) })?; print(format!("Result of the transfer from is {:?}", result)); Ok(())}/// Updates the request document status to `Processed`.pub fn set_request_processed( key: String, original_data: &RequestData, original_version: &Option,) -> Result<(), String> { let update_data: RequestData = RequestData { status: RequestStatus::Processed, ..original_data.clone() }; let data = encode_doc_data(&update_data)?; let doc: SetDoc = SetDoc { data, description: None, version: original_version.clone(), }; let _ = set_doc_store(id(), "request".to_string(), key, doc)?; Ok(())} +``` + +**Explanation:** + +* When a request is submitted, the `on_set_doc` hook is triggered for the `request` collection. +* The function checks the user's wallet balance, updates the request status, and performs the ICP transfer atomically. +* If any step fails, the entire operation is reverted. +* The frontend can monitor request status and balances via the exposed APIs. + +--- + +## How to Run + +1. **Clone the repo**: + +``` +git clone https://github.com/junobuild/examplescd rust/calls +``` + +2. **Install dependencies**: + +``` +npm install +``` + +3. **Start Juno local emulator**: + +**Important:** + +Requires the Juno CLI to be available `npm i -g @junobuild/cli` + +``` +juno dev start +``` + +4. **Create a Satellite** for local dev: + +* Visit [http://localhost:5866](http://localhost:5866) and follow the instructions. +* Update `juno.config.ts` with your Satellite ID. + +5. **Create required collections**: + +* `request` in Datastore: [http://localhost:5866/datastore](http://localhost:5866/datastore) + +6. **Start the frontend dev server** (in a separate terminal): + +``` +npm run dev +``` + +7. **Build the serverless functions** (in a separate terminal): + +``` +juno functions build +``` + +The emulator will automatically upgrade your Satellite and live reload the changes. + +--- + +## Juno-Specific Configuration + +* **juno.config.ts**: Defines Satellite IDs for development/production, build source, and predeploy steps. See the [Configuration reference](/docs/reference/configuration.md) for details. +* **vite.config.ts**: Registers the `juno` plugin to inject environment variables automatically. See the [Vite Plugin reference](/docs/reference/plugins.md#vite-plugin) for more information. + +--- + +## Production Deployment + +* Create a Satellite on the [Juno Console](https://console.juno.build) for mainnet. +* Update `juno.config.ts` with the production Satellite ID. +* Build and deploy the frontend: + +``` +npm run buildjuno deploy +``` + +* Build and upgrade the serverless functions: + +``` +juno functions buildjuno functions upgrade +``` + +--- + +## Notes + +* This example focuses on the Rust serverless function and canister call integration. The frontend is intentionally minimal and included only for demonstration. +* Use this project as a starting point for workflows that require on-chain asset transfers or canister calls in response to user actions. + +## Real-World Example + +Want to see how assertions and serverless logic are used in a live project? + +Check out [proposals.network](https://proposals.network), an open-source app built with Juno: + +* GitHub: [github.com/peterpeterparker/proposals.network](https://github.com/peterpeterparker/proposals.network) +* Example logic: [src/satellite/src/lib.rs](https://github.com/peterpeterparker/proposals.network/blob/main/src/satellite/src/lib.rs) + +This app uses: + +* `#[on_delete_doc]` and `#[assert_delete_doc]` to validate and clean up related documents and assets +* Shared helper modules like `assert`, `delete`, and `types` to keep logic organized +* A real-world pattern of chaining asset/document deletions with assertions + +It’s a great reference for more advanced setups and multi-collection coordination. + +--- + +## References + +* [Serverless Functions Guide](/docs/guides/rust.md) +* [Rust Functions Development](/docs/build/functions.md) +* [Rust SDK Reference](/docs/reference/functions/rust/sdk.md) +* [Rust Utils Reference](/docs/reference/functions/rust/utils.md) +* [Run Local Development](/docs/guides/local-development.md) +* [CLI Reference](/docs/reference/cli.md) +* [Configuration Reference](/docs/reference/configuration.md) +* [Datastore Collections](/docs/build/datastore/collections.md) + +--- + +## Crate Docs + +These crates are used to build and extend serverless functions in Rust with Juno: + +* [junobuild-satellite](https://docs.rs/junobuild-satellite): Core features and runtime for building a Satellite in Rust, including hooks, assertions, and datastore integration. +* [junobuild-macros](https://docs.rs/junobuild-macros): Procedural macros for declaratively attaching hooks and assertions. +* [junobuild-utils](https://docs.rs/junobuild-utils): Utility helpers for working with documents, including data encoding, decoding, and assertion context handling. +* [junobuild-shared](https://docs.rs/junobuild-shared): Shared types and helpers for Juno projects. Used by all containers including the Console. +* [junobuild-storage](https://docs.rs/junobuild-storage): Storage helpers for working with assets and HTTP headers in Juno. + +\* [icrc-ledger-types](https://docs.rs/icrc-ledger-types): Types for interacting with the ICRC ledger standard. \* [ic-cdk](https://docs.rs/ic-cdk): Internet Computer canister development kit for Rust. + # Generating Assets with Rust Serverless Functions This example demonstrates how to use **Rust serverless functions** to dynamically generate and store assets in **Juno Storage** from a **Satellite**. In this example, the generated assets are JSON files. @@ -8694,6 +8880,12 @@ In Juno, we use some terms that may be unfamiliar to some. This page provides a An "Account Identifier" is an address, serving as the textual representation of an account on the Internet Computer (ICP) ledger. It can represent an account owned by an individual or a smart contract. +## Canister + +A canister is the term for a smart contract on the Internet Computer. It includes both logic and state (memory), bundled together and deployed as a WebAssembly (WASM) container. + +All ([modules](#modules)) in Juno — such as ([satellites](#satellite)), ([mission controls](#mission-control)), and ([orbiters](#orbiter)) — are canisters under the hood. + ## Console The "console" refers to Juno's administration application, located at [https://console.juno.build](https://console.juno.build). diff --git a/.llms-snapshots/llms.txt b/.llms-snapshots/llms.txt index 470a74d7..cb8613c5 100644 --- a/.llms-snapshots/llms.txt +++ b/.llms-snapshots/llms.txt @@ -59,8 +59,9 @@ Juno is your self-contained serverless platform for building full-stack web apps ## Examples - Functions - Rust - [Rust Assertions Example](https://juno.build/docs/examples/functions/rust/assertion.md): An example demonstrating how to write custom assertions in Rust for Juno serverless functions. -- [Generating Assets with Rust Serverless Functions](https://juno.build/docs/examples/functions/rust/generating-assets-with-hooks.md): An example showing how to dynamically generate and store assets (like JSON) in Storage using Rust in Juno Satellites. -- [Mutating Documents with Rust Hooks](https://juno.build/docs/examples/functions/rust/mutating-docs-with-hooks.md): An example demonstrating how to modify and re-save documents in Juno Satellites using Rust hooks. +- [Making Canister Calls in Rust Serverless Functions](https://juno.build/docs/examples/functions/rust/canister-calls.md): An example showing how to call external canisters (e.g., ICRC ledger) from a serverless function written in Rust using Juno Satellites. +- [Generating Assets with Rust Serverless Functions](https://juno.build/docs/examples/functions/rust/generating-assets.md): An example showing how to dynamically generate and store assets (like JSON) in Storage using Rust in Juno Satellites. +- [Mutating Documents with Rust Hooks](https://juno.build/docs/examples/functions/rust/mutating-docs.md): An example demonstrating how to modify and re-save documents in Juno Satellites using Rust hooks. ## Guides