Skip to content
Draft
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
131 changes: 131 additions & 0 deletions evaluations/canister-calls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{
"skill": "canister-calls",
"description": "Evaluation cases for the canister-calls skill. Tests whether agents can discover canister interfaces via Candid, use curated workflows for well-known canisters, avoid common pitfalls (wrong IDs, missing fees, incomplete flows), and fall back to generic Candid discovery for unknown canisters.",

"output_evals": [
{
"name": "Discover unknown canister API",
"prompt": "I found this canister on mainnet: rdmx6-jaaaa-aaaaa-aaadq-cai. I want to call it from my Rust canister but I have no idea what methods it exposes. How do I figure out its API?",
"expected_behaviors": [
"Suggests fetching the Candid interface via icp canister metadata or equivalent",
"Explains how to read the returned .did file (method names, types, query vs update)",
"Does NOT hallucinate method names for this canister",
"Mentions generating typed Rust bindings from the .did (ic-cdk-bindgen)"
]
},
{
"name": "ICRC-1 token transfer",
"prompt": "I need my Motoko canister to send 1 ICP to another principal. Show me the code.",
"expected_behaviors": [
"Uses the ICP ledger canister ID ryjl3-tyaaa-aaaaa-aaaba-cai",
"Uses icrc1_transfer (not the legacy transfer method)",
"Fee is 10000 e8s (not 10000 ICP)",
"Amount is in e8s (100_000_000 for 1 ICP)",
"Account format is { owner: Principal; subaccount: ?Blob }, not AccountIdentifier",
"Handles the TransferError variant (not just Ok)"
]
},
{
"name": "ckBTC deposit flow",
"prompt": "I'm building a Rust canister that accepts BTC deposits from users. Walk me through the full flow.",
"expected_behaviors": [
"Uses the correct minter canister ID mqygn-kiaaa-aaaar-qaadq-cai (not the ledger ID)",
"Shows the complete flow: get_btc_address -> user sends BTC -> update_balance",
"Explicitly mentions that update_balance must be called (minter does not auto-detect deposits)",
"Derives per-user subaccounts from the caller's principal (32 bytes, padded)",
"Sets owner to the canister's own principal (not the user's principal)"
]
},
{
"name": "ckBTC withdrawal",
"prompt": "My canister holds ckBTC for users in subaccounts. A user wants to withdraw 0.001 BTC to their Bitcoin address. How do I implement this in Motoko?",
"expected_behaviors": [
"Uses the two-step flow: icrc2_approve on ledger, then retrieve_btc_with_approval on minter",
"Approve amount includes the fee (amount + 10 satoshis)",
"Spender in the approve call is the minter canister",
"Mentions the minimum withdrawal amount (50,000 satoshis)",
"Handles error variants from both the approve and retrieve calls"
]
},
{
"name": "EVM RPC call with cycles",
"prompt": "I want to read the ETH balance of a wallet address from my Motoko canister using the EVM RPC canister. Show me how.",
"expected_behaviors": [
"Uses the EVM RPC canister ID 7hfb6-caaaa-aaaar-qadga-cai",
"Attaches cycles using 'await (with cycles = ...)' syntax (not Cycles.add)",
"Handles both #Consistent and #Inconsistent result variants",
"Uses #EthMainnet variant for Ethereum L1",
"Does NOT forget the null config parameter"
]
},
{
"name": "Adversarial: wrong canister ID",
"prompt": "I want to check a user's ckBTC balance. I'll call icrc1_balance_of on mqygn-kiaaa-aaaar-qaadq-cai, right?",
"expected_behaviors": [
"Corrects the canister ID — mqygn is the minter, not the ledger",
"Provides the correct ledger canister ID: mxzaz-hqaaa-aaaar-qaada-cai",
"Explains the difference between the minter and ledger canisters"
]
},
{
"name": "Adversarial: missing update_balance",
"prompt": "I set up ckBTC deposits. I call get_btc_address, the user sends BTC, and then I show their ckBTC balance. But it always shows 0. What's wrong?",
"expected_behaviors": [
"Identifies the missing update_balance call as the root cause",
"Explains that the minter does not auto-detect BTC deposits",
"Shows how to call update_balance with the correct owner and subaccount"
]
},
{
"name": "ICRC-2 approve and transferFrom",
"prompt": "I'm building a marketplace canister in Rust. When a buyer purchases an item, my canister needs to transfer ICP from the buyer to the seller. How do I do this without the buyer calling my canister with the tokens directly?",
"expected_behaviors": [
"Explains the ICRC-2 approve/transferFrom flow",
"Buyer calls icrc2_approve on the ledger to authorize the marketplace canister",
"Marketplace calls icrc2_transfer_from to move tokens from buyer to seller",
"Uses correct ICP ledger canister ID",
"Mentions that approve must happen before transferFrom",
"Handles InsufficientAllowance error variant"
]
},
{
"name": "Call canister from frontend",
"prompt": "I have a canister deployed on mainnet and I want to call its methods from my TypeScript frontend. How do I generate the bindings and set up the actor?",
"expected_behaviors": [
"Recommends @icp-sdk/bindgen for generating TypeScript bindings",
"Mentions @icp-sdk/core for the runtime actor",
"Does NOT suggest dfx generate"
]
}
],

"trigger_evals": {
"description": "Queries to test whether the skill activates correctly. 'should_trigger' queries should cause the skill to load; 'should_not_trigger' queries should NOT activate this skill.",
"should_trigger": [
"How do I call a canister I found on the dashboard?",
"Send ICP tokens from my canister to another principal",
"I want to accept BTC deposits in my dapp using ckBTC",
"Read an ERC-20 balance from my IC canister",
"What's the Candid interface of this canister?",
"How do I do icrc2_approve and transferFrom?",
"My ckBTC balance shows 0 after sending BTC to the deposit address",
"Call the EVM RPC canister from Motoko",
"I need to interact with a canister but I don't know its API",
"Transfer ckETH from my canister",
"How do I generate TypeScript bindings for a canister?",
"Withdraw ckBTC back to a Bitcoin address"
],
"should_not_trigger": [
"Make an HTTP request to an external API from my canister",
"How do I deploy my canister to mainnet?",
"Add access control to my canister methods",
"How does stable memory work for canister upgrades?",
"Set up Internet Identity login for my frontend",
"How do I handle inter-canister call failures safely?",
"Configure my icp.yaml for a Rust canister",
"What's the best way to store large files on IC?",
"How do I set up a custom domain for my frontend?",
"Monitor my canister's cycle balance"
]
}
}
155 changes: 155 additions & 0 deletions skills/canister-calls/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
name: canister-calls
description: "Discover and call any Internet Computer canister. Covers retrieving Candid interfaces from deployed canisters (both off-chain and via inter-canister calls), reading type signatures, generating typed client bindings, and constructing calls using any IC agent library. Includes curated workflows for well-known infrastructure canisters (ICRC ledgers, ckBTC minter, EVM RPC). Use when making any canister call, exploring an unfamiliar canister's API, integrating with IC infrastructure canisters, working with token transfers, ckBTC deposits/withdrawals, or Ethereum/EVM calls from IC."
license: Apache-2.0
compatibility: "icp-cli >= 0.1.0"
metadata:
title: Canister Calls & Interface Discovery
category: Integration
---

