Skip to content
Tenzro
← All tutorials
Tutorial · Execution and bridges

Model a multi-party compute contract on Canton

Write a DAML contract between a client, a compute provider and a verifier, then propose, accept, deliver and settle a job through the Tenzro DAML runtime.

Advanced45 min

Some compute jobs involve parties that each need their own view of the deal and nothing more: a client that commissions work, a provider that runs it, and a verifier that must be able to confirm what was agreed and delivered. DAML models this directly. Each contract names its signatories and observers, and every party sees only the contracts it is a stakeholder on.

Tenzro Network 1 runs a DAML runtime alongside the EVM and SVM, connected to Canton synchronizers through the Canton JSON Ledger API. In this tutorial you write a small DAML model for a compute job, then take it through proposal, acceptance, delivery and settlement.

Prerequisites

  • The Daml SDK, to build the model.
  • The tenzro CLI.
  • Access to a Canton-connected Tenzro node. Its operator uploads your package, allocates a party for each participant and issues each one a Tenzro API key with the canton scope (see API keys). The key determines which party a request acts as.
bash
export RPC=https://rpc.tenzro.xyz
export CLIENT_KEY=tnz_...     # acts as the client party
export PROVIDER_KEY=tnz_...   # acts as the provider party
export VERIFIER_KEY=tnz_...    # acts as the verifier party

1. Write the model

Create daml/ComputeJob.daml:

haskell
module ComputeJob where

template JobProposal
  with
    client     : Party
    provider   : Party
    verifier   : Party
    modelHash  : Text   -- hash of the model weights to run
    inputHash  : Text   -- hash of the input dataset
    priceWei   : Text   -- agreed price in TNZO wei
  where
    signatory client
    observer provider, verifier

    choice Accept : ContractId ComputeJob
      controller provider
      do create ComputeJob with ..

template ComputeJob
  with
    client     : Party
    provider   : Party
    verifier   : Party
    modelHash  : Text
    inputHash  : Text
    priceWei   : Text
  where
    signatory client, provider
    observer verifier

    choice Deliver : ContractId Delivered
      with
        resultHash     : Text  -- hash of the output
        attestationRef : Text  -- reference to the TEE attestation for the run
      controller provider
      do create Delivered with ..

template Delivered
  with
    client         : Party
    provider       : Party
    verifier       : Party
    modelHash      : Text
    inputHash      : Text
    priceWei       : Text
    resultHash     : Text
    attestationRef : Text
  where
    signatory client, provider
    observer verifier

    choice Settle : ()
      with
        paymentTx : Text  -- Tenzro transaction hash of the payment
      controller client
      do return ()

The model records hashes, not data. The dataset and the output stay with the parties; the contract binds everyone to exactly which model, input and result were involved. The verifier is an observer on every contract, so it can confirm each stage without being able to act.

2. Build the package

In daml.yaml, name the package compute-job, then build:

bash
daml build
# produces .daml/dist/compute-job-0.1.0.dar

Hand the DAR to your node operator. They upload it (tenzro canton upload-dar), allocate the three parties and bind each party to one of the API keys above. Uploading packages and allocating parties are operator actions on a Canton participant.

3. The client proposes the job

bash
tenzro canton submit --rpc $RPC --api-key $CLIENT_KEY \
  --command-type create \
  --template '#compute-job:ComputeJob:JobProposal' \
  --create-arguments '{
    "client":   "<client-party>",
    "provider": "<provider-party>",
    "verifier": "<verifier-party>",
    "modelHash": "sha256:5f1c...",
    "inputHash": "sha256:a93e...",
    "priceWei":  "25000000000000000000"
  }'

The response includes the new contract ID. Only the client signed; the provider and verifier can see the proposal but it binds nobody else yet.

4. The provider accepts

The provider finds the proposal and exercises Accept:

bash
tenzro canton contracts --rpc $RPC --api-key $PROVIDER_KEY \
  --template '#compute-job:ComputeJob:JobProposal'

tenzro canton submit --rpc $RPC --api-key $PROVIDER_KEY \
  --command-type exercise \
  --template '#compute-job:ComputeJob:JobProposal' \
  --contract-id <proposal-contract-id> \
  --choice Accept \
  --choice-argument '{}'

Acceptance archives the proposal and creates a ComputeJob signed by both client and provider, atomically. There is no state in which the proposal is accepted but the job does not exist.

5. The provider delivers

The provider runs the job, preferably inside a TEE so the run carries attestation evidence (see TEE), then records the result hash and the attestation reference:

bash
tenzro canton submit --rpc $RPC --api-key $PROVIDER_KEY \
  --command-type exercise \
  --template '#compute-job:ComputeJob:ComputeJob' \
  --contract-id <job-contract-id> \
  --choice Deliver \
  --choice-argument '{"resultHash":"sha256:0b7d...","attestationRef":"<attestation id>"}'

6. The client pays and settles

The client checks the result against the delivered hash, pays the provider on Tenzro (in TNZO or a stablecoin, see Settlement), and closes the contract with the payment transaction hash:

bash
tenzro canton submit --rpc $RPC --api-key $CLIENT_KEY \
  --command-type exercise \
  --template '#compute-job:ComputeJob:Delivered' \
  --contract-id <delivered-contract-id> \
  --choice Settle \
  --choice-argument '{"paymentTx":"0x..."}'

7. The verifier confirms

The verifier's key reads everything it observes, and nothing else:

bash
tenzro canton contracts --rpc $RPC --api-key $VERIFIER_KEY \
  --template '#compute-job:ComputeJob:Delivered'

tenzro canton get-transaction --rpc $RPC --api-key $VERIFIER_KEY --update-id <update-id>

A party that is not a stakeholder, such as a second provider bidding for other jobs, sees none of these contracts.

Variations

  • Replace the compute job with a data licence: modelHash becomes a dataset hash, Deliver becomes GrantAccess, and the verifier becomes the data owner's compliance function.
  • Coordinate the same job across EVM contracts and several providers with a saga workflow: Run a workflow across the EVM, SVM and DAML runtimes.

Next steps