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

Run a workflow across the EVM, SVM and DAML runtimes

Use one native TNZO balance from all three runtimes, move value between VM views, and coordinate a multi-step compute job as a saga workflow with per-step escrow and an on-chain receipt.

Intermediate30 min

The Tenzro Ledger runs three execution environments side by side: an EVM, an SVM and a DAML runtime. They are not three chains joined by bridges. There is one native TNZO balance per account, and each runtime sees it through its own view: an ERC-20 pointer on the EVM, an SPL token on the SVM, and a holding on DAML. Transactions execute in parallel with Block-STM and blocks are produced on a deterministic schedule.

In this tutorial you read one balance through all three views, move value between VM addresses, and coordinate a multi-step job (fetch a dataset, run inference, record the result) as a saga workflow that pays each step from escrow and ends with a receipt on chain.

Prerequisites

  • A funded account (transfer TNZO to its address from another wallet or account) and its DID.
  • curl and jq.
  • The DIDs of the parties doing the work, for example a storage provider and an inference provider.

A small helper keeps the calls short:

bash
export RPC=https://rpc.tenzro.xyz
rpc() { curl -s $RPC -H 'content-type: application/json' \
  -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$1\",\"params\":$2}" | jq .result; }

export ME=0x<your-address>
export MY_DID=did:tenzro:human:...

1. Read one balance through three views

bash
rpc tenzro_getTokenBalance "{\"address\":\"$ME\",\"token\":\"TNZO\"}"
json
{
  "address": "0x...",
  "native":       { "balance_wei": "25000000000000000000", "decimals": 18 },
  "evm_wtnzo":    { "balance_wei": "25000000000000000000", "decimals": 18 },
  "svm_wtnzo":    { "balance_base_units": "25000000000", "decimals": 9 },
  "daml_holding": { "amount_wei": "25000000000000000000", "decimals": 18 }
}

All four figures are the same funds. The SVM view uses 9 decimals, as SPL tokens do, so it shows the same amount at a different scale. Spending from any view debits the one native balance, which is why there is nothing to wrap, lock or reconcile.

The standard Ethereum call returns the same number:

bash
rpc eth_getBalance "[\"$ME\",\"latest\"]"

2. Move value to another VM address

To pay an SVM program's account, or a party on DAML, transfer between VM addresses. This is an owner method: it moves money, so the request must carry a signature from the paying account (see RPC access). With the TypeScript SDK and your passkey signer configured:

ts
const res = await client.token.crossVmTransfer({
  token: "TNZO",
  amount: "5000000000000000000",   // 5 TNZO in wei
  from_vm: "evm",
  to_vm: "svm",
  from_address: ME,
  to_address: "<recipient SVM address>",
});
console.log(res);
json
{ "token": "TNZO", "amount": "5000000000000000000", "from_vm": "evm", "to_vm": "svm", "status": "transferred" }

The transfer is atomic: it either lands in the recipient's view or does not happen.

3. Open a saga workflow

Multi-party jobs rarely finish in one transaction. A saga workflow is an ordered list of steps, each executed by a named party, verified, and either completed or compensated. Open one for a three-step job:

bash
rpc tenzro_workflowOpen '{
  "workflow_id": "job-2026-10-01-001",
  "orchestrator_did": "'$MY_DID'",
  "participants": ["did:tenzro:machine:storage...", "did:tenzro:machine:inference..."],
  "saga_steps": [
    { "id": "fetch-dataset", "executor_did": "did:tenzro:machine:storage...",   "compensation": "release-escrow" },
    { "id": "run-inference", "executor_did": "did:tenzro:machine:inference...", "compensation": "release-escrow" },
    { "id": "record-result", "executor_did": "'$MY_DID'",                       "compensation": "none" }
  ]
}'

The node returns the workflow with every step pending and the workflow open.

4. Execute a step with escrow

When a provider starts a step, fund its escrow. The payment is held in a vault and released to the payee only when the step verifies. Funding escrow moves money, so the request is signed by the payer's account:

bash
rpc tenzro_workflowStepExecute '{
  "workflow_id": "job-2026-10-01-001",
  "step_idx": 0,
  "escrow_amount": "2000000000000000000",
  "payer": "'$ME'",
  "payee": "0x<storage-provider-address>"
}'

Pass an idempotency_key (a 32-byte hex value you derive from the workflow, step and payload) to make retries safe: a repeated call returns the first result instead of running the step twice.

5. Verify the step

When the provider delivers, mark the step verified with the witnesses you require, for example the provider's signed delivery receipt:

bash
rpc tenzro_workflowStepVerify '{
  "workflow_id": "job-2026-10-01-001",
  "step_idx": 0,
  "witness_signatures": ["<signed delivery receipt>"],
  "outcome_score": 100
}'
json
{ "workflow_id": "job-2026-10-01-001", "step_idx": 0, "status": "verified" }

Verification releases the step's escrow to the payee. The outcome_score feeds the executor's ERC-8004 reputation, so good work builds a public track record.

If a step fails, compensate it instead. Compensation refunds the step's escrow to the payer; with cascade it also compensates every earlier executed step:

bash
rpc tenzro_workflowStepCompensate '{"workflow_id":"job-2026-10-01-001","step_idx":1,"cascade":true}'

Repeat steps 4 and 5 for run-inference and record-result.

6. Finalise and read the receipt

bash
rpc tenzro_workflowFinalize '{"workflow_id":"job-2026-10-01-001"}'
rpc tenzro_getWorkflowReceipt '{"workflow_id":"job-2026-10-01-001"}'

Finalising succeeds only when every step is verified or cleanly compensated. It writes a workflow receipt on chain that any participant, reviewer or downstream contract can read. The CLI has the same surface:

bash
tenzro workflow get-saga --workflow-id job-2026-10-01-001 --rpc $RPC
tenzro workflow list-by-participant did:tenzro:machine:storage... --rpc $RPC

7. Record the outcome on DAML (optional)

Counterparties that run their own processes on DAML can receive the workflow receipt as a DAML contract. Mirroring a workflow to a Canton synchronizer is done by the operator of a Canton-connected node; see Multi-party workflow on Canton to model the job itself in DAML.

Next steps