# Canister Calls & Interface Discovery

## What This Is

Every canister on the Internet Computer exposes a Candid interface — a typed API description embedded in the WASM module. Candid is to canisters what `--help` is to CLI tools: the standard way to discover what a canister can do and how to call it. This skill teaches you how to retrieve, read, and use Candid interfaces to call any canister, plus curated workflows for well-known infrastructure canisters where raw Candid alone isn't enough.

## Prerequisites

- For Motoko: `mops` package manager, `core = "2.0.0"` in mops.toml
- For Rust: `ic-cdk >= 0.19`, `candid >= 0.10`
- For JavaScript/TypeScript: `@icp-sdk/core` (runtime), `@icp-sdk/bindgen` (codegen)
- For Rust bindings: `ic-cdk-bindgen` (build-time Candid-to-Rust codegen)

## Discovering a Canister's Interface

### From Outside IC (Off-Chain)

Retrieve the Candid interface of any deployed canister:

```bash
# Fetch the .did file from a deployed canister (local or mainnet)
icp canister metadata <CANISTER_ID> candid:service -e ic

# Example: get the ICP ledger's interface
icp canister metadata ryjl3-tyaaa-aaaaa-aaaba-cai candid:service -e ic
```

This returns the full Candid service definition with all method signatures, types, and documentation comments (if the canister author included them).

### From Inside a Canister (Inter-Canister)

When your canister needs to call another canister dynamically, you can fetch its Candid interface at runtime using the management canister:

```bash
# The management canister exposes canister metadata
icp canister call aaaaa-aa canister_metadata '(record { canister_id = principal "<CANISTER_ID>"; path = "candid:service" })' -e ic
```

### Reading Candid Interfaces

A Candid interface describes:
- **Method names** and whether they are `query` (fast, read-only) or `update` (consensus-based, can mutate state)
- **Argument types** and **return types** — fully typed, including records, variants, optionals, vectors
- **Documentation comments** (if the canister author included them, prefixed with `///` in the .did file)

