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

# Quickstart

> Install the SDK and compile your first Flow to calldata in a few minutes.

This quickstart builds a minimal Flow that swaps WETH into USDC and zaps the USDC into an Aave lending position — the same shape as the [`swap-and-zap` recipe](/composer/composer-api/recipes/swap-and-zap). 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](/composer/composer-api/guides/build-a-flow) after this page.

<Tip>
  **You don't need to learn every concept first.** Composer has its own vocabulary — *Flow*, *operation*, *resource*, *input*, *materialiser*, *guard* — but this page explains each term in one line, right where it first appears in the code. Skim it, run the example, and follow the linked concept pages only when you want to go deeper. **This single example is enough to start building and experimenting with Composer.**
</Tip>

## Prerequisites

* **Node.js ≥ 20** and a TypeScript project (`tsc`, `tsx`, or `ts-node` all work).
* An EVM account with a small amount of WETH on Ethereum mainnet (this example uses `1` WETH 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](/composer/composer-api/guides/build-a-flow#wire-in-a-signer).
* A **LI.FI API key**. Composer is in technical preview and every request is authenticated. Register at [portal.li.fi](https://portal.li.fi) and create a key from your dashboard.

<Note>
  **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`.
</Note>

<Warning>
  **ETHGlobal hackathon — unaudited preview.** The hackathon runs against a separate, **unaudited** deployment. Install the **staging** build — `yarn add @lifi/composer-sdk@staging @lifi/compose-spec@staging` — instead of the `@alpha` install shown below: the hackathon ops (flashloans, `lifi.flashloanRepay`) ship only in `@staging`, not in `@alpha`/`@latest`. Point the SDK at `https://ethglobal-composer.li.quest` instead of the default, and use the hackathon contract addresses from the [Addresses](/composer/composer-api/reference/addresses#ethglobal-hackathon) page. This deployment — and any surface available only there, such as flashloans — is for the hackathon and experimentation only. Don't use it in production or with significant funds. For the full hackathon setup, see the [ETHGlobal launchpad](/composer/ethglobal-ny-2026).
</Warning>

## Steps

<Steps>
  <Step title="Install the SDK">
    The `@lifi/composer-sdk` package is published on npm. Until a stable release ships, pin the alpha range.

    ```bash theme={"system"}
    npm install @lifi/composer-sdk@alpha
    # or: yarn add @lifi/composer-sdk@alpha
    # or: pnpm add @lifi/composer-sdk@alpha
    ```
  </Step>

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

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

    const sdk = createComposeSdk({
      baseUrl: 'https://composer.li.quest',
      apiKey: process.env.LIFI_API_KEY,
    });
    ```
  </Step>

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

    * An **input** is a value the flow consumes. `amountIn` here is a **resource** — a token balance (1 WETH) that Composer tracks as it moves between steps. (Inputs can also be plain scalars like a `uint256` or an `address`.)
    * An **operation** (*op*) is one named step in the flow, like a swap or a deposit. `builder.lifi.swap` and `builder.lifi.zap` are ops; the `zap` binds the `swap`'s output as its own input, threading the two together. These two are a small slice of the [full op catalog](/composer/composer-api/ops).

    Input names and node ids (the first argument to each op) are yours to choose — they become the keys you bind runtime values to and the handles downstream ops reference.

    ```ts theme={"system"}
    const builder = sdk.flow(1, {
      name: 'swap-and-zap-quickstart',
      inputs: {
        amountIn: resources.erc20(WETH, 1),
      },
    });

    // Swap WETH → USDC, then zap into Aave.
    const swap = builder.lifi.swap('swap', {
      bind:   { amountIn: builder.inputs.amountIn },
      config: { resourceOut: resources.erc20(USDC, 1), slippage: 0.03 },
    });

    builder.lifi.zap('zap', {
      bind:   { amountIn: swap.amountOut },
      config: { resourceOut: resources.erc20(A_ETH_USDC, 1) },
    });
    ```

    See the [full example](#full-example) below for the imports and address constants.
  </Step>

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

    ```ts theme={"system"}
    const result = await builder.compile({
      signer: '0xYourSignerAddress',
      inputs: {
        amountIn: materialisers.directDeposit({
          amount: '1000000000000000000', // 1 WETH (18 decimals)
        }),
      },
      sweepTo: builder.context.sender,
    });
    ```

    `result.transactionRequest` is what you hand to your wallet:

    ```json theme={"system"}
    {
      "to":    "0x6b3e…1f04",  // your per-user execution proxy (== userProxy)
      "data":  "0x…",          // calldata the proxy delegatecalls into the VM
      "value": "0"
    }
    ```

    The `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 `delegatecall`s 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](/composer/composer-api/reference/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.
  </Step>
</Steps>

## Full example

Adapted from [`swapAndZap.ts`](https://github.com/lifinance/composer-sdk-examples/blob/main/examples/swapAndZap.ts) in the `composer-sdk-examples` repo:

```ts theme={"system"}
import {
  createComposeSdk,
  guards,
  materialisers,
  resources,
} from '@lifi/composer-sdk';

const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c';

const run = async () => {
  const sdk = createComposeSdk({
    baseUrl: 'https://composer.li.quest',
    apiKey: process.env.LIFI_API_KEY,
  });

  const builder = sdk.flow(1, {
    name: 'swap-and-zap-quickstart',
    inputs: {
      amountIn: resources.erc20(WETH, 1),
    },
  });

  // Swap WETH → USDC via LI.FI.
  const swapOutputs = builder.lifi.swap('swap', {
    bind: { amountIn: builder.inputs.amountIn },
    config: {
      resourceOut: resources.erc20(USDC, 1),
      slippage: 0.03,
    },
  });

  // Zap the swapped USDC into Aave's aEthUSDC position.
  builder.lifi.zap('zap', {
    bind: { amountIn: swapOutputs.amountOut },
    config: {
      resourceOut: resources.erc20(A_ETH_USDC, 1),
    },
    guards: [guards.slippage({ port: 'amountOut', bps: 100 })],
  });

  const result = await builder.compile({
    signer: '0xYourSignerAddress',
    inputs: {
      amountIn: materialisers.directDeposit({
        amount: '1000000000000000000', // 1 WETH (18 decimals)
      }),
    },
    sweepTo: builder.context.sender,
  });

  console.log(JSON.stringify(result.transactionRequest, null, 2));
};

run().catch((err) => {
  console.error(err);
  process.exit(1);
});
```

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

## 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()`:

```ts theme={"system"}
const flow = builder.build();
console.log(JSON.stringify(flow, null, 2));
```

```json theme={"system"}
{
  "version": 1,
  "id": "swap-and-zap-quickstart",
  "chainId": 1,
  "inputs": [
    {
      "name": "amountIn",
      "resource": { "kind": "erc20", "token": "0xC02a…Cc2", "chainId": 1 }
    }
  ],
  "nodes": [
    {
      "id": "swap",
      "op": "lifi.swap",
      "bind":   { "amountIn": { "$ref": "input.amountIn" } },
      "config": { "resourceOut": { "kind": "erc20", "token": "0xA0b8…eB48", "chainId": 1 }, "slippage": 0.03 }
    },
    {
      "id": "zap",
      "op": "lifi.zap",
      "bind":   { "amountIn": { "$ref": "swap.amountOut" } },
      "config": { "resourceOut": { "kind": "erc20", "token": "0x98C2…6F5c", "chainId": 1 } },
      "guards": [ { "kind": "slippage", "port": "amountOut", "bps": 100 } ]
    }
  ]
}
```

This is the Flow document the compose endpoint receives. Every input name (`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](/composer/composer-api/concepts/flow-anatomy) 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:

```ts theme={"system"}
if (result.status === 'success') {
  // transactionRequest is ready to sign
  await wallet.sendTransaction(result.transactionRequest);
} else {
  // status === 'partial' — only reachable with simulationPolicy: 'allow-revert'.
  // transactionRequest is still present, but simulation reverted.
  console.error(result.error.kind, result.error.message);
  console.error(result.simulationRevert); // structured revert diagnostics
}
```

On `status: 'success'`, the payload contains:

* **`transactionRequest`** — `{ to, data, value, gasLimit? }`. `to` is your execution proxy (the proxy factory on the proxy's first use); `data` is 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](/composer/composer-api/overview#account-model)).
* **`producedResources`** — a map of terminal resource names to per-resource records. Simulated amounts live at `<resource>.simulated?.amountOut` and `<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 when `maxPriceImpactBps` is set on the run.

On `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](/composer/composer-api/guides/build-a-flow) — full walkthrough of inputs, handles, materialisers, guards, preconditions, and submission.
* [Flow Structure](/composer/composer-api/concepts/flow-anatomy) — the JSON document shape your builder produces.
* [References](/composer/composer-api/concepts/ref-grammar) — how handles become `$ref` strings on the wire.
* [Recipes](/composer/composer-api/recipes) — three canonical flow shapes (Dust Sweep, Swap and Deposit, Split Deposits).
