Skip to content

Packaging & signing your app

Once your app compiles, it still has to become something a node can install. That deliverable is an .mpk bundle: a single archive carrying your calimero:app component, a manifest.json, and an Ed25519 signature. This page walks the builder workflow that produces one.

The signing tool is mero-sign (tools/mero-sign/); scripts/pack-component.py writes the manifest, signs it through mero-sign and packs the archive. The node side — how a bundle is parsed, verified, and trusted on install — lives in Upgrades & migration; this page does not duplicate that detail.

  1. Build the component.

    calimero-app-build compiles your crate for wasm32-wasip2 and turns it into a calimero:app component, with the ABI the SDK’s macros embed (its calimero:abi section):

    Terminal window
    scripts/install-mero-app-build.sh # once: puts mero-app-build on PATH
    mero-app-build apps/my-app --out my-app.wasm

    A WASM core module (an SDK 0.12 app) is refused by the node (400 UNSUPPORTED_BUNDLE).

  2. Generate a signing key (one-time).

    mero-sign generate-key creates an Ed25519 keypair and derives your signer id — a did:key identifier built from the public key (the 0xed01 ed25519-pub multicodec tag plus the 32-byte key, base58btc-encoded under the did:key: prefix).

    Terminal window
    mero-sign generate-key --output my-signing-key.json
    # Generated new keypair: my-signing-key.json
    # signerId: did:key:z6Mkrb81NKkv7Mw4dZAWA5PTuwBhbq9u9eCPuby3icBUdirg

    The key file is JSON with base64url-encoded privateKey and publicKey plus the derived signerId. Keep it secret and out of version control — it is your update authority (see below).

  3. Write and sign the manifest, and create the .mpk.

    scripts/pack-component.py writes manifest.json (version, package, appVersion, minRuntimeVersion and the app.wasm artifact with its SHA-256 and size), has mero-sign sign sign it, and packs manifest.json and app.wasm into a gzip-compressed tar. mero-sign derives the signerId from the key, refuses to sign if minRuntimeVersion is absent (a signer must not rewrite the document it signs), canonicalizes the manifest with RFC 8785 (JCS), signs the domain-tagged SHA-256 of those canonical bytes, and writes a signature object back into the manifest.

    Terminal window
    python3 -I scripts/pack-component.py --wasm my-app.wasm --package com.example.my-app \
    --app-version 1.0.0 --key my-signing-key.json --out dist/com.example.my-app-1.0.0.mpk
    # omit --key to sign with the development key

    An app with compute methods also builds a compute component (mero-app-build <crate> --out app.wasm --compute-out compute.wasm). Pass it with --compute compute.wasm: the manifest then declares it as compute, and the .mpk carries it as compute.wasm. Without it a release cannot run compute, and neither can a raw .wasm upload.

  4. Install with meroctl.

    Terminal window
    meroctl --node node1 app install --path dist/com.example.my-app-1.0.0.mpk

    The node reads manifest.json straight out of the archive and verifies the signature before admitting the app. Installing the bundle is what produces an application: the manifest’s package and signerId fix the application id.

For a bundle, the application id is derived from the package and the signerId together — not from the bytecode. That makes the application id version-stable: a new release signed by the same key under the same package keeps the same application id and is recognized as an upgrade of the existing app.

The corollary is the rule to remember: a new signed version must use the same signer key. Sign a new release with a different key and the node computes a different application id — it is a different application, not an upgrade, and existing contexts will not pick it up. Treat the signing key as the long-lived identity of your app and guard it accordingly.