Skip to content

Call another context

Task: have a method in one context invoke a method in another context.

A cross-context call (xcall) is one-way and asynchronous: you queue a call to a target context’s method, and the node delivers it. The model — and why origin is trustworthy — is in the cross-context reference. This guide is the three moving parts from apps/xcall-example.

env::xcall(context_id, function, params) queues a call. Encode arguments as the JSON the target method expects:

use calimero_sdk::{app, ContextId};
fn xcall_to(&mut self, target_context: ContextId, method: &str) -> app::Result<()> {
let current_context = ContextId::from(calimero_sdk::env::context_id());
#[derive(calimero_sdk::serde::Serialize)]
#[serde(crate = "calimero_sdk::serde")]
struct Params {
from_context: ContextId,
}
let params = calimero_sdk::serde_json::to_vec(&Params {
from_context: current_context,
})?;
calimero_sdk::env::xcall(target_context.as_ref(), method, &params);
Ok(())
}

A method is only reachable by xcall if you mark it #[app::xcall]. The node refuses to dispatch a cross-context call to any method without this attribute — so undecorated methods stay private to direct callers.

env::xcall_origin() returns the calling context id — set by the node, so it cannot be forged. It is None for a direct (non-xcall) call. Use it to reject direct calls and to confirm the caller is who the payload claims:

#[app::xcall]
pub fn pong(&mut self, from_context: ContextId) -> app::Result<()> {
let origin = calimero_sdk::env::xcall_origin().map(ContextId::from);
let Some(origin) = origin else {
app::bail!("pong is xcall-only: no cross-context origin (direct call rejected)");
};
if origin != from_context {
app::bail!(
"xcall provenance mismatch: node-set origin {} != claimed from_context {}",
origin,
from_context
);
}
self.counter.increment()?;
Ok(())
}

Putting the three parts together — dispatch queues the call, the node delivers it, and the gated entry point verifies the node-set origin before doing any work. The call is one-way: nothing returns to the caller.

Terminal window
# from the source context, ping the target's `pong` entry point
meroctl --node node1 call ping \
--context <source-context-id> --args '{"target_context": "<target-context-id>"}'

The two contexts must run the same application and share a namespace.