> ## 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.

# Guards

> How the compose service simulates a flow on-chain and how guards turn simulated observations into enforceable invariants.

> In the [execution model](/composer/composer-api/concepts/execution-model), the simulation step observes runtime values. Guards are how those observations become on-chain assertions.

After a flow has been lowered to an executable program, the compose service simulates it against the latest state of the target chain (the current chain head) **before** returning calldata. This simulation produces three things: values for every terminal resource (so `producedResources` can be populated), values for every handle a guard asked to observe, and a gas estimate. Guards are the mechanism that turns those observed values into on-chain invariants baked into the transaction itself.

## What a guard is

A **guard** is an invariant attached to an op output. The service reads the relevant port value during simulation, then bakes a corresponding check into the compiled calldata so the invariant is enforced on-chain when the transaction runs.

Guards are not simulation-time checks that can be silently relaxed. The check is part of the final calldata and is verified on-chain when the transaction runs, so the calldata itself guarantees the invariant.

## When guards run

Guards fire during the **simulation and assertion** step of the compose pipeline (see [Execution model](/composer/composer-api/concepts/execution-model)). Specifically:

* The service simulates the program against the current chain head, reading any on-chain values the guards observe. The simulator returns the concrete value of every observed port.
* For each guard, the observed value is baked into a check that enforces the invariant. That check is part of the final compiled program.
* The final calldata is re-verified against the service's policy (contract allowlists, transfer patterns, instruction limits).

If the simulated program reverts or the assertion cannot be satisfied, compilation fails with `guard_error` (HTTP 422) and no calldata is returned. Under `simulationPolicy: "allow-revert"` the calldata is returned as HTTP 206 along with the revert diagnostics instead.

## Where guards can attach: `providesMinimum` ports

A guard declares a `compatibility.selectors` list describing which ports it can attach to. The compile-time validator rejects attachments to ports it cannot protect. In particular:

* Ports that already declare `providesMinimum: true` (the router or aggregator has already baked in a minimum-out) **cannot** carry a redundant minimum-out guard. Attaching a `slippage` guard to such a port produces a `guard_error` at compile time with the message *"Guard targets port … which provides its own minimum"*.
* `lifi.swap`'s `amountOut` port is the canonical example: LI.FI's aggregator already encodes `minOut` from the `slippage` config field, so you don't (and can't) attach a second slippage guard to it.
* `lifi.zap`'s `amountOut` does **not** provide its own minimum, which is why the [swap-and-zap recipe](/composer/composer-api/recipes/swap-and-zap) attaches a `slippage` guard there.

Use the op's manifest to see which output ports carry `providesMinimum`; use [References](/composer/composer-api/concepts/ref-grammar) and the port type guards when you need to discriminate programmatically.

## Guaranteed minimums and `amountOutMin`

Some guards (e.g. `slippage`) also produce a **guaranteed minimum**: the worst-case value for the observed port given the guard's tolerance. The service surfaces this on the response as `producedResources[*].simulated.amountOutMin` for any terminal resource whose output port is protected by such a guard. If you attach multiple guards to the same port, the tightest minimum (the largest one) wins.

This is why guards are the right place to encode safety bounds: the same tolerance value you give the guard becomes both an on-chain assertion **and** a user-visible field on the response, so your UI can display "you will receive at least X" without recomputing anything.

## Common guard failures

| Failure                                                                                                | Cause                                                                                                        |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `Guard targets port "<p>" which provides its own minimum`                                              | Attaching a minimum-out-style guard to a port whose op already declares `providesMinimum`. Remove the guard. |
| `Guard targets port "<p>" which does not exist`                                                        | The port name in the guard config doesn't match any port on the op. Check the op's manifest.                 |
| `Guard requires config field "<key>"`                                                                  | The guard selector uses `selection: { kind: "config" }` and the config is missing the key.                   |
| `Guard '<kind>' on '<call>' returned guaranteedMinimums key '<p>' which is not in its observedHandles` | Internal bug in a third-party guard definition; report upstream.                                             |
| `guard_error` at simulation time                                                                       | The simulated transaction would violate the invariant. Loosen the tolerance or investigate the quote.        |

## What guards do **not** do

* **Guards don't replace preconditions.** [Preconditions](/composer/composer-api/guides/build-a-flow#preconditions-state-before-execution) describe on-chain state that must be true **before** the transaction runs (e.g., "the user holds at least X USDC") and feed the simulator as state overrides. Guards run **during** the transaction and enforce invariants on computed values.
* **Guards don't change routing.** A slippage guard that would fail at the user's requested tolerance surfaces as `guard_error`; the service does not silently reroute. If you want the aggregator to honour the tolerance, configure it on the op (e.g. `lifi.swap`'s `slippage` field).
* **Guards don't run arbitrary user code on-chain.** A guard enforces its invariant using a bounded, audited set of checks — it cannot execute arbitrary logic on-chain.

## See also

* [Guards catalog](/composer/composer-api/guards) — every guard registered in the manifest.
* [Build a Flow → Add safety](/composer/composer-api/guides/build-a-flow#add-safety-preconditions-and-guards) — how to attach guards and preconditions to flows.
* [Execution Model](/composer/composer-api/concepts/execution-model) — where the simulation step sits in the pipeline.
