Skip to content

Deployment Guide

This guide covers the signing flow, deploy payload format, contract upgrades, and how to verify a deployment.

Signing Flow

All state-changing operations (deploy, upgrade, call) require a signed transaction. Dilithia uses ML-DSA-65 (a post-quantum digital signature algorithm) for transaction signing. Here is the step-by-step flow:

  1. Load the secret key -- the hex-encoded ML-DSA-65 secret key is read from --secret-key or the DILITHIA_SECRET_KEY environment variable
  2. Derive the public key -- the public key is extracted from the last MLDSA65_PK_LEN bytes of the secret key
  3. Derive the sender address -- address_from_pk(pk_bytes) computes the on-chain address
  4. Fetch the nonce -- unless --nonce is provided, the CLI fetches the current nonce via GET <RPC>/nonce/<address>
  5. Build the canonical payload -- a JSON object with alphabetically sorted keys (see below)
  6. Sign the payload -- sign_mldsa65(payload_bytes, sk_bytes) produces the signature
  7. Submit the transaction -- the full request body (including bytecode/args, signature, public key) is POSTed to the appropriate RPC endpoint

Canonical Payload Format

The canonical payload is the exact byte sequence that gets signed. Keys are always sorted alphabetically to ensure deterministic serialization.

Deploy / Upgrade Payload

{
  "bytecode_hash": "<hex hash of the hex-encoded WASM bytes>",
  "chain_id": "dilithia",
  "from": "<sender address>",
  "name": "<contract name>",
  "nonce": 0
}

Note

The bytecode_hash is computed over the hex-encoded string of the WASM bytes, not the raw binary. This matches the hash_hex() function from dilithia_core.

Call Payload

{
  "args": { ... },
  "chain_id": "dilithia",
  "contract": "<contract name>",
  "from": "<sender address>",
  "method": "<method name>",
  "nonce": 0
}

Full Deploy Request Body

The complete request body built by the CLI for deploy and upgrade operations:

{
  "name": "<contract name>",
  "bytecode": "<hex-encoded WASM bytes>",
  "from": "<sender address>",
  "alg": "mldsa65",
  "pk": "<hex-encoded public key>",
  "sig": "<hex-encoded signature>",
  "nonce": 0,
  "chain_id": "dilithia",
  "version": 1
}

Deploying with the CLI

dilithia-contract deploy \
  --name my_token \
  --wasm target/wasm32-unknown-unknown/release/my_token.wasm \
  --rpc http://localhost:8000/rpc \
  --secret-key $DILITHIA_SECRET_KEY

On success, the CLI returns the raw response body from the target node. The exact fields depend on the node version and endpoint behavior.

Deploying with the SDK

You can also deploy contracts programmatically using the Dilithia SDKs. The CLI itself delegates request-building to dilithia-sdk-rust, so canonical payload construction and endpoint selection stay aligned with that SDK.

Upgrading Contracts

The upgrade command replaces the on-chain WASM bytecode for an already-deployed contract:

dilithia-contract upgrade \
  --name my_token \
  --wasm target/wasm32-unknown-unknown/release/my_token.wasm \
  --rpc http://localhost:8000/rpc \
  --secret-key $DILITHIA_SECRET_KEY

Key points about upgrades:

  • Admin-only -- only the original deployer (the address that submitted the initial deploy transaction) can upgrade a contract
  • State preserved -- the contract's storage is not cleared during an upgrade; the new WASM code operates on the existing state
  • Same signing flow -- the canonical payload format is identical to deploy; the CLI swaps in the SDK upgrade request builder instead of the deploy request builder

Breaking storage changes

If your new contract code changes how it reads or writes storage keys, existing data may become unreadable or misinterpreted. Plan storage migrations carefully.

Verifying a Deployment

After deploying or upgrading, verify that the contract is live and functioning:

Inspect the extracted ABI

dilithia-contract extract-abi \
  --wasm target/wasm32-unknown-unknown/release/my_token.wasm \
  --output json

This returns the method list embedded into the compiled WASM by the vendored contract SDK macro.

Call a Method

dilithia-contract call \
  --contract my_token \
  --method init \
  --args '{"total_supply":1000000,"deployer":"0xabc..."}' \
  --rpc http://localhost:8000/rpc \
  --secret-key $DILITHIA_SECRET_KEY \
  --wait

Query State

dilithia-contract query \
  --contract my_token \
  --method total_supply \
  --rpc http://localhost:8000/rpc

If ABI extraction, a state-changing call, and a read-only query all return the expected results, your contract is successfully deployed and operational.