Skip to main content
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):

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.

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

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

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.

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

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.
  • 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.
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>().
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.
  • 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.

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

See the SDK API reference, the op catalog and Errors 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. Both kinds reach the SDK as a ComposeError with code VALIDATION_ERROR; narrow them with isComposeSdkOutdatedError and isComposeServerOutdatedError (see SDK reference: errors). To see which version production serves, read the x-lifi-composer-version header of GET /compose/manifest, with a unique query parameter: