Skip to content

Call it from code

Every Calimero client does the same three things before your first call: it verifies the node’s TDX attestation against a signed mero-tee release, opens a sealed channel to the key that attestation binds, and signs the caller in, a person with a passkey or a machine with an API key (cal_…). There is no way to call an app without them, which is why you use a Calimero client rather than plain HTTP.

Client For Install
mero-js Browser and Node 18+ npm install @calimero-network/mero-js
calimero-client-py Python 3.9+ pip install --pre calimero-client-py
calimero-client Rust the calimero-client crate in core
meroctl The command line the installer from the Quickstart

apps/kv-store/app/ is a Vite and React app on mero-js’s CalimeroClient. Run it with meroctl local dev apps/kv-store (the development loop) and open http://localhost:5173.

import { CalimeroClient, browserPasskey } from '@calimero-network/mero-js';
// The local cluster. Its quotes are mock ones, trusted only because you say so.
const client = new CalimeroClient({ baseUrl: 'http://127.0.0.1:2501', policy: { allowMock: true } });
await client.signUp(browserPasskey(), 'ada'); // or client.signIn(browserPasskey())

For a Calimero Cloud cluster, CalimeroClient.connect reads the cluster’s nodes from the public directory and attests each under your policy before it sends anything:

const client = await CalimeroClient.connect({
cluster: 'eu-shared-1',
// Trust the signed mero-tee release the node names, from this one up.
policy: { release: { minReleaseVersion: '2.3.109' } },
});

The example does not write this by hand. It takes it from the apps repository’s shared packages: @calimero-apps/connect picks the target from the build (merod local under vite, Calimero Cloud under vite build), and @calimero-apps/passkey gives the sign-in screens and keeps the session (useSession in src/session.ts).

src/generated/client.ts is generated from the app’s ABI by meroctl app codegen, the app’s codegen script:

"codegen": "meroctl app codegen --abi ../logic/res/abi.json --lang ts --name kv-store -o src/generated"

It writes KvStoreClient: one typed method per app method, each sending a call (client.apps.call) or a view (client.apps.view) over the sealed channel, plus the app’s events and error codes as types. App.tsx builds one for the store that is open:

import { KvStoreClient } from './generated/client';
const kv = new KvStoreClient(client, appId, { after });

after runs after every call and view; the example uses it to store a session the client refreshed meanwhile. After a contract change, rebuild logic/res/abi.json (The ABI), run pnpm -F kv-store codegen, and tsc -b shows every call site to fix.

await kv.set({ key: 'a', value: '1' }); // a call: Promise<null>
const value = await kv.get({ key: 'a' }); // a view: Promise<string | null>
const count = await kv.len(); // a u64, so a string: "1"

kv.raw has the same methods answering whole: { result, index, events } for a call, { result, index } for a view. A refusal is thrown as a typed error: the app’s NotFound is an AppError, a trap a TrapError.

const stop = new AbortController();
for await (const { kind, data } of kv.events(0, stop.signal)) {
console.log(kind, data); // "Inserted" { key: "a", value: "1" }
}

kv.events(from, signal) is the instance’s event stream from log index from, then live, until signal aborts. The example refreshes its list of entries on each event, so a second member’s writes appear at once.

KvStoreClient is a thin layer. The same calls on CalimeroClient directly:

await client.apps.call({ app, method: 'set', args: { key: 'a', value: '1' } });
const { result } = await client.apps.view({ app, method: 'get', args: { key: 'a' } });
for await (const event of client.events(app)) console.log(event);

Creating an instance from code is client.apps.create: from a published package ({ name, package, version }), with initArgs for the app’s init and org for the organization it goes in.

from calimero_client_py import Client
client = Client("http://127.0.0.1:2501", allow_mock=True) # or Client.connect("eu-shared-1", tee_profiles=[...])
client.use_api_key("cal_...")
client.apps.call(app=app_id, method="set", args={"key": "a", "value": "1"})
print(client.apps.view(app=app_id, method="get", args={"key": "a"}).to_dict()["result"])
for e in client.events(app_id, start=0, limit=10, timeout=5):
print(e["kind"], e["data"])

meroctl app codegen --lang python (also swift, kotlin and openapi) generates a typed client for the other languages from the same ABI.

  • Sign-in: sign up and sign in with a passkey, refresh and sign out, add and remove passkeys (a person’s devices), create and revoke scoped API keys.
  • Instances: create one from a package release (with init arguments, an organization and an access rule), list, upgrade, change access, transfer, delete.
  • Calls: calls, views, calls that need a fresh passkey assertion (#[app::confirm] methods), and the instance’s event stream. A call carries an idempotency key, so a retry is answered once.
  • Blobs and presence: upload and download an instance’s blobs; publish and stream presence.
  • Organizations: create them, manage members, roles, capabilities and teams, invite and redeem.
  • Owner decisions: upgrade runs and approvals that need the owner’s passkey.

Full references: mero-js, calimero-client-py, core’s Call your app from a client and meroctl.

Next → Package & publish: turn the app into a signed release.