> ## Documentation Index
> Fetch the complete documentation index at: https://docs.li.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Execution Model

> What happens when you POST a Flow to /compose, from validation through simulation to a signed-transaction-ready calldata response.

> 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`

<Steps>
  <Step title="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.
  </Step>

  <Step title="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`.
  </Step>

  <Step title="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](/composer/composer-api/materialisers).
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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`.
  </Step>

  <Step title="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).
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

## Errors you might see

The service returns a structured `ComposeError` with a `kind` field. The most common kinds:

| Error kind                                                                                       | HTTP status | When                                                                 |
| ------------------------------------------------------------------------------------------------ | ----------- | -------------------------------------------------------------------- |
| `decode_error`                                                                                   | 400         | The Flow JSON is malformed or fails shape validation.                |
| `validation_error`, `linearity_error`                                                            | 400         | Port bindings, ref targets, or resource linearity are invalid.       |
| `preparation_error`                                                                              | 422         | An amount that required on-chain resolution could not be determined. |
| `no_route_error`                                                                                 | 404         | No routing path exists for a zap edge.                               |
| `guard_error`, `simulation_revert`, `simulation_setup_error`, `price_impact_exceeded`            | 422         | The simulated transaction violates an invariant or reverts.          |
| `resolve_error`, `lowering_error`, `simulation_error`, `compilation_error`, `verification_error` | 500         | Transport 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

* [Flow Structure](/composer/composer-api/concepts/flow-anatomy) — what you submit.
* [Guards](/composer/composer-api/concepts/simulation-and-guards) — what the simulate stage asserts.
* [Error codes](/composer/composer-api/reference/error-codes) — full catalog of `kind` values.