Example Candid snippet:
```candid
service : {
icrc1_transfer : (TransferArg) -> (variant { Ok : nat; Err : TransferError });
icrc1_balance_of : (Account) -> (nat) query;
icrc1_fee : () -> (nat) query;
}
```

This tells you: `icrc1_transfer` is an update call taking `TransferArg` and returning a result variant. `icrc1_balance_of` is a query call. The types (`TransferArg`, `Account`, `TransferError`) are defined elsewhere in the same .did file.

### Generating Typed Client Bindings

Each language has a dedicated tool for generating typed bindings from .did files:

#### Rust

Use `ic-cdk-bindgen` to generate typed Rust bindings from .did files at build time. Add it to your `build-dependencies` in `Cargo.toml` and configure it in `build.rs`. See https://crates.io/crates/ic-cdk-bindgen for setup.

#### JavaScript / TypeScript

Use `@icp-sdk/bindgen` to generate typed JS/TS bindings from .did files:

```bash
npx @icp-sdk/bindgen --canister <CANISTER_ID> -e ic
```

See https://www.npmjs.com/package/@icp-sdk/bindgen for options.

### Calling Any Canister via CLI

Once you know the method signature from the Candid interface:

```bash
# Call any method on any canister
icp canister call <CANISTER_ID> <METHOD_NAME> '(<CANDID_ARGS>)' -e ic

# Query call (faster, read-only)
icp canister call <CANISTER_ID> <METHOD_NAME> '(<CANDID_ARGS>)' --query -e ic
```

### Calling Any Canister from Code

#### Motoko — Dynamic Actor Reference

```motoko
// Reference a remote canister by principal with a typed interface
transient let remote = actor ("aaaaa-bbbbb-ccccc-ddddd-cai") : actor {
some_method : shared (Nat) -> async Text;
some_query : shared query () -> async Nat;
};

// Call it
let result = await remote.some_method(42);
```

#### Rust — Using ic-cdk Call API

```rust
use ic_cdk::call::Call;
use candid::Principal;

let canister_id = Principal::from_text("aaaaa-bbbbb-ccccc-ddddd-cai").unwrap();

// Unbounded wait (guaranteed response)
let (result,): (String,) = Call::unbounded_wait(canister_id, "some_method")
.with_arg(42u64)
.await
.expect("Call failed")
.candid_tuple()
.expect("Decode failed");
```

## What Candid Doesn't Tell You

Candid gives you the shape of an API but not the workflow. For well-known infrastructure canisters, you need to know:
- **Which canisters to call and in what order** (e.g., ckBTC deposit is a multi-step flow across minter + ledger)
- **Cycle costs** (e.g., EVM RPC requires cycles attached to calls)
- **Fee amounts and units** (e.g., ICP fee is 10,000 e8s, not 10,000 ICP)
- **Pitfalls that cause silent failures** (e.g., forgetting `update_balance` after a BTC deposit)

The reference files below contain this curated knowledge for each well-known canister.

## Well-Known Canister Registry

| Canister | ID (Mainnet) | What It Does | Reference |
|----------|-------------|-------------|-----------|
| ICP Ledger | `ryjl3-tyaaa-aaaaa-aaaba-cai` | ICP token transfers, balances, ICRC-1/2 | `references/icrc-ledger.md` |
| ckBTC Ledger | `mxzaz-hqaaa-aaaar-qaada-cai` | ckBTC token transfers | `references/icrc-ledger.md` |
| ckBTC Minter | `mqygn-kiaaa-aaaar-qaadq-cai` | BTC deposit/withdrawal via ckBTC | `references/ckbtc.md` |
| ckETH Ledger | `ss2fx-dyaaa-aaaar-qacoq-cai` | ckETH token transfers | `references/icrc-ledger.md` |
| EVM RPC | `7hfb6-caaaa-aaaar-qadga-cai` | Ethereum/EVM JSON-RPC proxy | `references/evm-rpc.md` |

**For any canister not listed here**, use the Candid discovery flow above: fetch the .did, read the types, generate bindings, and call.

### When to Read a Reference File

- **Making token transfers (ICP, ckBTC, ckETH)** or working with **ICRC-1/ICRC-2 approve/transferFrom** -> Read `references/icrc-ledger.md`
- **Integrating Bitcoin** (BTC deposits, ckBTC minting, BTC withdrawals) -> Read `references/ckbtc.md`
- **Calling Ethereum/EVM chains** (ETH balances, ERC-20 reads, sending transactions) -> Read `references/evm-rpc.md`
Loading
Loading