npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lifi/composer-sdk

v0.0.1-alpha

Published

Public Composer SDK for building and submitting flows

Readme

@lifi/composer-sdk

TypeScript SDK for building and submitting LI.FI Compose flows.

Install

@lifi/compose-spec is a peer dependency and must be installed alongside the SDK at the same version (they are versioned in lockstep).

npm install @lifi/composer-sdk @lifi/compose-spec

Quick start

Swap WETH to USDC, then zap the USDC into an Aave lending position — all in a single transaction.

import {
  createComposeSdk,
  resources,
  guards,
  materialisers,
} from '@lifi/composer-sdk';

const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c'; // Aave aEthUSDC
const OWNER = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';

// Create the SDK pointed at the Compose API.
const sdk = createComposeSdk({
  baseUrl: 'https://compose.li.fi',
  apiKey: process.env.LIFI_API_KEY, // optional
});

// Build a two-step flow on Ethereum mainnet.
const builder = sdk.flow(1, {
  name: 'swap-and-zap-weth-to-aave',
  inputs: {
    amountIn: resources.erc20(WETH, 1),
  },
});

// Step 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,
  },
});

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

const flow = builder.build();

// Compile the flow into transaction calldata.
const request = sdk.request(flow, {
  signer: OWNER,
  inputs: {
    amountIn: materialisers.directDeposit({
      amount: '1000000000000000000',
    }),
  },
  sweepTo: builder.context.sender,
});

const result = await sdk.client.compile(request);

if (result.status === 'success') {
  // Full success — transactionRequest includes gasLimit.
  console.log(result.transactionRequest);
} else {
  // result.status === 'partial' — simulation reverted.
  // Transaction is still available but without gasLimit.
  console.log(result.simulationRevert);
}

Core concepts

Flows and operations — A flow is a sequence of on-chain operations. You declare inputs, chain operations together, and the backend compiles everything into a single transaction. Operations are namespaced (e.g., builder.lifi.swap, builder.core.split).

Resourcesresources.erc20(address, chainId) and resources.native(chainId) describe the tokens flowing through your operations. They carry chain and address metadata used for routing and validation.

Handles — Operations produce typed output handles (e.g. OutputHandle<'resource'>, OutputHandle<'uint256'>) that you bind to downstream inputs. The type system enforces compatibility at compile time — a resource handle can flow into a uint256 slot (since resources are amounts), but an address handle cannot.

Runtime inputs (materialisers) — Materialisers resolve input values at execution time rather than at build time. directDeposit is exact by default when you provide an amount; pass allowNonExact: true to permit capped ERC-20 deposits or deposit-all behavior. balanceOf reads the wallet's current balance; call measures a balance delta after an arbitrary contract call.

Preconditions — Expected on-chain state at execution time: erc20Balance and nativeBalance assert wallet holdings, erc20Allowance asserts token approvals.

Guards — Protect against slippage and other runtime conditions. Applied per-operation via the guards field.

API surface

SDK factory

  • createComposeSdk({ baseUrl, fetch?, apiKey? }) — creates the SDK instance

Flow building

  • sdk.flow(chainId, options) — creates a FlowBuilder
  • builder.<namespace>.<operation>(id, { bind, config }) — adds an operation, returns typed OutputHandle<T> per port
  • builder.untypedOp(id, op, args) — escape hatch for operations not in the manifest (returns void; use raw.ref<T>() to reference its outputs)
  • builder.build() — produces a Flow document
  • sdk.request(flow, { signer, inputs, preconditions, sweepTo, ... }) — builds a compile request

HTTP client

  • sdk.client.compile(request) — sends the flow to the backend and returns a ComposeCompileResult (discriminated union: status: 'success' or status: 'partial')
  • sdk.client.getManifest() — fetches the operation manifest

Helpers

  • resources.erc20(address, chainId) / resources.native(chainId) — resource constructors
  • guards.* — guard factories (e.g., slippage)
  • materialisers.* — materialiser factories (directDeposit, balanceOf, call)
  • preconditions.* — precondition factories (erc20Balance, nativeBalance, erc20Allowance)
  • raw.ref<T>(path) — create a typed $ref pointer for use in bind slots (escape hatch for untypedOp outputs)
  • raw.guard(kind, config?) / raw.materialiser(kind, config?) — low-level factories for guards and materialisers

