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

# Release notes and migration

> Breaking changes, migration steps, and notable additions in the Composer API and SDK, from SDK 0.2.0 to 0.14.0.

Composer changes that need action from an integrator, newest first, then notable additions and the compatibility policy. The current release is SDK `0.14.0`, served by `https://composer.li.quest`.

A server-side change applies to every client the day it ships. The SDK version next to it is the first release whose types match. To upgrade, install both packages together (see [Before you start](https://docs.li.fi/composer/composer-api/overview.md#before-you-start)):

```bash theme={"system"}
npm install @lifi/composer-sdk @lifi/compose-spec
```

## Breaking changes

### SDK 0.14.0 (October 26, 2026)

**`paraswap.buy` returns all of its unsold input as `unspentIn`.**
`unspentIn` is now the full input minus the amount sold: 1,050 USDC in, 1,000 USDC sold, 50 USDC on `unspentIn`. Before, input above the op's quote-based cap stayed on the execution proxy with no port naming it.

* **Migrate:** recompile stored `paraswap.buy` flows instead of replaying their calldata, and re-check any downstream sizing based on the old `unspentIn`.

### SDK 0.13.0 (October 22, 2026)

**A settle node may commit zero outcomes.**
A `continuation.settle` node with `exact: true` may now set `outcomes: []`: the leg itself is the only thing the settlement enforces. Such a node has no `<nodeId>.output` entry in `outputs`.

* **Migrate:** code that reads `config.outcomes[0]` or `outputs['<settleId>.output']` must handle the empty case. See [Cross-chain flows](https://docs.li.fi/composer/composer-api/concepts/cross-chain-flows.md).

### SDK 0.12.0 (October 21, 2026)

**Composer fee collection is removed.**
`run.integratorFeeBps` no longer exists; a request that sends it fails with `400 VALIDATION_ERROR`. The `fees` response field, the `ComposeFee` type, and the `fee_resolution_error` and `fee_resolution_unavailable` error kinds are gone.

* **Migrate:** delete `integratorFeeBps` from every compile request and stop reading `result.fees`.

**Perpetual settlements are removed.**
A settle node no longer accepts `config.perpetual`; sending it fails with `400 decode_error`. Every compile registers a fresh, one-time escrow.

* **Migrate:** remove `perpetual` from settle configs. Never reuse an escrow address from an earlier compile; read it from `settlements[nodeId].escrow` each time.

**`verificationByNode[*].assertedPredicate` changed shape.**
The `erc20BalanceDeltaGte` and `nativeBalanceDeltaGte` predicates were removed, and the `call` predicate was reshaped. SDK 0.5.x to 0.11.x clients receive the new shape without matching types.

* **Migrate:** if you read `verificationByNode`, upgrade to SDK 0.12.0 or later and re-check your parsing.

### SDK 0.11.3 (October 16, 2026)

**`run.sweepTo` is shorthand for the flow epilogue, and the `SweepTo` type is renamed `ForwardTo`.**
A flow can now carry an `epilogue` (named `forwards` plus a `remainder` address); `run.sweepTo` sets `epilogue.remainder`. The wire shape of `sweepTo` is unchanged.

* **Migrate:** replace `import type { SweepTo }` from `@lifi/compose-spec` with `ForwardTo`. Send either `run.sweepTo` or `flow.epilogue`, not both; a request with both fails with `validation_error` at `run.sweepTo`. See [Sweeping and amounts](https://docs.li.fi/composer/composer-api/concepts/sweeping-and-amounts.md#sweepto).

**An unspent resource input is a terminal output.**
A resource input that no node consumes now appears in `outputs` under the key `input.<name>`, with `role: 'terminal'` and `amount.minimum.basis: 'input'`.

* **Migrate:** code that iterates `outputs` must expect `input.*` keys next to `<nodeId>.<portName>` keys.

**Settle nodes commit at most one outcome, and the leg may not name a receiver or an epilogue.**
A settle node with two or more `outcomes` fails with `validation_error`. Inside the leg (`continuationFlow`), binding `context.sender` fails with reason `leg_binds_sender`, because in a leg it is the settlement service's executor, not your user. A leg that declares its own `epilogue` fails with reason `leg_names_epilogue`.

* **Migrate:** split multi-outcome settles into one settle node per outcome. Where the leg needs a receiver, bind `context.executionAddress`; the compiler writes the leg's epilogue from the settle's outcome and `refund`. See the [cross-chain flow guide](https://docs.li.fi/composer/composer-api/guides/build-a-cross-chain-flow.md).

### SDK 0.10.0 (October 13, 2026)

**A settle node's funding needs an enforced minimum.**
The amount bound to a settle node's `input` port must carry a committed floor (`amount.minimum`) that the chain enforces. Funding with no floor, or a floor of 0, fails with `validation_error` naming the settle node and its `input` port, before any escrow is registered.

* **Migrate:** fund the settle from a declared input deposited with a fixed `amount`, from a quoted output that provides a minimum (such as `lifi.swap`'s `amountOut`), or add a `slippage` guard on the producing port. A `directDeposit` with `allowNonExact` cannot fund a settle directly.

### SDK 0.9.0 (October 12, 2026)

**`core.transfer` moves its whole input; `amount` and `remainder` are removed.**
`core.transfer` now sends everything bound to its `amount` port and has a single output, `transferred`. An old `config: { amount }` fails with `decode_error`.

* **Migrate:** to send a fixed amount, split it off with `core.splitAt` first, then transfer the `head` output. See [Sweeping and amounts](https://docs.li.fi/composer/composer-api/concepts/sweeping-and-amounts.md).

```ts Before theme={"system"}
// Sent 500 USDC and kept the rest on the proxy as `remainder`.
builder.core.transfer('pay', {
  bind: { amount: builder.inputs.amountIn, recipient: builder.inputs.recipient },
  config: { amount: '500000000' },
});
```

```ts After theme={"system"}
// Carve exactly 500 USDC out of the input; `tail` holds the rest.
const { head, tail } = builder.core.splitAt('split', {
  bind: { source: builder.inputs.amountIn },
  config: { amount: '500000000' },
});

// core.transfer moves its whole input: all of `head`.
builder.core.transfer('pay', {
  bind: { amount: head, recipient: builder.inputs.recipient },
  config: {},
});
```

**`lifi.swap` `slippage` must be between 0 and 1.**
`slippage` is a fraction: `0.03` means 3%. Before, `slippage: 30` was sent to the quote as 3000%. A value outside `[0, 1]` now fails with `decode_error`. The default stays `0.03`.

* **Migrate:** express slippage as a fraction, for example `0.005` for 0.5%. See [`lifi.swap`](https://docs.li.fi/composer/composer-api/ops/lifi-swap).

**Temporary upstream failures return a retryable `503 resolution_error`.**
When a quote, routing or settlement service is temporarily unavailable, the compile fails with `503 resolution_error` instead of `422 preparation_error`.

* **Migrate:** retry `503 resolution_error` with backoff; keep treating `422` kinds as failures of the request or flow.

### SDK 0.8.1 (October 8, 2026)

**The manual-relay settlement mode is removed.**
The `continuation.assertReady` op and the `settlements[nodeId].continuation` response field are gone. A settle node that names no `provider` uses LI.FI's settlement service, which fills the escrow.

* **Migrate:** remove `continuation.assertReady` nodes and stop relaying legs yourself. Track progress with the [escrow status endpoint](https://docs.li.fi/composer/composer-api/reference/escrow-status.md).

### October 5, 2026: new execution contracts (VM v1.2)

**Every signer has a new execution proxy.**
Production moved to new Composer contracts, so every signer's execution proxy has a new address. Approvals and balances on the old proxy stay there; the signer can still use them on chain, but the API no longer targets the old proxy.

* **Migrate:** do not cache proxy or spender addresses. Read `userProxy`, `approvals` and `actions` from each compile response; your users grant approvals to the new proxy once. Current addresses are on [Addresses](https://docs.li.fi/composer/composer-api/reference/addresses.md).

### SDK 0.7.0 (October 2, 2026)

**ERC-20 token addresses are validated when the flow is decoded.**
A resource's `token` must be a `0x`-prefixed 20-byte address. Placeholders such as `'0xUSDC'` fail with `400 decode_error`. Settle node addresses, integer strings and expiries are validated the same way.

* **Migrate:** replace placeholder tokens with real addresses.

**An op's immediate output must be on the flow's chain.**
Every output available in the same transaction (`delivery.when: 'now'`) must be on the flow's `chainId`; otherwise the compile fails with `validation_error`.

* **Migrate:** use [`lifi.bridge`](https://docs.li.fi/composer/composer-api/ops/lifi-bridge) for cross-chain moves. See its [example](https://docs.li.fi/composer/composer-api/ops/lifi-bridge.md#example).

**`phase1ByNode` and `continuationPlans` are replaced by `settlements`.**
The compile response returns one record per settle node, `settlements[nodeId]`, with the escrow terms under `escrow`. Each `fundEscrow` action names its settle node in `nodeId`.

* **Migrate:** read escrow details from `settlements[nodeId].escrow` and submit `actions` in order. See the [cross-chain flow guide](https://docs.li.fi/composer/composer-api/guides/build-a-cross-chain-flow.md).

### SDK 0.6.0 (September 14, 2026)

**`outputs` replaces `producedResources`.**
The compile response has one `outputs` record with an entry per port, keyed `<nodeId>.<portName>`, carrying `role`, `resource` and `amount: { estimate?, minimum? }`. See [Flow Schema: outputs](https://docs.li.fi/composer/composer-api/reference/flow-wire-format.md#outputs).

* **Migrate:** read `outputs` instead of `producedResources`, and use `terminalOutputs()` to keep only what the flow hands back. The deprecated `producedResources` field is still returned for 0.5.x clients and will be removed; don't rely on it.

```ts Before theme={"system"}
const out = result.producedResources['zap.amountOut'];
console.log(out?.simulated?.amountOut, out?.simulated?.amountOutMin);
```

```ts After theme={"system"}
import { terminalOutputs } from '@lifi/composer-sdk';

const zapOut = result.outputs['zap.amountOut'];
console.log(zapOut?.amount.estimate?.value, zapOut?.amount.minimum?.value);

// Only what the flow hands back: terminal ports and unspent inputs.
for (const [key, output] of Object.entries(terminalOutputs(result.outputs))) {
  console.log(key, output.owner, output.delivery.when, output.amount.minimum?.value);
}
```

**Typed bind slots reject plain `{ $ref }` objects.**
`TypedRef` is now branded, so a hand-written `{ $ref: 'node.port' }` no longer satisfies a typed bind slot.

* **Migrate:** take refs from typed handles, or build them with `raw.ref<T>()`.

```ts Before theme={"system"}
builder.core.add('sum', {
  bind: { a: { $ref: 'custom.result' }, b: { $ref: 'custom.other' } },
});
```

```ts After theme={"system"}
import { raw } from '@lifi/composer-sdk';

builder.core.add('sum', {
  bind: {
    a: raw.ref<'uint256'>('custom.result'),
    b: raw.ref<'uint256'>('custom.other'),
  },
});
```

**The settle node's `destination` bind is removed.**
`bind.destination` on a `continuation.settle` node now fails with `400 validation_error` (`Extra bind key "destination"`).

* **Migrate:** set the receiver only in `config.outcomes[0].destination`.

### SDK 0.5.1 (September 6, 2026)

**SDKs older than 0.5.0 are rejected.**
The API now checks the SDK version on `POST /compose`, `POST /compose/route` and `POST /compose/sweep`. Older SDKs get `400 sdk_outdated`; see [Compatibility policy](#compatibility-policy).

* **Migrate:** upgrade both packages to the same version, `0.14.0` recommended.

### SDK 0.5.0 (September 3, 2026)

**The compile result was reshaped.**

* `spends` and `quote.validUntilMs` moved under `fill: { spends, validUntilMs? }`. `fill.validUntilMs` is absent when nothing in the flow expires.

* Each `spends` entry gained `input`, `chainId` and an optional `nodeId` next to `token` and `amount`.

* `transactionRequest` is optional on success: read `actions` for the ordered list of transactions to submit.

* `verificationDescriptor` became `verificationByNode`, keyed by node id.

* The `continuations` array was removed.

* **Migrate:** read `fill.spends` and `fill.validUntilMs` instead of `spends` and `quote.validUntilMs`, and handle a missing `transactionRequest`.

### SDK 0.4.0 (August 21, 2026)

**`quote.validUntilMs` is the real expiry.**
Before, it was always a far-future placeholder. It is now the earliest expiry of any prepared node; the placeholder remains only when nothing in the flow expires.

* **Migrate:** treat the expiry as a deadline: submit before it, or recompile. From SDK 0.5.0 the field is `fill.validUntilMs`.

### SDK 0.3.0 (August 14, 2026)

**`apiKey` is required.**
`createComposeSdk({ baseUrl })` no longer type-checks, and a missing or blank key throws `ComposeError` with code `VALIDATION_ERROR` when the SDK is created. The API rejects requests without a key with `401`.

* **Migrate:** pass `apiKey` to `createComposeSdk`. See [Before you start](https://docs.li.fi/composer/composer-api/overview.md#before-you-start).

### SDK 0.2.0 (June 23, 2026)

**The legacy `POST /route` and `GET /zap-packs` endpoints are removed.**

* **Migrate:** use `POST /compose/route` (`sdk.route`) and `GET /compose/zap-packs`.

## Notable additions

| SDK | Date | Added |
| - | - | - |
| 0.14.0 | Oct 26, 2026 | Error bodies and `failedOps` entries carry a structured `shortfall` (`required`, `available`) when an amount falls short. |
| 0.13.0 | Oct 22, 2026 | [Exact legs](https://docs.li.fi/composer/composer-api/concepts/cross-chain-flows.md) funded by a bridge delivery, and zero-outcome exact legs ([pay cross-chain](https://docs.li.fi/composer/composer-api/recipes/pay-cross-chain.md)). A settle node reports its expected outcome next to its committed floor. Continuations run on 13 mainnets. `POST /simulate` accepts Permit2 allowance requirements. |
| 0.12.0 | Oct 21, 2026 | Exact legs (`exact: true`): the escrow is bound to the leg's compiled calldata, disclosed in `settlements[nodeId].binding`. Typed `core.call` and `core.staticCall` accept `raw.literal` arguments. |
| 0.11.3 | Oct 16, 2026 | Typed [`continuation.settle`](https://docs.li.fi/composer/composer-api/ops/continuation-settle) in the SDK. Delivered funding: a `lifi.bridge` or `lifiIntents.order` on one chain funds an escrow on another ([bridge and deposit](https://docs.li.fi/composer/composer-api/recipes/bridge-and-deposit.md)); new error kind `delivery_error`. `lifiIntents.order` and its refund ops. `flow.epilogue` with `forwards` and `remainder`, reported back as `epilogue`. `warnings` on success responses. |
| 0.10.0 | Oct 13, 2026 | Settle nodes size the escrow from the funding's expected amount and enforce its committed floor. |
| 0.9.0 | Oct 12, 2026 | `core.splitAt`: split a fixed amount off a resource. Prepared ops such as `lifi.swap` and `lifi.bridge` quote at their input's enforced floor. Terminal outputs behind pure ops (arithmetic, splits) report the upstream's enforced floor in `amount.minimum`. |
| 0.8.1 | Oct 8, 2026 | [Uniswap V2 and V3 liquidity ops](https://docs.li.fi/composer/composer-api/recipes/uniswap-liquidity.md) (`uniswapV2.addLiquidity`, `uniswapV2.removeLiquidity`, `uniswapV3.mint`, `uniswapV3.increaseLiquidity`) and the [`band` guard](https://docs.li.fi/composer/composer-api/guards/band). A settle node can be funded by an output of the same transaction, such as a swap. |
| 0.7.1 | Oct 5, 2026 | `aave.getHealthFactor` and the Morpho Blue ops (`borrow`, `repay`, `supply`, `withdraw`, `getPosition`) in the default SDK build. |
| 0.7.0 | Oct 2, 2026 | `core.partition`: split a resource into N parts by basis points. `settlements[nodeId]` with escrow terms. `POST /feedback` for reporting bugs and docs problems. |
| 0.6.2 | Sep 24, 2026 | Multi-hop planning on `/compose/route` and `buildReachability` ([server-planned route](https://docs.li.fi/composer/composer-api/recipes/server-planned-route.md)). The [escrow status endpoint](https://docs.li.fi/composer/composer-api/reference/escrow-status.md). |
| 0.6.0 | Sep 14, 2026 | `outputs` and `terminalOutputs()`. [`lifi.bridge`](https://docs.li.fi/composer/composer-api/ops/lifi-bridge): a cross-chain transfer reported as a future output with `delivery.provider`. |
| 0.5.1 | Sep 6, 2026 | SDK version handshake, `isComposeSdkOutdatedError` and `isComposeServerOutdatedError`. |
| 0.5.0 | Sep 3, 2026 | `raw.literal(kind, value)` for constant bind values. `run.simulateUserProgram`. `GET /chains` and `sdk.getSupportedChains()`. `fly.swap`. `etaSeconds` on future outputs (under `delivery` since 0.6.0). |
| 0.4.1 | Sep 1, 2026 | `sdk.route` with `routeAmount.exact()` and `routeAmount.all()`. |
| 0.4.0 | Aug 21, 2026 | `preparation_error` lists `failedOps` and `succeededOps`; narrow it with `isComposePreparationError`. |
| 0.2.0 | Jun 23, 2026 | `POST /simulate` with `sdk.simulate` and `buildSimulateRequest`. |

See the [SDK API reference](https://docs.li.fi/composer/composer-api/reference/sdk-api.md), the [op catalog](https://docs.li.fi/composer/composer-api/ops.md) and [Errors](https://docs.li.fi/composer/composer-api/reference/error-codes.md) for the full surface.

## Compatibility policy

The API serves every SDK from a floor up to its own version. The SDK sends its version in the `x-lifi-composer-sdk` request header, and the API checks it on `POST /compose`, `POST /compose/route` and `POST /compose/sweep`. `@lifi/compose-spec` is a peer dependency pinned to the same version as `@lifi/composer-sdk`, so upgrade them together.

| SDK version | Result |
| - | - |
| Below `0.5.0` | `400` with `kind: 'sdk_outdated'` and `minimumSdkVersion: '0.5.0'`. Upgrade. |
| `0.5.0` up to the served version | Accepted. Older versions still receive every server-side change listed above, typed or not. |
| Above the served version, in any component | `400` with `kind: 'server_outdated'`, for example right after an npm release. Pin both packages to the served version, or retry after the rollout. |

Both kinds reach the SDK as a `ComposeError` with code `VALIDATION_ERROR`; narrow them with `isComposeSdkOutdatedError` and `isComposeServerOutdatedError` (see [SDK reference: errors](https://docs.li.fi/composer/composer-api/reference/sdk-api.md#errors)).

To see which version production serves, read the `x-lifi-composer-version` header of `GET /compose/manifest`, with a unique query parameter:

```bash theme={"system"}
curl -sI "https://composer.li.quest/compose/manifest?cb=$(date +%s)" | grep -i x-lifi-composer-version
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.