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.
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
tenzroCLI. - 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
cantonscope (see API keys). The key determines which party a request acts as.
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 party1. Write the model
Create daml/ComputeJob.daml:
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:
daml build
# produces .daml/dist/compute-job-0.1.0.darHand 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
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:
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:
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:
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:
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:
modelHashbecomes a dataset hash,DeliverbecomesGrantAccess, 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
- DAML and Canton reference.
- Rent GPU capacity for the provider side: Compute rental.
- Put confidential runs behind attestation: TEE.