Simulation policy and partial results

By default, the Compose backend simulates the compiled transaction and returns an error (HTTP 422) if simulation detects a revert. You can opt into receiving a partial result instead by passing simulationPolicy: 'allow-revert':

const result = await builder.compile({
  signer: OWNER,
  inputs: { amountIn: materialisers.balanceOf({ owner: OWNER }) },
  simulationPolicy: 'allow-revert',
});

if (result.status === 'success') {
  // Simulation succeeded. transactionRequest includes gasLimit.
  const tx = result.transactionRequest;
  console.log(tx.gasLimit); // string
} else {
  // result.status === 'partial'
  // Simulation reverted, but a transaction is still available (without gasLimit).
  console.log(result.error.kind);    // 'simulation_revert'
  console.log(result.error.message); // human-readable revert description

  // Revert diagnostics
  const revert = result.simulationRevert;
  console.log(revert.code);          // e.g. 3
  console.log(revert.rawErrorBytes); // raw ABI-encoded error

  // Decoded error candidates (when available)
  if (revert.decodeResult?.errorCandidates) {
    for (const c of revert.decodeResult.errorCandidates) {
      console.log(c.decodedErrorSignature, c.decodedParams);
    }
  }

  // The transactionRequest is still usable — the caller must estimate gas themselves.
  const tx = result.transactionRequest;
  console.log(tx.to, tx.data, tx.value);
}

The simulationPolicy field accepts two values:

  • 'strict' (default) — revert causes a thrown ComposeError with code VALIDATION_ERROR
  • 'allow-revert' — revert returns a partial result with status: 'partial'

You can also pass checkOnChainAllowances: true to have the server filter the returned approvals array against current on-chain allowances, omitting approvals that are already sufficient:

const result = await builder.compile({
  signer: OWNER,
  inputs: { amountIn: materialisers.balanceOf({ owner: OWNER }) },
  checkOnChainAllowances: true,
});

Error handling

All SDK errors are thrown as ComposeError with a code property:

import { isComposeError } from '@lifi/composer-sdk';

try {
  const result = await sdk.client.compile(request);
} catch (err) {
  if (isComposeError(err)) {
    console.error(err.code, err.message);
    // Codes: VALIDATION_ERROR, SERVER_ERROR, RATE_LIMITED, NETWORK_ERROR, ...
  }
}

Examples

The src/examples/ directory contains complete working examples:

  • lifiSwap — Single token swap (WETH to USDC)
  • lifiZap — Swap into a DeFi position
  • swapAndZap — Multi-step: swap then deposit
  • splitAndZap — Split a resource and zap each portion into a different vault
  • splitWithArithmetic — Split then verify with add/subtract/assertEqual assertions
  • dustSweep — Split and partially use tokens, sweep leftover dust back to sender
  • depositFromProxy — Read tokens already on the proxy via balanceOf, with a precondition guard
  • approveAndDeposit — Approve a vault, deposit, and graduate shares via asResource
  • consolidateToUsdc — Consolidate multiple tokens into USDC
  • consolidateToEth — Consolidate multiple tokens into ETH
  • swapToRecipient — Swap and send to a different address
  • swapWithBalanceCheck — Swap with balance precondition
  • swapWithOutputValidation — Swap with computed slippage bounds using bpsDown/bpsUp/assertInRange
  • rawCallWithArithmetic — Query a contract with pre-encoded calldata, then scale with multiply/divide
  • readContractState — Compare peek (compile-time), staticCall (execution-time), and balanceOf (resource)
  • swapWithAllowRevert — Swap with simulationPolicy: 'allow-revert' and handle the ComposeCompileResult discriminated union
  • untypedOpWithTypedRef — Insert an untyped operation node via untypedOp, then bridge its output into typed operations using raw.ref<T>()

License

Apache-2.0