Your first flow
This page builds the smallest flow that is genuinely worth using TxFlow for: two transactions, where the second spends an output the first created.
If you only need one transaction, stop here and use QuickTx — it is simpler and this page will not make it better. Come back when you hit the problem below.
The problem TxFlow solves
Section titled “The problem TxFlow solves”Try chaining two transactions by hand and you meet this immediately:
// Transaction 1 — pays 5 ADA to a staging address. complete() returns once submitted.TxResult first = quickTxBuilder.compose(fundTx) .withSigner(SignerProviders.signerFrom(sender)) .complete();
// Transaction 2 — wants to spend the output transaction 1 just created.// But the backend does not know about that UTXO yet, so selection can't find it.completeAndWait() narrows this window but doesn’t remove it: its default wait is 60 seconds, and a
public indexer’s UTxO view can lag behind a confirmed transaction.
The output exists in a submitted transaction, but until the node has indexed it, a normal UTXO query cannot see it. Working around that by hand means polling, retrying, and reasoning about rollbacks — and getting it subtly wrong.
TxFlow handles it: you declare that step two consumes step one’s output, and the engine substitutes the concrete transaction hash and output index at run time.
1. Declare the flow
Section titled “1. Declare the flow”TxFlow definitions are YAML. Save this as funding-flow.yaml:
api_version: txflow.cardano-client.dev/v1alpha1kind: TxFlowmetadata: name: funding version: 1.0.0spec: network: preview parameters: staging: type: address required: true beneficiary: type: address required: true execution: confirmation: min_confirmations: 1 check_interval: 2s timeout: 2m steps: # Step 1 — pay into a staging address, and publish that output as "held". - id: fund outputs: held: select: output_index: 0 expect: exactly_one transaction: tx: from_ref: account://sender intents: - type: payment address: '${{ inputs.staging }}' amounts: - unit: lovelace quantity: 5000000 context: signers: - ref: account://sender scope: payment
# Step 2 — spend exactly that output. - id: forward needs: [fund] transaction: tx: from_ref: account://staging inputs: - type: collect_from refs: - flow_output: step: fund output: held intents: - type: payment address: '${{ inputs.beneficiary }}' amounts: - unit: lovelace quantity: 3000000 context: fee_payer_ref: account://staging signers: - ref: account://staging scope: paymentThree things carry the whole idea:
outputs.heldpublishes step one’s output 0 under a flow-local name.output_indexis zero-based, andexpect: exactly_onemeans execution fails loudly if that output is not there.flow_outputin step two’sinputsis the consumption edge. At run time TxFlow replaces it with the real transaction hash and index from this execution.needs: [fund]declares the dependency. On its own it records and validates the ordering — it does not spend anything. Theflow_outputreference is what actually moves the UTXO.
The step that spends a flow_output must declare a sender with from_ref and carry a signer for
that output’s payment key, here the staging account. from_ref supplies only the address, not a
signature. Preflight doesn’t build transactions, so a mistake here surfaces only when the step runs,
after earlier steps have moved funds.
Note what the file does not contain: no addresses baked in, no keys, no network endpoint.
account://sender is a reference resolved by your application, and ${{ inputs.* }} are bound at
run time. The definition is reusable across environments.
2. Run it
Section titled “2. Run it”FlowRuntime is the managed entry point — it owns the executors, wires the four backend
suppliers, and holds the signer registry, so a first flow does not start with a page of setup:
import com.bloxbean.cardano.client.txflow.FlowRuntime;import com.bloxbean.cardano.client.txflow.TxFlow;import com.bloxbean.cardano.client.txflow.codec.FlowParseOptions;import com.bloxbean.cardano.client.txflow.codec.FlowParseResult;import com.bloxbean.cardano.client.txflow.codec.TxFlowCodec;import com.bloxbean.cardano.client.txflow.exec.FlowExecutionHandle;import com.bloxbean.cardano.client.txflow.exec.FlowExecutionRequest;import com.bloxbean.cardano.client.txflow.exec.FlowExecutionResult;import com.bloxbean.cardano.client.txflow.model.FlowBindings;
String source = Files.readString(Path.of("funding-flow.yaml"), StandardCharsets.UTF_8);
FlowParseResult parsed = TxFlowCodec.standard() .parse(source, FlowParseOptions.serverDefaults());if (parsed.hasErrors()) { throw new IllegalArgumentException("Invalid flow: " + parsed.getDiagnostics());}TxFlow definition = parsed.requireFlow();
FlowBindings bindings = FlowBindings.builder() .put("staging", stagingAccount.baseAddress()) .put("beneficiary", receiverAddress) .build();
try (FlowRuntime runtime = FlowRuntime.builder(backendService) .account("account://sender", senderAccount) .account("account://staging", stagingAccount) .build()) {
FlowExecutionRequest request = FlowExecutionRequest.builder(definition) .executionId("funding-0001") .bindings(bindings) .build();
FlowExecutionHandle handle = runtime.engine().start(request); FlowExecutionResult result = handle.await();
// await() doesn't throw on a failed flow, so check the state and the error System.out.println(result.state() + " error=" + result.error()); result.steps().forEach(step -> System.out.println(step.getStepId() + " " + step.getTransactionHash()));}FlowRuntime implements AutoCloseable, so try-with-resources shuts its executors down for you.
It creates an ordinary FlowEngine, and runtime.engine() returns it.
account(...) registers the signing key against the account://sender reference. Note what is
not here: no FlowResourceCatalog and no FlowExecutionPolicy. Both are optional — with no
catalog the compiler skips resource resolution entirely — which is fine for a script you control.
A server accepting flow documents from elsewhere should add both, so the network and the resource
prefixes a document may touch are constrained. Getting started shows
that setup.
FlowRuntime doesn’t accept a catalog, policy, store or script registry; use
FlowEngine.builder(...) for those. Without a catalog there’s no spending identity, so concurrent
flows from one account aren’t serialized, and spec.network isn’t enforced: a preview document
runs against whatever backend you pass.
3. Check before you run
Section titled “3. Check before you run”preflight compiles and policy-checks every step without submitting anything:
FlowCompilationResult preflight = runtime.engine().preflight(request);if (preflight.hasErrors()) { throw new IllegalArgumentException("Preflight failed: " + preflight.getDiagnostics());}It catches an unbound parameter, a flow_output naming a step that does not publish it, and a
step ordered after its consumer — all before a transaction reaches the chain. Run it whenever a
flow comes from outside your codebase.
start(...) deliberately compiles again rather than trusting an earlier preflight result, so a
definition that changed in between cannot slip through.
What you get beyond the chaining
Section titled “What you get beyond the chaining”The same execution already gives you:
- Confirmation tracking —
min_confirmationsis honoured per step; in the defaultSEQUENTIALmode the flow doesn’t advance on a merely submitted transaction. - Honest rollback handling — while a step waits for confirmation, a transaction that disappears
isn’t treated as success or failure: the flow ends
RECOVERY_REQUIRED. To detect and act on rollbacks, addspec.execution.rollbackand a chain adapter that declares authoritative absence; see Policies.FlowRuntimecan’t supply such an adapter. - Idempotency — add
.idempotency("payments", "order-18473")to the request and a repeat call to this engine attaches to the existing execution instead of paying twice. OnFlowRuntimethat claim lives in memory, so it’s lost on restart. Surviving a restart needs a durable store. - Honest uncertainty — an ambiguous submission is reported as uncertain, never as a false success or a false failure.
Where to go next
Section titled “Where to go next”| You want | Page |
|---|---|
| The full setup with explicit resources and policy | Getting started |
| Every document field and output-reference form | Portable authoring |
| Engine construction, executors, cancellation | Execution engine |
| Retries, events, confirmation and rollback behaviour | Policies, results & observability |
| Surviving a process restart | Durable runtime |
| Many transactions over time, not one workflow | TxStream |