Workflows · Explanation
Cross-VM composition
A workflow can use one execution environment to coordinate work performed by another while keeping the result inside one Singularity transaction. The useful property is shared transactional state: a later call can read an earlier call's staged write, but those writes become canonical only if the outer transaction succeeds. This makes a multi-step application different from a client sending several independent transactions.
The shared state model does not make every VM pair interchangeable. Each call needs an implemented adapter, a compatible argument format, declared account authority and sufficient compute. Bombil is a VM-level financial environment, but its current onebook_* dispatch is not a target in the generic workflow router. ZK programs likewise use their own execution route.
The example below follows an inspected integration fixture to explain shared visibility and rollback. For a first hands-on client result, use the offline signing lesson; for the transaction envelope and receipt contract, use RPC reference.
Follow a concrete transaction
The composition integration fixture deploys an EVM counter and a WASM program that calls it, then installs a Move module with a native EVM-call adapter. Starting from counter zero:
- A WASM call invokes CuEVM and stages counter one. Reading the underlying committed state still returns zero.
- A Move call invokes CuEVM in the same transaction. It sees the journal's one and stages two.
- The outer transaction commits once. The committed counter becomes two.
- A later transaction advances the counter to three.
- In a failing transaction, a nested call stages four before the WASM parent traps. The staged changes are discarded, committed state remains three, and consumed compute remains nonzero.
This is a GPU-dependent integration test marked ignored for ordinary runs. Its explicitly installed Move native table is part of the fixture; it does not prove that every generic incoming Move call has that adapter available. This documentation run inspected the test and did not execute the CUDA workload. Source and assertions.
Explore the transaction sequence.
An unsigned workflow plan preserves client authority
The eto_buildWorkflow RPC method is a pure instruction builder. It does not sign, submit or mutate state. Supply a request object, or a one-element array containing that object, with a legs array. It returns:
| Result field | Meaning |
|---|---|
programId | Workflow router program address |
accounts | Ordered public keys with isSigner and isWritable flags |
data | Encoded router instruction as a 0x hex string |
requiredSigners | Account public keys that need signatures |
A client converts data to bytes, creates an instruction with the returned accounts, adds it to a transaction, supplies the fee payer and recent blockhash, and gathers the required signatures. The builder's signer list describes its instruction accounts; a fee payer may add another signature requirement. Inspect the target programs and account privileges before signing. Builder contract.
Each leg uses these inputs:
| Input | Contract |
|---|---|
targetVm | Case-insensitive evm, wasm, bpf, move or svm |
targetId | Strict 0x hex: 20 bytes for EVM, 32 for other targets |
selectorOrEntry or selector | Optional hex bytes; do not supply both aliases |
calldata or args | Optional hex bytes; do not supply both aliases |
value | Unsigned 128-bit integer or decimal string, default zero; currently must be zero |
accounts | Per-leg account capabilities, at most 64 entries |
programData | Optional upgradeable BPF program-data address; valid only for BPF |
The current zero-value restriction matters for EVM applications: this builder cannot attach a native EVM value transfer to a workflow leg because that balance persistence path is not enabled. Encoding a positive value is a validation error, not a promise to settle it later.
Top-level declaredAccounts add accounts to the outer instruction without automatically granting them to every leg. Repeated public keys are stably deduplicated across legs. The outer meta receives the union of requested privileges, while each leg retains only its own requested capabilities. A BPF target program is added read-only automatically; supply its program-data account when the upgradeable deployment requires it. The builder caps the instruction account list at 254 to reserve space for the program and payer in the enclosing transaction. Compilation and validation.
Order and return-data dependencies
The router has a legacy encoding and an account-bearing encoding. New builder output uses opcode 1, the account-bearing form. It supports at most 16 legs. A leg can splice bytes returned by an earlier leg into its calldata, allowing one call's output to determine another call's input.
Dependencies always point backwards in the declared order. A dependency records the source leg, source offset and length, and destination offset. The decoder and splice operation reject forward references and out-of-bounds regions. This is ordered execution with explicit data dependencies, not a scheduler that discovers independent branches or runs arbitrary cyclic calls.
The wire format uses length-prefixed byte fields, bounded to 64 KiB each, and at most 16 dependencies per leg. Account references are indices into the enclosing workflow instruction's account list. They are not direct transaction-wide indices. Runtime resolution follows instruction-local index to compiled transaction index to actual account and rejects invalid or aliased references. Encoding, runtime resolution.
Account authority survives the boundary
A callee cannot gain a signature or writable account merely by setting a flag in its request. The transaction context is initialized from the outer transaction's verified signers and declared writable accounts. Requested child capabilities must be contained in those declarations. The dispatcher checks the resolved accounts again rather than trusting the builder.
The workflow's EVM caller is derived from the verified fee payer using the canonical last-20-byte mapping. WASM receives the fee payer's 32-byte key. This prevents a caller from selecting an unrelated unsigned account as msg.sender. The router also passes the actual consensus slot into each call, so nested code does not receive a fabricated zero height.
These checks provide authority attenuation. They do not replace a program's own ownership checks, balance rules, signature domains or application authorization. A legitimately writable account can still be the wrong account for a particular contract. Transaction authority, caller binding.
Roll back writes, retain work already consumed
The transaction context owns the shared journal and compute meter. A call checkpoint clones the journal, not the compute counter. On a child failure, rollback restores staged state while leaving execution cost consumed. Otherwise a contract could repeatedly perform expensive failed work and recover its budget.
Frame depth is derived from the existing parent depth as well as the requested depth; a callee cannot reset nesting to zero. The maximum depth constant is four, and entry rejects a resulting depth at or above that limit. The router's per-leg gas ceiling is 30,000,000, but that is still subject to the shared transaction context's remaining compute. It is not a fresh unlimited budget for each leg. Depth, checkpoints and metering.
Errors have different consequences. An ordinary revert, rejected account capability, exhausted budget or depth violation produces a failed transaction path. A state-read, journal or engine defect represents an execution invariant failure and follows the canonical fatal-error handling; treating it as an ordinary user revert could let validators accept different state. The execution pipeline explains that distinction.
Adapter availability determines which composition works
Use the VM chapters to check the exact direction you need. WASM's EVM and WASM calls have host interfaces; its SVM call supports a narrower System transfer path. Incoming generic Move execution does not automatically install the same native table as the top-level or specialized test path. EVM host precompiles do not imply that every nested CuEVM CALL reaches a Rust host implementation.
For foreign-chain work, HILSA reads consume locally committed evidence; a contract is not issuing an arbitrary external RPC request during consensus. A HILSA outbox write can be part of local execution, but remote delivery and settlement happen later. Local transaction atomicity does not roll back already executed foreign-chain effects.
A practical integration should first prove its exact adapter path with a focused fixture, then test staged visibility, privilege rejection, parent failure and budget exhaustion. Follow the transaction lifecycle for submission and receipt behavior, and durability and recovery for what happens after the outer state change is accepted.
Transaction lifecycle
Follow signed instructions from client construction and admission to ordered execution, receipts, retries and persistent state.
Durability & recovery
Follow a finalized block through the WAL, durable height, QMDB acknowledgement and validated restart, including failure windows and backup requirements.