@lifi/composer-sdk
Version:
Public Composer SDK for building and submitting flows
175 lines (163 loc) • 5.67 kB
text/typescript
import type {
ComposeCompileRequest,
ComposeCompileResult,
InputSpec,
} from '@lifi/compose-spec';
import {
createFlowBuilderCore,
type FlowBuilderCore,
type FlowOptions,
type TypedFlow,
} from './authoring/FlowBuilderCore.js';
import { type ComposeClient, createComposeClient } from './client.js';
import {
bindGeneratedOps,
type GeneratedOps,
} from './generated/operations.generated.js';
import { buildRun, type ComposeRunInput } from './run/inputs.js';
import type { InputSchema } from './types.js';
export type { ComposeRunInput };
/**
* A flow builder augmented with generated operation methods and a `compile` method
* that submits the flow to the Compose backend for compilation.
*
* Created via {@link ComposeSdk.flow}.
*
* @typeParam T - The input schema describing the flow's required inputs.
*/
export type FlowBuilder<T extends InputSchema = InputSchema> =
FlowBuilderCore<T> &
GeneratedOps & {
/**
* Builds the flow document from the current builder state and submits it
* to the Compose API for compilation.
*
* @param run - Runtime inputs, preconditions, and signer address.
* @returns The compiled transaction calldata and metadata.
* @throws {@link ComposeError} on network, validation, or server errors.
*
* @example
* ```ts
* const result = await builder.compile({
* inputs: { token: materialisers.balanceOf({}) },
* signer: '0xYourAddress...',
* });
* console.log(result.calldata);
* ```
*/
readonly compile: (
run: ComposeRunInput<T>,
) => Promise<ComposeCompileResult>;
};
/**
* Configuration for creating a Compose SDK instance.
*/
export interface ComposeSdkOptions {
/** Base URL of the Compose API (e.g. `"https://li.quest"`). */
readonly baseUrl: string;
/** Optional custom `fetch` implementation. Defaults to `globalThis.fetch`. */
readonly fetch?: typeof globalThis.fetch;
/** Optional LI.FI API key for authenticated access. Sent as the `x-lifi-api-key` header on every request. */
readonly apiKey?: string;
}
/**
* The top-level Compose SDK interface.
*
* Provides methods to build flows, compile them into executable calldata, and
* interact with the Compose API directly.
*
* @example
* ```ts
* import { createComposeSdk, resources, materialisers } from '@lifi/composer-sdk';
*
* const sdk = createComposeSdk({ baseUrl: 'https://li.quest' });
*
* const builder = sdk.flow(1, {
* inputs: { usdc: resources.erc20('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 1) },
* });
*
* builder.lifi.swap('swap1', {
* bind: { amountIn: builder.inputs.usdc },
* config: { resourceOut: resources.native(1) },
* });
*
* const result = await builder.compile({
* inputs: { usdc: materialisers.balanceOf({}) },
* signer: '0xYourAddress...',
* });
* ```
*/
export interface ComposeSdk {
/** Low-level HTTP client for the Compose API. */
readonly client: ComposeClient;
/**
* Creates a new flow builder targeting the given chain.
* @param chainId - The EVM chain ID (e.g. `1` for Ethereum mainnet).
* @param options - Flow configuration including input declarations.
* @returns A {@link FlowBuilder} with generated operation methods and a `compile` method.
* @throws Error if any resource input's `chainId` doesn't match the flow's `chainId`.
*/
readonly flow: <T extends InputSchema>(
chainId: number,
options: FlowOptions<T>,
) => FlowBuilder<T>;
/**
* Builds a raw compile request without sending it. Useful for inspecting the
* request payload or submitting it through a custom transport.
* @param flow - A built flow document (from `builder.build()`).
* @param run - Runtime inputs, preconditions, and signer address.
* @returns A {@link ComposeCompileRequest} ready to send to the Compose API.
*/
readonly request: <T extends InputSchema>(
flow: TypedFlow<T>,
run: ComposeRunInput<T>,
) => ComposeCompileRequest;
}
/**
* Creates a new Compose SDK instance.
*
* @param options - SDK configuration including the API base URL and optional fetch implementation.
* @returns A {@link ComposeSdk} instance.
*
* @example
* ```ts
* const sdk = createComposeSdk({ baseUrl: 'https://li.quest' });
* ```
*/
export const createComposeSdk = (options: ComposeSdkOptions): ComposeSdk => {
const composeClient = createComposeClient(options);
const request = <T extends InputSchema>(
flowDoc: TypedFlow<T>,
run: ComposeRunInput<T>,
): ComposeCompileRequest => ({
flow: flowDoc,
run: buildRun({
inputs: run.inputs as Record<string, InputSpec>,
preconditions: run.preconditions,
signer: run.signer,
assumptions: run.assumptions as Record<string, bigint> | undefined,
referrer: run.referrer,
integratorFeeBps: run.integratorFeeBps,
maxPriceImpactBps: run.maxPriceImpactBps,
sweepTo: run.sweepTo,
simulationPolicy: run.simulationPolicy,
checkOnChainAllowances: run.checkOnChainAllowances,
}),
});
const flow = <T extends InputSchema>(
chainId: number,
opts: FlowOptions<T>,
): FlowBuilder<T> => {
const core = createFlowBuilderCore(chainId, opts);
const builder = Object.assign(core, bindGeneratedOps(core));
const compile = async (
run: ComposeRunInput<T>,
): Promise<ComposeCompileResult> => {
const flowDoc = builder.build();
const req = request(flowDoc, run);
return composeClient.compile(req);
};
return Object.assign(builder, { compile });
};
return { client: composeClient, flow, request };
};