Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 184 additions & 2 deletions .llms-snapshots/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2233,7 +2233,7 @@ Write serverless backend logic for your app using TypeScript or Rust. These exam

[## 🗃️ TypeScript

2 items](/docs/examples/functions/typescript.md)
3 items](/docs/examples/functions/typescript.md)

# Angular Example

Expand Down Expand Up @@ -3189,6 +3189,10 @@ An example demonstrating how to write custom assertions in TypeScript for Juno s

An example demonstrating how to modify and re-save documents in Juno Satellites using TypeScript hooks.](/docs/examples/functions/typescript/mutating-docs.md)

[## 📄️ Canister Calls

An example showing how to call external canisters (e.g., ICRC ledger) from a serverless function written in TypeScript using Juno Satellites.](/docs/examples/functions/typescript/canister-calls.md)

# Rust Assertion Example

This example demonstrates how to write a **custom assertion** in **Rust** for a Juno **serverless function**. It shows how to intercept and validate data operations—such as rejecting specific content—before it's written to the datastore.
Expand Down Expand Up @@ -3549,7 +3553,9 @@ 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.

\* [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.
* [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

Expand Down Expand Up @@ -4051,6 +4057,182 @@ It’s a great reference for more advanced setups and orchestration.
* [Configuration Reference](/docs/reference/configuration.md)
* [Datastore Collections](/docs/build/datastore/collections.md)

# Making Canister Calls in TypeScript Serverless Functions

This example demonstrates how to use **TypeScript 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/functions/typescript/calls](https://github.com/junobuild/examples/tree/main/functions/typescript/calls)

---

## Folder Structure

```
typescript/calls/├── src/│ ├── satellite/ # TypeScript Satellite serverless function│ │ ├── index.ts # Main TypeScript logic for Satellite│ │ ├── services.ts # Helper logic for balance, transfer, status│ │ ├── ledger-icrc.ts # Ledger helper functions│ │ └── tsconfig.json # TypeScript config for Satellite│ ├── declarations/│ │ └── satellite/ # 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 TypeScript 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/index.ts**: The entry point for the Satellite serverless function. Triggers the canister call and updates request status on document set.
* **src/satellite/services.ts**: Helper logic for checking wallet balance, performing the transfer, and updating request status.
* **src/satellite/ledger-icrc.ts**: Helper functions for interacting with the ICRC ledger.
* **src/types/request.ts**: Data model for requests and status.

---

## Example: Canister Call on Document Set

Here’s the actual TypeScript logic from `index.ts` and `services.ts`:

```
// src/satellite/index.tsimport { Account } from "@dfinity/ledger-icrc/dist/candid/icrc_ledger";import { Principal } from "@dfinity/principal";import { type AssertSetDoc, defineAssert, defineHook, type OnSetDoc} from "@junobuild/functions";import { id } from "@junobuild/functions/ic-cdk";import { decodeDocData } from "@junobuild/functions/sdk";import { COLLECTION_REQUEST, ICP_LEDGER_ID } from "../constants/app.constants";import { RequestData, RequestDataSchema } from "../types/request";import { assertWalletBalance, setRequestProcessed, transferIcpFromWallet} from "./services";export const assertSetDoc = defineAssert<AssertSetDoc>({ collections: [COLLECTION_REQUEST], assert: (context) => { // We validate that the data submitted for create or update matches the expected schema. const person = decodeDocData<RequestData>(context.data.data.proposed.data); RequestDataSchema.parse(person); }});export const onSetDoc = defineHook<OnSetDoc>({ collections: [COLLECTION_REQUEST], run: async (context) => { // Init data const { data: { key, data: { after: { version } } } } = context; const data = decodeDocData<RequestData>(context.data.data.after.data); const { amount: requestAmount, fee } = data; const ledgerId = ICP_LEDGER_ID; const fromAccount: Account = { owner: Principal.fromUint8Array(context.caller), subaccount: [] }; // Check current account balance await assertWalletBalance({ ledgerId, fromAccount, amount: requestAmount, fee }); // Update request status to processed (atomic with transfer) setRequestProcessed({ key, version, data }); // Transfer from wallet to satellite const toAccount: Account = { owner: id(), subaccount: [] }; await transferIcpFromWallet({ ledgerId, fromAccount, toAccount, amount: requestAmount, fee }); }});
```

```
// src/satellite/services.tsexport const assertWalletBalance = async ({ ledgerId, fromAccount, amount, fee}: { ledgerId: Principal; fromAccount: Account; amount: bigint; fee: bigint | undefined;}) => { const balance = await icrcBalanceOf({ ledgerId, account: fromAccount }); const total = amount + (fee ?? IC_TRANSACTION_FEE_ICP); if (balance < total) { throw new Error( `Balance ${balance} is smaller than ${total} for account ${fromAccount.owner.toText()}.` ); }};export const transferIcpFromWallet = async (params: { ledgerId: Principal; fromAccount: Account; toAccount: Account; amount: bigint; fee: bigint | undefined;}): Promise<bigint> => { const result = await icrcTransferFrom(params); if ("Err" in result) { throw new Error( `Failed to transfer ICP from wallet: ${JSON.stringify(result)}` ); } return result.Ok;};export const setRequestProcessed = ({ key, data: currentData, version: originalVersion}: { key: string; data: RequestData; version: bigint | undefined;}) => { const updateData: RequestData = { ...currentData, status: "processed" }; const data = encodeDocData(updateData); const doc: SetDoc = { data, version: originalVersion }; setDocStore({ caller: id(), collection: COLLECTION_REQUEST, doc, key });};
```

**Explanation:**

* When a request is submitted, the `onSetDoc` 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 typescript/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 TypeScript serverless function. The frontend is intentionally minimal and included only for demonstration.
* Use this project as a starting point for writing custom backend logic in TypeScript using Juno serverless functions and canister calls.

---

## Real-World Example

Want to see how assertions and serverless logic are used in a live project?

Check out [cycles.watch](https://cycles.watch), an open-source app built with Juno:

* GitHub: [github.com/peterpeterparker/cycles.watch](https://github.com/peterpeterparker/cycles.watch)
* Example logic: [src/satellite/index.ts](https://github.com/peterpeterparker/cycles.watch/blob/main/src/satellite/index.ts)

This app uses:

* `assertSetDoc` to validate requests
* `onSetDoc` to implement a swap-like feature that performs various canister calls
* Service modules to keep logic organized
* A real-world pattern for chaining calls and document insertions with assertions

It’s a great reference for more advanced setups and orchestration.

---

## References

* [Serverless Functions Guide](/docs/guides/typescript.md)
* [Functions Development](/docs/build/functions.md)
* [TypeScript SDK Reference](/docs/reference/functions/typescript/sdk.md)
* [TypeScript ic-cdk Reference](/docs/reference/functions/typescript/ic-cdk.md)
* [TypeScript Utils Reference](/docs/reference/functions/typescript/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)

# Mutating Documents with TypeScript Hooks

This example demonstrates how to use **hooks in TypeScript** to modify documents automatically when they're created or updated in your Juno **Satellite**.
Expand Down
1 change: 1 addition & 0 deletions .llms-snapshots/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Juno is your self-contained serverless platform for building full-stack web apps
## Examples - Functions - Typescript

- [TypeScript Assertions Example](https://juno.build/docs/examples/functions/typescript/assertion.md): An example demonstrating how to write custom assertions in TypeScript for Juno serverless functions.
- [Making Canister Calls in TypeScript Serverless Functions](https://juno.build/docs/examples/functions/typescript/canister-calls.md): An example showing how to call external canisters (e.g., ICRC ledger) from a serverless function written in TypeScript using Juno Satellites.
- [Mutating Documents with TypeScript Hooks](https://juno.build/docs/examples/functions/typescript/mutating-docs.md): An example demonstrating how to modify and re-save documents in Juno Satellites using TypeScript hooks.

## Guides
Expand Down
Loading