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
4 changes: 2 additions & 2 deletions .llms-snapshots/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6387,11 +6387,11 @@ typescript/calls/├── src/│ ├── satellite/ # TypeScript
Here’s the actual TypeScript logic from `index.ts` and `services.ts`:

```
// src/satellite/index.tsimport { Principal } from "@icp-sdk/core/principal";import { type AssertSetDoc, defineAssert, defineHook, type OnSetDoc} from "@junobuild/functions";import { IcrcLedgerDid } from "@junobuild/functions/canisters/ledger/icrc";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: IcrcLedgerDid.Account = { owner: Principal.fromUint8Array(context.caller), subaccount: [] }; // ############### // Check current account balance. This way the process can stop early on // ############### await assertWalletBalance({ ledgerId, fromAccount, amount: requestAmount, fee }); // ############### // The request is about to be processed by transferring the amount via the ICP ledger. // We update the status beforehand. Since the function is atomic, a failed transfer reverts everything. // This avoids a case where the transfer succeeds but the status isn't updated — even if unlikely. // This is for demo only. In production, proper error handling and bookkeeping would be required. // ############### setRequestProcessed({ key, version, data }); // ############### // Transfer from wallet to satellite. // ############### const toAccount: IcrcLedgerDid.Account = { owner: id(), subaccount: [] }; await transferIcpFromWallet({ ledgerId, fromAccount, toAccount, amount: requestAmount, fee }); console.log(`${requestAmount} ICP transferred to Satellite 🥳`); }});
// src/satellite/index.tsimport { Principal } from "@icp-sdk/core/principal";import { type AssertSetDoc, defineAssert, defineHook, type OnSetDoc} from "@junobuild/functions";import { type Account, encodeIcrcAccount} from "@junobuild/functions/canisters/ledger/icrc";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) }; // ############### // Check current account balance. This way the process can stop early on // ############### await assertWalletBalance({ ledgerId, fromAccount, amount: requestAmount, fee }); // ############### // The request is about to be processed by transferring the amount via the ICP ledger. // We update the status beforehand. Since the function is atomic, a failed transfer reverts everything. // This avoids a case where the transfer succeeds but the status isn't updated — even if unlikely. // This is for demo only. In production, proper error handling and bookkeeping would be required. // ############### setRequestProcessed({ key, version, data }); // ############### // Transfer from wallet to satellite. // ############### const toAccount: Account = { owner: id() }; await transferIcpFromWallet({ ledgerId, fromAccount, toAccount, amount: requestAmount, fee }); console.log(`${requestAmount} ICP transferred to Satellite 🥳`); }});
```

