swap-and-zap recipe. By the end you will have a ComposeCompileResult containing transactionRequest.data (calldata) ready to sign.
For a full walkthrough of inputs, handles, materialisers, guards, preconditions, and submission, see Build a Flow after this page.
Prerequisites
- Node.js ≥ 20 and a TypeScript project (
tsc,tsx, orts-nodeall work). - An EVM account with a small amount of WETH on Ethereum mainnet (this example uses
1WETH for illustration). - The signer’s
0x-prefixed address. The quickstart does not send the transaction; it stops at the compiled calldata. Signer wiring (viem, ethers, etc.) is covered in Build a Flow. - A LI.FI API key. Composer is in technical preview and every request is authenticated. Register at portal.li.fi and create a key from your dashboard.
Base URL. The public compose endpoint is served by a dedicated host; the SDK default is
https://composer.li.quest. If your integration uses a staging or internal environment, ask your LI.FI contact for the right baseUrl.Steps
Install the SDK
The
@lifi/composer-sdk package is published on npm. Until a stable release ships, pin the alpha range.Create the SDK
createComposeSdk returns a handle with a flow factory, a low-level client, and a request helper for custom transports. During the technical preview an apiKey is required.Build the flow
A Flow is the document you’re building here: an ordered list of steps that compiles into one transaction. You declare its inputs, then chain operations on the builder.See the full example below for the imports and address constants.
- An input is a value the flow consumes.
amountInhere is a resource — a token balance (1 WETH) that Composer tracks as it moves between steps. (Inputs can also be plain scalars like auint256or anaddress.) - An operation (op) is one named step in the flow, like a swap or a deposit.
builder.lifi.swapandbuilder.lifi.zapare ops; thezapbinds theswap’s output as its own input, threading the two together. These two are a small slice of the full op catalog.
Compile the flow
builder.compile(run) ships the flow to POST /compose, simulates it, and returns a ComposeCompileResult containing the calldata your user signs.The inputs map here supplies the concrete runtime value for each input the flow declared. directDeposit is a materialiser — a small descriptor that tells Composer how to obtain the tokens at execution time (here, pull exactly 1 WETH into the proxy via transferFrom). sweepTo says where leftover balances go when the flow finishes.result.transactionRequest is what you hand to your wallet:to address is your per-user execution proxy — the same value as userProxy. On the proxy’s first use it’s instead the proxy factory, which deploys your proxy and runs the flow in one transaction. Either way the proxy delegatecalls the shared Composer VM, so the flow executes in your proxy’s own balance and storage context. The VM and proxy-factory addresses per chain are on the Addresses page.Alongside transactionRequest, the result carries userProxy (the execution address), producedResources (simulated amounts per terminal resource), and any approvals the signer must grant first.Full example
Adapted fromswapAndZap.ts in the composer-sdk-examples repo:
The full example adds one term the steps didn’t: a guard.
guards.slippage({ port: 'amountOut', bps: 100 }) on the zap is an invariant Composer enforces during execution — here, a 1% slippage floor on the zap’s output — so the flow fails safely instead of executing on bad terms. The swap doesn’t need one: its slippage: 0.03 config already bakes a floor into the quote.What the SDK builds
builder.compile(run) doesn’t just call the endpoint — it serialises the typed builder state to a wire-format Flow document, ships it to POST /compose, and parses the response. If you want to see what the wire actually looks like, swap builder.compile(run) for builder.build():
amountIn), node id (swap, zap), and $ref you see here is exactly what you wrote in the builder above — the SDK does not rename or rewrite. See Flow Structure for the full wire-format reference.
What you got back
ComposeCompileResult is a discriminated union on status. Branch on it before reading shape-specific fields:
status: 'success', the payload contains:
transactionRequest—{ to, data, value, gasLimit? }.tois your execution proxy (the proxy factory on the proxy’s first use);datais the calldata the proxy delegatecalls into the VM.userProxy— the predicted execution proxy address (a deterministic per-signer contract derived by the proxy factory; see Account model).producedResources— a map of terminal resource names to per-resource records. Simulated amounts live at<resource>.simulated?.amountOutand<resource>.simulated?.amountOutMin.approvals— ERC-20 approvals the signer must grant before executing the transaction (omitted when none are needed).priceImpact— optional USD price-impact breakdown whenmaxPriceImpactBpsis set on the run.
status: 'partial', the payload additionally carries error ({ kind, message }) and simulationRevert. This shape is only returned when you pass simulationPolicy: 'allow-revert' on the run; under the default 'strict' policy a revert raises a ComposeError instead.
Next steps
You’ve now seen every core concept — Flow, operation, resource, input, materialiser, guard — in action. The example above is enough to start experimenting. When you want the full picture of any of these, these pages go deeper:- Build a Flow — full walkthrough of inputs, handles, materialisers, guards, preconditions, and submission.
- Flow Structure — the JSON document shape your builder produces.
- References — how handles become
$refstrings on the wire. - Recipes — three canonical flow shapes (Dust Sweep, Swap and Deposit, Split Deposits).

