@lifi/composer-sdk
Version:
Public Composer SDK for building and submitting flows
148 lines (142 loc) • 5.47 kB
text/typescript
import type {
ComposeRun,
InputSpec,
MaterialiserInput,
Precondition,
SimulationPolicy,
SweepTo,
} from '@lifi/compose-spec';
import type { Address, InputSchema, InputSpecOf } from '../types.js';
/**
* Runtime inputs for compiling a flow. Passed to `builder.compile()` or `sdk.request()`.
*
* @typeParam T - The flow's input schema, used to type-check `inputs` and `assumptions`.
*/
export interface ComposeRunInput<T extends InputSchema = InputSchema> {
/**
* Concrete values for each declared flow input. Resource inputs accept a
* materialiser or a literal integer amount; scalar inputs accept the matching type.
*/
readonly inputs: { readonly [K in keyof T]: InputSpecOf<T[K]> };
/** Optional preconditions that must hold before the flow executes (e.g. minimum balances). */
readonly preconditions?: readonly Precondition[];
/** The `0x`-prefixed address of the transaction signer. */
readonly signer: Address;
/**
* Optional assumed token amounts for resource inputs, keyed by input name.
* When a materialiser resolves amounts at execution time rather than upfront,
* assumptions let the compiler plan routes using estimated values. The keys
* must match the input names declared in the flow schema.
*/
readonly assumptions?: { readonly [K in keyof T]?: bigint };
/** Optional integrator referrer identifier. */
readonly referrer?: string;
/**
* Optional integrator fee in basis points (1bp = 0.01%, max `9000` = 90%).
* A non-zero value requires the request to be authenticated with an
* integration-scoped API key — the integration is derived from the key, not
* this field. Defaults to `0` (no fee) when omitted.
*/
readonly integratorFeeBps?: number;
/** Maximum acceptable price impact in basis points (e.g. `300` = 3%). */
readonly maxPriceImpactBps?: number;
/**
* Destination for sweeping all proxy-held terminal resources after execution.
* Pass a literal `0x`-prefixed address or `{ $ref: "context.sender" }` to
* send remaining tokens to the signer.
*/
readonly sweepTo?: SweepTo;
/**
* Controls behavior when simulation detects a revert.
* `'strict'` (default) returns an error; `'allow-revert'` returns a partial
* result with the transaction and revert diagnostics.
*/
readonly simulationPolicy?: SimulationPolicy;
/**
* When `true`, the server also simulates the final user-facing transaction
* after the structured simulation succeeds; reverts honour
* `simulationPolicy`. On success the result is echoed back on the response as
* `userProgramSimulation`. Defaults to `false`.
*/
readonly simulateUserProgram?: boolean;
/**
* When `true`, the server checks on-chain ERC-20 allowances and omits
* approvals that are already sufficient. Defaults to `false`.
*/
readonly checkOnChainAllowances?: boolean;
}
/**
* Creates an untyped materialiser input spec. This is a low-level escape hatch
* for materialiser kinds not yet covered by the generated helpers.
*
* Prefer the typed helpers in the `materialisers` namespace (e.g.
* `materialisers.balanceOf()`, `materialisers.directDeposit()`) which provide
* compile-time config validation.
*
* @param kind - The materialiser type (e.g. `"balanceOf"`, `"directDeposit"`).
* @param config - Optional materialiser-specific configuration.
* @returns A {@link MaterialiserInput} for use in {@link ComposeRunInput.inputs}.
*
* @example
* ```ts
* const run = {
* inputs: { token: materialiser('customKind', { myParam: '0x...' }) },
* signer: '0x...',
* };
* ```
*/
export const materialiser = (
kind: string,
config: Record<string, unknown> = {},
): MaterialiserInput => ({
kind,
...config,
});
/**
* Drops the keys whose value is `undefined`, so optional fields are omitted
* from the wire payload rather than serialized as `null`.
*
* A local copy of `pickDefined` from `@lifi/utils`: this package is published
* to npm and `@lifi/utils` is a private workspace package, so it cannot be a
* runtime dependency here.
*/
const pickDefined = <S extends Record<string, unknown>>(
obj: S,
): Partial<{ [K in keyof S]: Exclude<S[K], undefined> }> =>
Object.fromEntries(
Object.entries(obj).filter(([, value]) => value !== undefined),
) as Partial<{ [K in keyof S]: Exclude<S[K], undefined> }>;
/**
* Assembles a {@link ComposeRun} object from individual parameters.
* Used internally by the SDK; prefer {@link ComposeRunInput} for external use.
*
* @internal
*/
export const buildRun = (run: {
inputs: Record<string, InputSpec>;
preconditions?: readonly Precondition[];
signer: string;
assumptions?: Record<string, bigint>;
referrer?: string;
integratorFeeBps?: number;
maxPriceImpactBps?: number;
sweepTo?: SweepTo;
simulationPolicy?: SimulationPolicy;
simulateUserProgram?: boolean;
checkOnChainAllowances?: boolean;
}): ComposeRun => ({
inputs: run.inputs,
signer: run.signer,
...pickDefined({
// An empty precondition list is the same as none, so drop it too.
preconditions: run.preconditions?.length ? run.preconditions : undefined,
assumptions: run.assumptions,
referrer: run.referrer,
integratorFeeBps: run.integratorFeeBps,
maxPriceImpactBps: run.maxPriceImpactBps,
sweepTo: run.sweepTo,
simulationPolicy: run.simulationPolicy,
simulateUserProgram: run.simulateUserProgram,
checkOnChainAllowances: run.checkOnChainAllowances,
}),
});