```
// src/satellite/services.tsexport const assertWalletBalance = async ({ ledgerId, fromAccount, amount, fee}: { ledgerId: Principal; fromAccount: IcrcLedgerDid.Account; amount: bigint; fee: bigint | undefined;}) => { const { icrc1BalanceOf } = new IcrcLedgerCanister({ canisterId: ledgerId }); const balance = await icrc1BalanceOf({ account: fromAccount }); const total = amount + (fee ?? IC_TRANSACTION_FEE_ICP); if (balance < total) { const encodedAccountText = encodeIcrcAccount({ owner: fromAccount.owner, subaccount: fromNullable(fromAccount.subaccount) }); throw new Error( `Balance ${balance} is smaller than ${total} for account ${encodedAccountText}.` ); }};export const transferIcpFromWallet = async ({ ledgerId, fromAccount, amount, fee, toAccount}: { ledgerId: Principal; fromAccount: IcrcLedgerDid.Account; toAccount: IcrcLedgerDid.Account; amount: bigint; fee: bigint | undefined;}): Promise<IcrcLedgerDid.Tokens> => { const args: IcrcLedgerDid.TransferFromArgs = { amount, from: fromAccount, to: toAccount, created_at_time: toNullable(), fee: toNullable(fee), memo: toNullable(), spender_subaccount: toNullable() }; const { icrc2TransferFrom } = new IcrcLedgerCanister({ canisterId: ledgerId }); const result = await icrc2TransferFrom({ args }); if ("Err" in result) { throw new Error( `Failed to transfer ICP from wallet: ${JSON.stringify(result, jsonReplacer)}` ); } 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 });};
// src/satellite/services.tsimport { Principal } from "@icp-sdk/core/principal";import { IcrcLedgerCanister, type Account, type TransferFromArgs, type Tokens} from "@junobuild/functions/canisters/ledger/icrc";export const assertWalletBalance = async ({ ledgerId, fromAccount, amount, fee}: { ledgerId: Principal; fromAccount: Account; amount: bigint; fee: bigint | undefined;}) => { const { icrc1BalanceOf } = new IcrcLedgerCanister({ canisterId: ledgerId }); const balance = await icrc1BalanceOf({ account: fromAccount }); const total = amount + (fee ?? 10_000n); if (balance < total) { const encodedAccountText = encodeIcrcAccount(fromAccount); throw new Error( `Balance ${balance} is smaller than ${total} for account ${encodedAccountText}.` ); }};export const transferIcpFromWallet = async ({ ledgerId, fromAccount, toAccount, amount, fee}: { ledgerId: Principal; fromAccount: Account; toAccount: Account; amount: bigint; fee: bigint | undefined;}): Promise<Tokens> => { const args: TransferFromArgs = { amount, from: fromAccount, to: toAccount, fee }; const { icrc2TransferFrom } = new IcrcLedgerCanister({ canisterId: ledgerId }); const result = await icrc2TransferFrom({ args }); if ("Err" in result) { throw new Error( `Failed to transfer ICP from wallet: ${JSON.stringify(result, jsonReplacer)}` ); } 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:**
Expand Down
53 changes: 27 additions & 26 deletions docs/examples/functions/typescript/canister-calls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ import {
defineHook,
type OnSetDoc
} from "@junobuild/functions";
import { IcrcLedgerDid } from "@junobuild/functions/canisters/ledger/icrc";
import {
type Account,
encodeIcrcAccount
} from "@junobuild/functions/canisters/ledger/icrc";
import { id } from "@junobuild/functions/ic-cdk";
import { decodeDocData } from "@junobuild/functions/sdk";
import { COLLECTION_REQUEST, ICP_LEDGER_ID } from "../constants/app.constants";
Expand Down Expand Up @@ -129,9 +132,8 @@ export const onSetDoc = defineHook<OnSetDoc>({

const ledgerId = ICP_LEDGER_ID;

const fromAccount: IcrcLedgerDid.Account = {
owner: Principal.fromUint8Array(context.caller),
subaccount: []
const fromAccount: Account = {
owner: Principal.fromUint8Array(context.caller)
};

// ###############
Expand Down Expand Up @@ -161,9 +163,8 @@ export const onSetDoc = defineHook<OnSetDoc>({
// Transfer from wallet to satellite.
// ###############

const toAccount: IcrcLedgerDid.Account = {
owner: id(),
subaccount: []
const toAccount: Account = {
owner: id()
};

await transferIcpFromWallet({
Expand All @@ -181,30 +182,33 @@ export const onSetDoc = defineHook<OnSetDoc>({

```ts
// src/satellite/services.ts
import { Principal } from "@icp-sdk/core/principal";
import {
IcrcLedgerCanister,
type Account,
type TransferFromArgs,
type Tokens
} from "@junobuild/functions/canisters/ledger/icrc";

export const assertWalletBalance = async ({
ledgerId,
fromAccount,
amount,
fee
}: {
ledgerId: Principal;
fromAccount: IcrcLedgerDid.Account;
fromAccount: Account;
amount: bigint;
fee: bigint | undefined;
}) => {
const { icrc1BalanceOf } = new IcrcLedgerCanister({ canisterId: ledgerId });

const balance = await icrc1BalanceOf({
account: fromAccount
});
const balance = await icrc1BalanceOf({ account: fromAccount });

const total = amount + (fee ?? IC_TRANSACTION_FEE_ICP);
const total = amount + (fee ?? 10_000n);

if (balance < total) {
const encodedAccountText = encodeIcrcAccount({
owner: fromAccount.owner,
subaccount: fromNullable(fromAccount.subaccount)
});
const encodedAccountText = encodeIcrcAccount(fromAccount);

throw new Error(
`Balance ${balance} is smaller than ${total} for account ${encodedAccountText}.`
Expand All @@ -215,24 +219,21 @@ export const assertWalletBalance = async ({
export const transferIcpFromWallet = async ({
ledgerId,
fromAccount,
toAccount,
amount,
fee,
toAccount
fee
}: {
ledgerId: Principal;
fromAccount: IcrcLedgerDid.Account;
toAccount: IcrcLedgerDid.Account;
fromAccount: Account;
toAccount: Account;
amount: bigint;
fee: bigint | undefined;
}): Promise<IcrcLedgerDid.Tokens> => {
const args: IcrcLedgerDid.TransferFromArgs = {
}): Promise<Tokens> => {
const args: TransferFromArgs = {
amount,
from: fromAccount,
to: toAccount,
created_at_time: toNullable(),
fee: toNullable(fee),
memo: toNullable(),
spender_subaccount: toNullable()
fee
};

const { icrc2TransferFrom } = new IcrcLedgerCanister({
Expand Down