Skip to main content
Validation, lowering, simulation, and compilation each enforce rules introduced in the previous three pages: Flow shape, the resource model, and ref grammar.
A Flow is declarative: it describes what the transaction should do, not how to produce the bytecode. POST /compose turns the Flow JSON into a transactionRequest your wallet can sign. This page describes the user-observable behaviour: what the service checks, what it simulates, and what it returns.

What happens when you call POST /compose

1

Shape validation

The Flow JSON is validated against the wire-format schema: required fields, types, and ref shape. Malformed input is rejected with a decode_error (HTTP 400) before anything else happens.
2

Semantic validation

Every op and guard name must be registered, every bound port must exist and match its expected kind (resource vs. handle), and every ref must resolve to a node or input declared in the same flow. Failures surface as validation_error, linearity_error, or resolve_error.
3

Runtime input resolution

The run.inputs values you passed are resolved against the Flow’s declared inputs. A plain integer or hex string is used verbatim; a materialiser descriptor (for example directDeposit or balanceOf) is expanded into the concrete amount and any preconditions that need to hold at execution time. See the materialisers catalog.
4

Amount preparation (when needed)

When a node depends on a value that can only be known by reading on-chain state (for example, the current share price of a vault), the service resolves those amounts from current on-chain state before producing calldata.
5

Lowering to executable bytecode

Each high-level op (for example lifi.swap, lifi.zap) is lowered into the routing and transfer calls the VM actually executes. Guards are attached to the nodes they protect. A no_route_error is raised if the routing layer cannot find a path for a zap edge.
6

Sweeping

When run.sweepTo is set, transfers are appended to move every terminal resource (tokens held by the execution proxy after the flow runs) to the requested address. The sweep target is usually builder.context.sender.
7

Simulation and compilation (in parallel)

The compiled program is both simulated against current on-chain state and compiled to EVM calldata at the same time:
  • Simulation runs the transaction against the latest state of the target chain (the current chain head), checks every attached guard (for example minimum output, slippage), and records the simulated output amount for each terminal resource. Violations produce guard_error; a low-level revert produces simulation_revert.
  • Compilation produces the final calldata and confirms the resulting artifact against the service’s policy (contract allowlists, transfer patterns, instruction limits, and external-call analysis).
8

Price-impact check (optional)

When you pass run.maxPriceImpactBps and the service has USD prices for both inputs and outputs, it aggregates the impact in USD and rejects the compilation with price_impact_exceeded if the bound is violated.
9

Response

The response is a ComposeCompileResult — a discriminated union on its status field:
  • status: 'success' (HTTP 200): the compile completed cleanly. The payload carries transactionRequest (calldata, to, value, optional gasLimit), userProxy (the execution address), producedResources (a map of terminal-resource records; simulated amounts live under .simulated.amountOut / .simulated.amountOutMin), any required approvals, and an optional price-impact breakdown.
  • status: 'partial' (HTTP 206, only when simulationPolicy: "allow-revert" was set on the run): the compile produced a transactionRequest but simulation reverted. The payload additionally carries error ({ kind, message }) and simulationRevert so the caller can surface the revert reason rather than hide it.
Branch on result.status before reading shape-specific fields — accessing result.transactionRequest after an allow-revert call without checking status will work, but ignoring simulationRevert will hide the very signal you opted into.

Errors you might see

The service returns a structured ComposeError with a kind field. The most common kinds:
Error kindHTTP statusWhen
decode_error400The Flow JSON is malformed or fails shape validation.
validation_error, linearity_error400Port bindings, ref targets, or resource linearity are invalid.
preparation_error422An amount that required on-chain resolution could not be determined.
no_route_error404No routing path exists for a zap edge.
guard_error, simulation_revert, simulation_setup_error, price_impact_exceeded422The simulated transaction violates an invariant or reverts.
resolve_error, lowering_error, simulation_error, compilation_error, verification_error500Transport or server-side failures. Retry is usually safe.
If you pass simulationPolicy: "allow-revert", a structured revert is returned as HTTP 206 Partial Content with the transaction and the revert diagnostics, instead of failing the call. This is useful when you want to surface the revert reason to the user rather than hide it.

See also