Skip to content
Open
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 docs/concepts/cycles.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Every state-changing operation (each block created) costs 100M cycles as a fee.
The cycles ledger does not support calling arbitrary canisters with cycles attached, because open call contexts can cause the ledger to become stuck. Two patterns address this:

- **Top up the target canister first**: if you control the canister, transfer cycles to it using `withdraw` or `icp canister top-up`, then let the canister attach cycles internally from its own balance. This is the preferred pattern for canisters you deploy and control.
- **Proxy canister**: if you need to call a canister method with cycles attached from the CLI or an external agent, deploy a proxy canister using the [`proxy` template](https://github.com/dfinity/icp-cli-templates/tree/main/proxy) and route the call through it. See [Calling canisters that require cycles](../guides/canister-management/cycles-management.md#calling-canisters-that-require-cycles) for the how-to.
- **Proxy canister**: if you need to call a canister method with cycles attached from the CLI or an external agent, deploy a proxy canister using the [`proxy` template](https://github.com/dfinity/icp-cli-templates/tree/main/proxy) and route the call through it. See [Calls with attached cycles](../guides/canister-calls/inter-canister-calls.md#calls-with-attached-cycles) for the how-to.

## Developer responsibility

Expand All @@ -118,7 +118,7 @@ The tradeoff is that developers must forecast and fund usage upfront rather than
## Related

- [Cycles Management](../guides/canister-management/cycles-management.md): how to check balances, top up canisters, and set freezing thresholds
- [Calling canisters that require cycles](../guides/canister-management/cycles-management.md#calling-canisters-that-require-cycles): proxy canister pattern for attaching cycles from the CLI
- [Calls with attached cycles](../guides/canister-calls/inter-canister-calls.md#calls-with-attached-cycles): attach cycles to an inter-canister call and use the proxy canister pattern for the CLI
- [Cycles ledger reference](../references/system-canisters.md#cycles-ledger): canister IDs, interface specification, and CMC integration
- [Cycles Costs Reference](../references/cycles-costs.md): exact cost tables for all operations
- [Canisters](./canisters.md): canisters as the paying entity for compute and storage
Expand Down
135 changes: 134 additions & 1 deletion docs/guides/canister-calls/inter-canister-calls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,138 @@ Call::bounded_wait(callee, "method")

**Calling third-party canisters:** When calling canisters outside your control, always use bounded wait and design for uncertainty. The callee may be upgraded, become unresponsive, or behave unexpectedly. Use idempotent operations where possible and provide a way to query the outcome of a call separately, so your canister can recover from ambiguous responses.

## Calls with attached cycles

Some canister methods require cycles to be attached to the incoming call as a per-request fee. The [exchange rate canister](../chain-fusion/exchange-rates.mdx) is a common example: each request costs one XDR's worth of cycles, which must arrive with the call.

This is distinct from a canister's ongoing operational balance. The [cycles ledger](../../concepts/cycles.md#cycles-ledger) cannot forward calls with cycles attached, so you must attach them explicitly at the call site.

### Sending cycles

<Tabs syncKey="lang">
<TabItem label="Motoko">

Use the `(with cycles = amount)` parenthetical on any `await` expression:

```motoko
import Cycles "mo:core/Cycles";

persistent actor {
let target = actor ("rrkah-fqaaa-aaaaa-aaaaq-cai") : actor {
someMethod : () -> async ();
};

public func callWithCycles() : async () {
await (with cycles = 500_000_000) target.someMethod();
};
}
```

</TabItem>
<TabItem label="Rust">

Chain `.with_cycles()` on the `Call` builder before awaiting:

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

#[update]
async fn call_with_cycles() {
Call::unbounded_wait(
Principal::from_text("rrkah-fqaaa-aaaaa-aaaaq-cai").unwrap(),
"someMethod",
)
.with_cycles(500_000_000u128)
.await
.expect("call failed");
}
```

</TabItem>
</Tabs>

The cycles come from the calling canister's own balance, not the cycles ledger. Top up the calling canister with `icp canister top-up` before using this pattern.

### Charging a cycle fee

A canister that charges per-call defines a required fee, rejects calls that don't meet it, and accepts exactly that amount before doing its work. Any cycles above the fee are returned to the caller automatically:

<Tabs syncKey="lang">
<TabItem label="Motoko">

```motoko
import Cycles "mo:core/Cycles";
import Runtime "mo:core/Runtime";

persistent actor {
let fee : Nat = 100_000_000;

public func compute() : async () {
let available = Cycles.available();
if (available < fee) {
Runtime.trap("Insufficient cycles: requires " # debug_show fee)
};
ignore Cycles.accept<system>(fee); // accept exactly the fee; excess is returned automatically
// ... do work here
};
}
```

</TabItem>
<TabItem label="Rust">

```rust
use ic_cdk::update;

const FEE: u128 = 100_000_000;

#[update]
fn compute() {
let available = ic_cdk::api::msg_cycles_available();
if available < FEE {
ic_cdk::trap("Insufficient cycles");
}
ic_cdk::api::msg_cycles_accept(FEE); // accept exactly the fee; excess is returned automatically
// ... do work here
}
```

</TabItem>
</Tabs>

### Attaching cycles from the CLI

The CLI cannot attach cycles directly to a canister call. Two approaches address this:

**Top up the target canister first** (preferred when you control it): transfer cycles to the target using `icp canister top-up`, then call the method normally. The canister uses its own balance when the method runs.

```bash
# Transfer 1T cycles to the target canister
icp canister top-up rrkah-fqaaa-aaaaa-aaaaq-cai --amount 1T -n ic

# Then call the method as normal
icp canister call rrkah-fqaaa-aaaaa-aaaaq-cai someMethod '()' -n ic
```

**Proxy canister** (required when you cannot top up the target, or need cycles attached to each individual call): deploy a proxy canister that forwards calls with cycles attached.

```bash
# Deploy the proxy canister using the provided template
icp new proxy --subfolder proxy
cd proxy
icp deploy -e ic

# Get the proxy canister ID
export PROXY_ID=$(icp canister status -e ic --id-only proxy)

# Call any canister through the proxy with cycles attached
icp canister call --proxy "$PROXY_ID" rrkah-fqaaa-aaaaa-aaaaq-cai someMethod '()' -n ic
```

The proxy canister template is available at [icp-cli-templates/proxy](https://github.com/dfinity/icp-cli-templates/tree/main/proxy). It deploys the [proxy-canister](https://github.com/dfinity/proxy-canister), which is automatically provisioned on local networks but must be deployed manually on mainnet.

## Pub/sub pattern

The publisher/subscriber pattern is a natural fit for inter-canister communication on ICP. A publisher canister maintains a list of subscribers and notifies them when events occur. Unlike traditional pub/sub systems, ICP's reliable message delivery means subscribers are guaranteed to receive notifications (as long as both canisters have sufficient cycles).
Expand Down Expand Up @@ -400,7 +532,8 @@ Calls between canisters on the same subnet complete within a single round. Cross

- [Parallel inter-canister calls](parallel-inter-canister-calls.md): make multiple calls concurrently and use composite queries for efficient read patterns
- [Candid](candid.md): define the interface your canister exposes for inter-canister calls
- [Cycles Management](../canister-management/cycles-management.md): acquire cycles, monitor balances, and set freezing thresholds
- [Certified Variables](../backends/certified-variables.md): make query responses verifiable without update call overhead
- [Inter-Canister Call Security](../security/inter-canister-calls.md): reentrancy guards, async safety patterns, and trust considerations

{/* Upstream: informed by dfinity/portal docs/building-apps/interact-with-canisters/advanced-calls.mdx, docs/building-apps/developer-tools/cdks/rust/intercanister.mdx, multi-canister icskill, icp-cli icskill, dfinity/icp-cli docs/concepts/canister-discovery.md, dfinity/examples motoko/pub-sub, and caffeinelabs/motoko doc/md/fundamentals/2-actors/1-actors-async.md (timeout parenthetical syntax, try/finally cleanup pattern) */}
{/* Upstream: informed by dfinity/portal docs/building-apps/interact-with-canisters/advanced-calls.mdx, docs/building-apps/developer-tools/cdks/rust/intercanister.mdx, multi-canister icskill, icp-cli icskill, dfinity/icp-cli docs/concepts/canister-discovery.md, dfinity/examples motoko/pub-sub, caffeinelabs/motoko doc/md/fundamentals/2-actors/1-actors-async.md (timeout parenthetical syntax, try/finally cleanup pattern); cycles-attachment section moved from docs/guides/canister-management/cycles-management.mdx, proxy template from dfinity/icp-cli-templates */}
125 changes: 2 additions & 123 deletions docs/guides/canister-management/cycles-management.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -140,48 +140,7 @@ icp cycles mint --icp 1.0 -n ic
icp canister top-up backend --amount 1T -e ic
```

### Accepting cycles in your canister

Canisters can also accept cycles sent with an inter-canister call. This pattern is used for "tip jar" flows and payment routing:

<Tabs syncKey="lang">
<TabItem label="Motoko">

```motoko
import Cycles "mo:core/Cycles";
import Runtime "mo:core/Runtime";

persistent actor {
public func deposit() : async Nat {
let available = Cycles.available();
if (available == 0) {
Runtime.trap("No cycles sent with this call")
};
Cycles.accept<system>(available)
};
}
```

</TabItem>
<TabItem label="Rust">

```rust
use ic_cdk::update;
use candid::Nat;

#[update]
fn deposit() -> Nat {
let available = ic_cdk::api::msg_cycles_available();
if available == 0 {
ic_cdk::trap("No cycles sent with this call");
}
let accepted = ic_cdk::api::msg_cycles_accept(available);
Nat::from(accepted)
}
```

</TabItem>
</Tabs>
For accepting cycles sent with an inter-canister call (the per-request payment pattern used by canisters like the exchange rate canister), see [Calls with attached cycles](../canister-calls/inter-canister-calls.md#calls-with-attached-cycles).

## Freezing threshold

Expand Down Expand Up @@ -313,87 +272,6 @@ async fn top_up_canister(canister_id: Principal, amount: u128) {
</TabItem>
</Tabs>

## Calling canisters that require cycles

Some canister methods expect cycles to be attached to the call itself. The [cycles ledger](../../concepts/cycles.md#cycles-ledger) cannot forward calls with cycles attached, so you need a different approach depending on whether you are calling from canister code or from the CLI.

### From canister code

Attach cycles to an inter-canister call using `Cycles.add` (Motoko) or `msg_cycles_add` (Rust). The called canister receives the cycles as part of the message context and accepts them with `Cycles.accept`:

<Tabs syncKey="lang">
<TabItem label="Motoko">

```motoko
import Cycles "mo:core/Cycles";

persistent actor {
let target = actor ("rrkah-fqaaa-aaaaa-aaaaq-cai") : actor {
someMethod : () -> async ();
};

public func callWithCycles() : async () {
Cycles.add<system>(500_000_000);
await target.someMethod();
};
}
```

</TabItem>
<TabItem label="Rust">

```rust
use ic_cdk::update;

#[update]
async fn call_with_cycles() {
ic_cdk::api::call::msg_cycles_add(500_000_000u64);
let _: () = ic_cdk::call(
candid::Principal::from_text("rrkah-fqaaa-aaaaa-aaaaq-cai").unwrap(),
"someMethod",
(),
)
.await
.expect("call failed");
}
```

</TabItem>
</Tabs>

The calling canister uses cycles from its own balance, not from the cycles ledger. Top up the calling canister first using `icp canister top-up` or `icp cycles mint`.

### From the CLI or an agent

The CLI cannot attach cycles directly to a canister call. Two approaches address this:

**Top up the target canister first** (preferred when you control it): transfer cycles to the target canister using `icp canister top-up`, then call the method normally. The canister uses its own balance when the method runs.

```bash
# Transfer 1T cycles to the target canister
icp canister top-up rrkah-fqaaa-aaaaa-aaaaq-cai --amount 1T -n ic

# Then call the method as normal
icp canister call rrkah-fqaaa-aaaaa-aaaaq-cai someMethod '()' -n ic
```

**Proxy canister** (required when you need to attach cycles to a call and don't control the target): deploy a proxy canister that can forward calls with cycles attached.

```bash
# Deploy the proxy canister using the provided template
icp new proxy --template proxy
cd proxy
icp deploy -e ic

# Get the proxy canister ID
export PROXY_ID=$(icp canister status -e ic --id-only proxy)

# Call any canister through the proxy with cycles attached
icp canister call --proxy "$PROXY_ID" rrkah-fqaaa-aaaaa-aaaaq-cai someMethod '()' -n ic
```

The proxy canister template is available at [icp-cli-templates/proxy](https://github.com/dfinity/icp-cli-templates/tree/main/proxy). It deploys the [proxy-canister](https://github.com/dfinity/proxy-canister), which is automatically provisioned on local networks but must be deployed manually on mainnet.

## Multi-environment deployment

For production, use separate environments for staging and production to avoid accidentally affecting live canisters. Configure environments in `icp.yaml`:
Expand Down Expand Up @@ -495,6 +373,7 @@ icp canister top-up backend --amount 1T -n ic
- [Cycles costs reference](../../references/cycles-costs.md#cost-table): Exact cost tables per operation
- [Cycles](../../concepts/cycles.md): Why canisters pay for execution and how the cycles ledger works
- [Cycles ledger reference](../../references/system-canisters.md#cycles-ledger): Canister IDs and interface specification
- [Calls with attached cycles](../canister-calls/inter-canister-calls.md#calls-with-attached-cycles): attach cycles to an inter-canister call and accept them in the callee
- [Reproducible builds](reproducible-builds.md): Verify your WASM is trustworthy before deploying
- [icp-cli docs](https://cli.internetcomputer.org/0.2/reference/cli#icp-cycles): Full command reference for `icp cycles` and `icp canister top-up`

Expand Down
4 changes: 2 additions & 2 deletions docs/guides/chain-fusion/exchange-rates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,15 @@ The most important errors to handle explicitly:

## Testing from the CLI

The XRC requires cycles attached to the call, so you cannot call it directly from the CLI on mainnet. To test the integration from the terminal, use the [proxy canister pattern](../canister-management/cycles-management.md#calling-canisters-that-require-cycles): deploy a proxy canister that forwards the call with cycles attached.
The XRC requires cycles attached to the call, so you cannot call it directly from the CLI on mainnet. To test the integration from the terminal, use the [proxy canister pattern](../canister-calls/inter-canister-calls.md#attaching-cycles-from-the-cli): deploy a proxy canister that forwards the call with cycles attached.

On a local replica, note that the XRC fetches from live external exchanges via HTTPS outcalls, so local testing requires a connection to the internet and a subnet configured as type `system`.

## Next steps

- [Exchange rate canister concept](../../concepts/chain-fusion/exchange-rate-canister.md): how median aggregation and rate derivation work
- [Exchange rate canister reference](../../references/protocol-canisters.md#exchange-rate-canister-xrc): full Candid interface, all error types, and data sources
- [Calling canisters that require cycles](../canister-management/cycles-management.md#calling-canisters-that-require-cycles): proxy canister pattern for CLI testing
- [Calls with attached cycles](../canister-calls/inter-canister-calls.md#calls-with-attached-cycles): attach cycles to an outgoing call and use the proxy canister pattern for CLI testing
- [HTTPS outcalls](../../concepts/https-outcalls.md): how the XRC fetches external price data
- [Full Rust example](https://github.com/dfinity/examples/tree/master/rust/exchange-rates): complete Rust project with build configuration

Expand Down
Loading