@lifi/composer-sdk 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/authoring/FlowBuilderCore.cjs +1 -5
- package/dist/authoring/FlowBuilderCore.cjs.map +1 -1
- package/dist/authoring/FlowBuilderCore.d.cts +9 -7
- package/dist/authoring/FlowBuilderCore.d.ts +9 -7
- package/dist/authoring/FlowBuilderCore.js +2 -6
- package/dist/authoring/FlowBuilderCore.js.map +1 -1
- package/dist/authoring/handles.cjs +5 -2
- package/dist/authoring/handles.cjs.map +1 -1
- package/dist/authoring/handles.d.cts +25 -6
- package/dist/authoring/handles.d.ts +25 -6
- package/dist/authoring/handles.js +3 -1
- package/dist/authoring/handles.js.map +1 -1
- package/dist/authoring/raw.cjs +3 -0
- package/dist/authoring/raw.cjs.map +1 -1
- package/dist/authoring/raw.d.cts +33 -3
- package/dist/authoring/raw.d.ts +33 -3
- package/dist/authoring/raw.js +2 -0
- package/dist/authoring/raw.js.map +1 -1
- package/dist/authoring/signatureArgs.cjs.map +1 -1
- package/dist/authoring/signatureArgs.d.cts +16 -12
- package/dist/authoring/signatureArgs.d.ts +16 -12
- package/dist/authoring/signatureArgs.js.map +1 -1
- package/dist/client.cjs +70 -36
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +33 -4
- package/dist/client.d.ts +33 -4
- package/dist/client.js +70 -36
- package/dist/client.js.map +1 -1
- package/dist/discovery.cjs.map +1 -1
- package/dist/discovery.d.cts +18 -2
- package/dist/discovery.d.ts +18 -2
- package/dist/generated/config.generated.cjs.map +1 -1
- package/dist/generated/config.generated.d.cts +21 -1
- package/dist/generated/config.generated.d.ts +21 -1
- package/dist/generated/operations.generated.cjs +6 -0
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +18 -2
- package/dist/generated/operations.generated.d.ts +18 -2
- package/dist/generated/operations.generated.js +6 -0
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js.map +1 -1
- package/dist/{raw-IxCF_bSS.d.ts → raw-BvRghN3r.d.ts} +3 -2
- package/dist/{raw-Dj_2rBSQ.d.cts → raw-s1rer8X3.d.cts} +3 -2
- package/dist/raw.cjs +2 -0
- package/dist/raw.cjs.map +1 -1
- package/dist/raw.d.cts +1 -1
- package/dist/raw.d.ts +1 -1
- package/dist/raw.js +2 -1
- package/dist/raw.js.map +1 -1
- package/dist/run/inputs.cjs +14 -15
- package/dist/run/inputs.cjs.map +1 -1
- package/dist/run/inputs.d.cts +8 -0
- package/dist/run/inputs.d.ts +8 -0
- package/dist/run/inputs.js +14 -15
- package/dist/run/inputs.js.map +1 -1
- package/dist/run/simulate.cjs +5 -0
- package/dist/run/simulate.cjs.map +1 -1
- package/dist/run/simulate.d.cts +5 -2
- package/dist/run/simulate.d.ts +5 -2
- package/dist/run/simulate.js +5 -0
- package/dist/run/simulate.js.map +1 -1
- package/dist/sdk.cjs +2 -0
- package/dist/sdk.cjs.map +1 -1
- package/dist/sdk.d.cts +28 -1
- package/dist/sdk.d.ts +28 -1
- package/dist/sdk.js +2 -0
- package/dist/sdk.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +18 -2
- package/dist/types.d.ts +18 -2
- package/package.json +3 -10
- package/src/authoring/FlowBuilderCore.ts +13 -15
- package/src/authoring/handles.ts +41 -5
- package/src/authoring/raw.ts +36 -2
- package/src/authoring/signatureArgs.ts +17 -13
- package/src/client.ts +133 -39
- package/src/discovery.ts +18 -1
- package/src/examples/flySwap.ts +167 -0
- package/src/examples/run.ts +29 -4
- package/src/examples/supportedChains.ts +122 -0
- package/src/generated/config.generated.ts +24 -0
- package/src/generated/operations.generated.ts +26 -0
- package/src/index.ts +10 -1
- package/src/raw.ts +1 -1
- package/src/run/inputs.ts +30 -11
- package/src/run/simulate.ts +14 -2
- package/src/sdk.ts +30 -0
- package/src/types.ts +20 -1
package/dist/authoring/raw.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const ref = (path) => ({
|
|
2
2
|
$ref: path
|
|
3
3
|
});
|
|
4
|
+
const literal = (kind, value) => ({ kind, value });
|
|
4
5
|
const rawGuard = (kind, config = {}) => {
|
|
5
6
|
if ("kind" in config) {
|
|
6
7
|
throw new Error(
|
|
@@ -12,6 +13,7 @@ const rawGuard = (kind, config = {}) => {
|
|
|
12
13
|
return { ...config, kind };
|
|
13
14
|
};
|
|
14
15
|
export {
|
|
16
|
+
literal,
|
|
15
17
|
rawGuard,
|
|
16
18
|
ref
|
|
17
19
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedRef } from '../types.js';\n\n/**\n * Creates a typed `$ref` pointer for use in bind slots.\n *\n * This is an **escape hatch** for referencing values that the typed builder\n * API doesn't cover — for example, outputs of an `untypedOp` node or custom\n * context paths. The caller is responsible for choosing the correct type\n * parameter; no runtime validation occurs.\n *\n * Prefer typed handles (`OutputHandle`, `InputHandle`) and `builder.context`\n * whenever possible. Use `ref` only when you need to construct a `$ref` path\n * manually and want it accepted by a `Bindable<T>` slot.\n *\n * @typeParam T - The output kind this ref represents (e.g. `'address'`, `'uint256'`, `'resource'`).\n * @param path - The `$ref` path (e.g. `\"context.sender\"`, `\"myNode.result\"`).\n * @returns A {@link TypedRef} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * // Reference an untypedOp output in a typed operation:\n * builder.untypedOp('custom', 'some.op', {\n * bind: { x: { $ref: 'input.token' } },\n * config: {},\n * });\n * builder.core.add('sum', {\n * bind: {\n * a: raw.ref<'uint256'>('custom.result'),\n * b: someTypedHandle,\n * },\n * });\n * ```\n */\nexport const ref = <T extends OutputKind>(path: string): TypedRef<T> => ({\n $ref: path,\n});\n\n/**\n * Creates a raw guard object for use in the `guards` array of an operation call.\n *\n * @param kind - The guard type (e.g. `\"slippage\"`).\n * @param config - Guard-specific configuration (e.g. `{ toleranceBps: 300 }`).\n * @returns An {@link AppliedGuard} object.\n */\nexport const rawGuard = (\n kind: string,\n config: Record<string, unknown> = {},\n): AppliedGuard => {\n if ('kind' in config) {\n throw new Error(\n `rawGuard: 'kind' must not appear in config (got \"${String(\n config.kind,\n )}\"). Pass it as the first argument instead.`,\n );\n }\n return { ...config, kind };\n};\n"],"mappings":"AAqCO,MAAM,MAAM,CAAuB,UAA+B;AAAA,EACvE,MAAM;AACR;
|
|
1
|
+
{"version":3,"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard, SolType } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedLiteral, TypedRef } from '../types.js';\n\n/**\n * Creates a typed `$ref` pointer for use in bind slots.\n *\n * This is an **escape hatch** for referencing values that the typed builder\n * API doesn't cover — for example, outputs of an `untypedOp` node or custom\n * context paths. The caller is responsible for choosing the correct type\n * parameter; no runtime validation occurs.\n *\n * Prefer typed handles (`OutputHandle`, `InputHandle`) and `builder.context`\n * whenever possible. Use `ref` only when you need to construct a `$ref` path\n * manually and want it accepted by a `Bindable<T>` slot.\n *\n * @typeParam T - The output kind this ref represents (e.g. `'address'`, `'uint256'`, `'resource'`).\n * @param path - The `$ref` path (e.g. `\"context.sender\"`, `\"myNode.result\"`).\n * @returns A {@link TypedRef} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * // Reference an untypedOp output in a typed operation:\n * builder.untypedOp('custom', 'some.op', {\n * bind: { x: { $ref: 'input.token' } },\n * config: {},\n * });\n * builder.core.add('sum', {\n * bind: {\n * a: raw.ref<'uint256'>('custom.result'),\n * b: someTypedHandle,\n * },\n * });\n * ```\n */\nexport const ref = <T extends OutputKind>(path: string): TypedRef<T> => ({\n $ref: path,\n});\n\n/**\n * Creates a typed literal for use in bind slots: a constant baked into the\n * flow document rather than supplied at run time.\n *\n * Prefer a declared flow input and `builder.inputs.<name>` — a value the\n * caller supplies per run stays out of the document. Use a literal when the\n * flow has no run of its own to take the value from: the `continuationFlow`\n * of a `continuation.settle` node is stored by the settlement backend and\n * re-run later against the deferred proceeds, so its delivery address has to\n * be part of the committed document.\n *\n * @typeParam T - The Solidity type of the literal (e.g. `'address'`).\n * @param kind - The literal's Solidity type.\n * @param value - The literal value, as the wire format carries it: a string.\n * @returns A {@link TypedLiteral} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * builder.core.transfer('deliver', {\n * bind: {\n * amount: zap.amountOut,\n * recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),\n * },\n * config: {},\n * });\n * ```\n */\nexport const literal = <T extends SolType>(\n kind: T,\n value: string,\n): TypedLiteral<T> => ({ kind, value });\n\n/**\n * Creates a raw guard object for use in the `guards` array of an operation call.\n *\n * @param kind - The guard type (e.g. `\"slippage\"`).\n * @param config - Guard-specific configuration (e.g. `{ toleranceBps: 300 }`).\n * @returns An {@link AppliedGuard} object.\n */\nexport const rawGuard = (\n kind: string,\n config: Record<string, unknown> = {},\n): AppliedGuard => {\n if ('kind' in config) {\n throw new Error(\n `rawGuard: 'kind' must not appear in config (got \"${String(\n config.kind,\n )}\"). Pass it as the first argument instead.`,\n );\n }\n return { ...config, kind };\n};\n"],"mappings":"AAqCO,MAAM,MAAM,CAAuB,UAA+B;AAAA,EACvE,MAAM;AACR;AA+BO,MAAM,UAAU,CACrB,MACA,WACqB,EAAE,MAAM,MAAM;AAS9B,MAAM,WAAW,CACtB,MACA,SAAkC,CAAC,MAClB;AACjB,MAAI,UAAU,QAAQ;AACpB,UAAM,IAAI;AAAA,MACR,oDAAoD;AAAA,QAClD,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,EAAE,GAAG,QAAQ,KAAK;AAC3B;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/authoring/signatureArgs.ts"],"sourcesContent":["import type { Ref } from '@lifi/compose-spec';\nimport { type ParseAbiItem, parseAbiItem } from 'abitype';\n\nimport type { AbiTypeToOutputKind, TypedGuard } from '../types.js';\n\nimport type { AnyBindable, CallArgs } from './FlowBuilderCore.js';\nimport type { Bindable, OutputHandle } from './handles.js';\nimport { handleToRef } from './handles.js';\n\n/**\n * Type-level extraction of named bind keys from a function signature string.\n * When `TSig` is a string literal matching a Solidity function signature,\n * resolves to a record whose keys are the parameter names and values are\n * `Bindable<kind>` where the kind is derived from each parameter's Solidity\n * type via `AbiTypeToOutputKind`. For recognised `SolType` parameters\n * (e.g. `uint256`, `address`, `uint128`) the bind slot is precisely typed;\n * for other Solidity types (e.g. tuples) the slot accepts any scalar handle.\n * Falls back to `Record<string, AnyBindable>` for non-literal strings.\n */\nexport type SignatureBind<TSig extends string> = string extends TSig\n ? Record<string, AnyBindable>\n : ParseAbiItem<TSig> extends {\n type: 'function';\n inputs: infer I extends readonly { name: string; type: string }[];\n }\n ? {\n readonly [P in I[number] as P['name']]: Bindable<\n AbiTypeToOutputKind<P['type']>\n >;\n }\n : Record<string, AnyBindable>;\n\n// ---------------------------------------------------------------------------\n// Return-type inference from function signatures\n// ---------------------------------------------------------------------------\n\n/**\n * Describes the output type(s) of a parsed function signature as a\n * human-readable string for inclusion in type-level error messages.\n */\ntype DescribeOutputs<O extends readonly { type: string }[]> =\n O extends readonly [{ type: infer T extends string }] ? T : 'multiple values';\n\n/**\n * A string-literal type that surfaces a clear error when the user provides a\n * function signature whose return type is not supported by core.call.\n * The message is visible in IDE tooltips and compiler diagnostics.\n */\ntype UnsupportedReturnTypeError<TLabel extends string, TSig extends string> =\n ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: infer O extends readonly { type: string }[];\n }\n ? `[TypeError] ${TLabel}: return type '${DescribeOutputs<O>}' is not supported — only 'uint256' or void signatures are allowed`\n : `[TypeError] ${TLabel}: unable to parse return type from signature`;\n\n/**\n * Conditional output type for `core.call` based on the function signature's\n * return type:\n *\n * - `returns (uint256)` → `{ result: OutputHandle }`\n * - No returns clause → `{ result: undefined }`\n * - Anything else → `{ result: \"[TypeError] ...\" }` (compile-time error)\n *\n * When `TSig` is a non-literal `string` (e.g. a variable, not a string\n * constant), the type falls back to `{ result: OutputHandle | undefined }`\n * so that both void and uint256 call sites remain assignable.\n */\nexport type CoreCallResult<TSig extends string> = string extends TSig\n ? { readonly result: OutputHandle<'uint256'> | undefined }\n : ParseAbiItem<TSig> extends { type: 'function'; outputs: readonly [] }\n ? { readonly result: undefined }\n : ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: readonly [{ type: 'uint256' }];\n }\n ? { readonly result: OutputHandle<'uint256'> }\n : { readonly result: UnsupportedReturnTypeError<'core.call', TSig> };\n\n/**\n * Conditional output type for `core.staticCall`. staticCall always reads a\n * value, so void signatures are also rejected:\n *\n * - `returns (uint256)` → `{ result: OutputHandle }`\n * - No returns / other → `{ result: \"[TypeError] ...\" }` (compile-time error)\n */\nexport type StaticCallResult<TSig extends string> = string extends TSig\n ? { readonly result: OutputHandle<'uint256'> }\n : ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: readonly [{ type: 'uint256' }];\n }\n ? { readonly result: OutputHandle<'uint256'> }\n : ParseAbiItem<TSig> extends { type: 'function'; outputs: readonly [] }\n ? {\n readonly result: `[TypeError] core.staticCall: function has no return value — staticCall requires a 'returns (uint256)' signature`;\n }\n : {\n readonly result: UnsupportedReturnTypeError<'core.staticCall', TSig>;\n };\n\n/**\n * Parses a Solidity function signature and returns parameter names in order.\n * Delegates to abitype's runtime `parseAbiItem` — the same parser that powers\n * the compile-time `ParseAbiItem<TSig>` type — so runtime and type-level\n * parsing can never diverge.\n *\n * @throws if the signature is not a valid function signature or a parameter is unnamed\n */\nexport const parseFunctionParams = (functionSignature: string): string[] => {\n const parsed = parseAbiItem(functionSignature);\n if (parsed.type !== 'function') {\n throw new Error(\n `core.call: expected a function signature, got \"${parsed.type}\": \"${functionSignature}\"`,\n );\n }\n\n return parsed.inputs.map((input, index) => {\n if (!input.name) {\n throw new Error(\n `core.call: parameter at index ${index} in signature has no name. All parameters must be named for named binds to work.`,\n );\n }\n return input.name;\n });\n};\n\n/**\n * Converts any `AnyBindable` to a `Ref` (`{ $ref: string }`).\n * Handles carry a `_tag` property and are converted via `handleToRef`;\n * `Ref` objects pass through as-is.\n */\nexport const toBindRef = (bindable: AnyBindable): Ref => {\n if ('_tag' in bindable) return handleToRef(bindable);\n return bindable;\n};\n\nexport interface BuildCallWireFormatInput {\n readonly resource?: AnyBindable;\n readonly bind: Record<string, AnyBindable>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\nexport interface CallWireResult {\n readonly op: 'core.call' | 'core.invoke';\n readonly bind: Record<string, Ref>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Parses a function signature from `config`, validates bind keys against it,\n * and converts named binds to positional `$ref` args.\n */\nconst resolvePositionalArgs = (\n label: string,\n bind: Record<string, AnyBindable>,\n config: Record<string, unknown>,\n): Ref[] => {\n const functionSignature = config.functionSignature;\n if (typeof functionSignature !== 'string') {\n throw new Error(\n `${label}: config.functionSignature is required and must be a string`,\n );\n }\n\n const paramNames = parseFunctionParams(functionSignature);\n const paramSet = new Set(paramNames);\n const bindKeys = Object.keys(bind);\n const bindSet = new Set(bindKeys);\n\n const missing = paramNames.filter((n) => !bindSet.has(n));\n const extra = bindKeys.filter((k) => !paramSet.has(k));\n\n if (missing.length > 0 || extra.length > 0) {\n throw new Error(\n `${label}: bind keys [${bindKeys.join(\n ', ',\n )}] do not match signature parameters [${paramNames.join(\n ', ',\n )}]. Missing: ${missing.join(', ') || '(none)'}. Extra: ${\n extra.join(', ') || '(none)'\n }.`,\n );\n }\n\n return paramNames.map((name) => {\n const bindable = bind[name];\n if (bindable === undefined) {\n throw new Error(\n `${label}: bind value for parameter \"${name}\" is undefined`,\n );\n }\n return toBindRef(bindable);\n });\n};\n\n/**\n * Transforms the user-facing `resource` + named `bind` API into the wire\n * format that the backend expects:\n * - With resource: `core.call` with `bind: { input: <resourceRef> }`\n * - Without resource: `core.invoke` with `bind: {}`\n * - `config: { ...userConfig, args: [positional refs...] }`\n */\nexport const buildCallWireFormat = (\n args: BuildCallWireFormatInput,\n): CallWireResult => {\n const positionalArgs = resolvePositionalArgs(\n 'core.call',\n args.bind,\n args.config,\n );\n const hasResource = args.resource !== undefined;\n\n return {\n op: hasResource ? 'core.call' : 'core.invoke',\n bind: hasResource ? { input: toBindRef(args.resource) } : {},\n config: { ...args.config, args: positionalArgs },\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n};\n\nexport interface BuildStaticCallWireFormatInput {\n readonly bind: Record<string, AnyBindable>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Transforms the user-facing named `bind` API into the wire format that the\n * backend expects for `core.staticCall`. Same as `buildCallWireFormat` but\n * with no `resource` field — staticCall has no resource inputs.\n */\nexport const buildStaticCallWireFormat = (\n args: BuildStaticCallWireFormatInput,\n): CallArgs => {\n const positionalArgs = resolvePositionalArgs(\n 'core.staticCall',\n args.bind,\n args.config,\n );\n\n return {\n bind: {},\n config: { ...args.config, args: positionalArgs },\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,qBAAgD;AAMhD,qBAA4B;AAsGrB,MAAM,sBAAsB,CAAC,sBAAwC;AAC1E,QAAM,aAAS,6BAAa,iBAAiB;AAC7C,MAAI,OAAO,SAAS,YAAY;AAC9B,UAAM,IAAI;AAAA,MACR,kDAAkD,OAAO,IAAI,OAAO,iBAAiB;AAAA,IACvF;AAAA,EACF;AAEA,SAAO,OAAO,OAAO,IAAI,CAAC,OAAO,UAAU;AACzC,QAAI,CAAC,MAAM,MAAM;AACf,YAAM,IAAI;AAAA,QACR,iCAAiC,KAAK;AAAA,MACxC;AAAA,IACF;AACA,WAAO,MAAM;AAAA,EACf,CAAC;AACH;AAOO,MAAM,YAAY,CAAC,aAA+B;AACvD,MAAI,UAAU,SAAU,YAAO,4BAAY,QAAQ;AACnD,SAAO;AACT;AAoBA,MAAM,wBAAwB,CAC5B,OACA,MACA,WACU;AACV,QAAM,oBAAoB,OAAO;AACjC,MAAI,OAAO,sBAAsB,UAAU;AACzC,UAAM,IAAI;AAAA,MACR,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAEA,QAAM,aAAa,oBAAoB,iBAAiB;AACxD,QAAM,WAAW,IAAI,IAAI,UAAU;AACnC,QAAM,WAAW,OAAO,KAAK,IAAI;AACjC,QAAM,UAAU,IAAI,IAAI,QAAQ;AAEhC,QAAM,UAAU,WAAW,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AACxD,QAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;AAErD,MAAI,QAAQ,SAAS,KAAK,MAAM,SAAS,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,gBAAgB,SAAS;AAAA,QAC/B;AAAA,MACF,CAAC,wCAAwC,WAAW;AAAA,QAClD;AAAA,MACF,CAAC,eAAe,QAAQ,KAAK,IAAI,KAAK,QAAQ,YAC5C,MAAM,KAAK,IAAI,KAAK,QACtB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,WAAW,IAAI,CAAC,SAAS;AAC9B,UAAM,WAAW,KAAK,IAAI;AAC1B,QAAI,aAAa,QAAW;AAC1B,YAAM,IAAI;AAAA,QACR,GAAG,KAAK,+BAA+B,IAAI;AAAA,MAC7C;AAAA,IACF;AACA,WAAO,UAAU,QAAQ;AAAA,EAC3B,CAAC;AACH;AASO,MAAM,sBAAsB,CACjC,SACmB;AACnB,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,QAAM,cAAc,KAAK,aAAa;AAEtC,SAAO;AAAA,IACL,IAAI,cAAc,cAAc;AAAA,IAChC,MAAM,cAAc,EAAE,OAAO,UAAU,KAAK,QAAQ,EAAE,IAAI,CAAC;AAAA,IAC3D,QAAQ,EAAE,GAAG,KAAK,QAAQ,MAAM,eAAe;AAAA,IAC/C,GAAI,KAAK,UAAU,KAAK,OAAO,SAAS,KAAK,EAAE,QAAQ,KAAK,OAAO;AAAA,EACrE;AACF;AAaO,MAAM,4BAA4B,CACvC,SACa;AACb,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAEA,SAAO;AAAA,IACL,MAAM,CAAC;AAAA,IACP,QAAQ,EAAE,GAAG,KAAK,QAAQ,MAAM,eAAe;AAAA,IAC/C,GAAI,KAAK,UAAU,KAAK,OAAO,SAAS,KAAK,EAAE,QAAQ,KAAK,OAAO;AAAA,EACrE;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/authoring/signatureArgs.ts"],"sourcesContent":["import type { Ref } from '@lifi/compose-spec';\nimport { type ParseAbiItem, parseAbiItem } from 'abitype';\n\nimport type { AbiTypeToOutputKind, TypedGuard } from '../types.js';\n\nimport type { CallArgs } from './FlowBuilderCore.js';\nimport type { AnyRefBindable, OutputHandle, RefBindable } from './handles.js';\nimport { handleToRef } from './handles.js';\n\n/**\n * Type-level extraction of named bind keys from a function signature string.\n * When `TSig` is a string literal matching a Solidity function signature,\n * resolves to a record whose keys are the parameter names and values are\n * `RefBindable<kind>` where the kind is derived from each parameter's Solidity\n * type via `AbiTypeToOutputKind`. For recognised `SolType` parameters\n * (e.g. `uint256`, `address`, `uint128`) the bind slot is precisely typed;\n * for other Solidity types (e.g. tuples) the slot accepts any scalar handle.\n * Falls back to `Record<string, AnyRefBindable>` for non-literal strings.\n *\n * These slots are lowered into `config.args`, which is positional calldata\n * rather than a node `bind` record, so a literal is not accepted here: only\n * values that resolve to a `$ref`.\n */\nexport type SignatureBind<TSig extends string> = string extends TSig\n ? Record<string, AnyRefBindable>\n : ParseAbiItem<TSig> extends {\n type: 'function';\n inputs: infer I extends readonly { name: string; type: string }[];\n }\n ? {\n readonly [P in I[number] as P['name']]: RefBindable<\n AbiTypeToOutputKind<P['type']>\n >;\n }\n : Record<string, AnyRefBindable>;\n\n// ---------------------------------------------------------------------------\n// Return-type inference from function signatures\n// ---------------------------------------------------------------------------\n\n/**\n * Describes the output type(s) of a parsed function signature as a\n * human-readable string for inclusion in type-level error messages.\n */\ntype DescribeOutputs<O extends readonly { type: string }[]> =\n O extends readonly [{ type: infer T extends string }] ? T : 'multiple values';\n\n/**\n * A string-literal type that surfaces a clear error when the user provides a\n * function signature whose return type is not supported by core.call.\n * The message is visible in IDE tooltips and compiler diagnostics.\n */\ntype UnsupportedReturnTypeError<TLabel extends string, TSig extends string> =\n ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: infer O extends readonly { type: string }[];\n }\n ? `[TypeError] ${TLabel}: return type '${DescribeOutputs<O>}' is not supported — only 'uint256' or void signatures are allowed`\n : `[TypeError] ${TLabel}: unable to parse return type from signature`;\n\n/**\n * Conditional output type for `core.call` based on the function signature's\n * return type:\n *\n * - `returns (uint256)` → `{ result: OutputHandle }`\n * - No returns clause → `{ result: undefined }`\n * - Anything else → `{ result: \"[TypeError] ...\" }` (compile-time error)\n *\n * When `TSig` is a non-literal `string` (e.g. a variable, not a string\n * constant), the type falls back to `{ result: OutputHandle | undefined }`\n * so that both void and uint256 call sites remain assignable.\n */\nexport type CoreCallResult<TSig extends string> = string extends TSig\n ? { readonly result: OutputHandle<'uint256'> | undefined }\n : ParseAbiItem<TSig> extends { type: 'function'; outputs: readonly [] }\n ? { readonly result: undefined }\n : ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: readonly [{ type: 'uint256' }];\n }\n ? { readonly result: OutputHandle<'uint256'> }\n : { readonly result: UnsupportedReturnTypeError<'core.call', TSig> };\n\n/**\n * Conditional output type for `core.staticCall`. staticCall always reads a\n * value, so void signatures are also rejected:\n *\n * - `returns (uint256)` → `{ result: OutputHandle }`\n * - No returns / other → `{ result: \"[TypeError] ...\" }` (compile-time error)\n */\nexport type StaticCallResult<TSig extends string> = string extends TSig\n ? { readonly result: OutputHandle<'uint256'> }\n : ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: readonly [{ type: 'uint256' }];\n }\n ? { readonly result: OutputHandle<'uint256'> }\n : ParseAbiItem<TSig> extends { type: 'function'; outputs: readonly [] }\n ? {\n readonly result: `[TypeError] core.staticCall: function has no return value — staticCall requires a 'returns (uint256)' signature`;\n }\n : {\n readonly result: UnsupportedReturnTypeError<'core.staticCall', TSig>;\n };\n\n/**\n * Parses a Solidity function signature and returns parameter names in order.\n * Delegates to abitype's runtime `parseAbiItem` — the same parser that powers\n * the compile-time `ParseAbiItem<TSig>` type — so runtime and type-level\n * parsing can never diverge.\n *\n * @throws if the signature is not a valid function signature or a parameter is unnamed\n */\nexport const parseFunctionParams = (functionSignature: string): string[] => {\n const parsed = parseAbiItem(functionSignature);\n if (parsed.type !== 'function') {\n throw new Error(\n `core.call: expected a function signature, got \"${parsed.type}\": \"${functionSignature}\"`,\n );\n }\n\n return parsed.inputs.map((input, index) => {\n if (!input.name) {\n throw new Error(\n `core.call: parameter at index ${index} in signature has no name. All parameters must be named for named binds to work.`,\n );\n }\n return input.name;\n });\n};\n\n/**\n * Converts a ref-resolving bindable to a `Ref` (`{ $ref: string }`).\n * Handles carry a `_tag` property and are converted via `handleToRef`;\n * `Ref` objects pass through as-is.\n */\nexport const toBindRef = (bindable: AnyRefBindable): Ref => {\n if ('_tag' in bindable) return handleToRef(bindable);\n return bindable;\n};\n\nexport interface BuildCallWireFormatInput {\n readonly resource?: AnyRefBindable;\n readonly bind: Record<string, AnyRefBindable>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\nexport interface CallWireResult {\n readonly op: 'core.call' | 'core.invoke';\n readonly bind: Record<string, Ref>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Parses a function signature from `config`, validates bind keys against it,\n * and converts named binds to positional `$ref` args.\n */\nconst resolvePositionalArgs = (\n label: string,\n bind: Record<string, AnyRefBindable>,\n config: Record<string, unknown>,\n): Ref[] => {\n const functionSignature = config.functionSignature;\n if (typeof functionSignature !== 'string') {\n throw new Error(\n `${label}: config.functionSignature is required and must be a string`,\n );\n }\n\n const paramNames = parseFunctionParams(functionSignature);\n const paramSet = new Set(paramNames);\n const bindKeys = Object.keys(bind);\n const bindSet = new Set(bindKeys);\n\n const missing = paramNames.filter((n) => !bindSet.has(n));\n const extra = bindKeys.filter((k) => !paramSet.has(k));\n\n if (missing.length > 0 || extra.length > 0) {\n throw new Error(\n `${label}: bind keys [${bindKeys.join(\n ', ',\n )}] do not match signature parameters [${paramNames.join(\n ', ',\n )}]. Missing: ${missing.join(', ') || '(none)'}. Extra: ${\n extra.join(', ') || '(none)'\n }.`,\n );\n }\n\n return paramNames.map((name) => {\n const bindable = bind[name];\n if (bindable === undefined) {\n throw new Error(\n `${label}: bind value for parameter \"${name}\" is undefined`,\n );\n }\n return toBindRef(bindable);\n });\n};\n\n/**\n * Transforms the user-facing `resource` + named `bind` API into the wire\n * format that the backend expects:\n * - With resource: `core.call` with `bind: { input: <resourceRef> }`\n * - Without resource: `core.invoke` with `bind: {}`\n * - `config: { ...userConfig, args: [positional refs...] }`\n */\nexport const buildCallWireFormat = (\n args: BuildCallWireFormatInput,\n): CallWireResult => {\n const positionalArgs = resolvePositionalArgs(\n 'core.call',\n args.bind,\n args.config,\n );\n const hasResource = args.resource !== undefined;\n\n return {\n op: hasResource ? 'core.call' : 'core.invoke',\n bind: hasResource ? { input: toBindRef(args.resource) } : {},\n config: { ...args.config, args: positionalArgs },\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n};\n\nexport interface BuildStaticCallWireFormatInput {\n readonly bind: Record<string, AnyRefBindable>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Transforms the user-facing named `bind` API into the wire format that the\n * backend expects for `core.staticCall`. Same as `buildCallWireFormat` but\n * with no `resource` field — staticCall has no resource inputs.\n */\nexport const buildStaticCallWireFormat = (\n args: BuildStaticCallWireFormatInput,\n): CallArgs => {\n const positionalArgs = resolvePositionalArgs(\n 'core.staticCall',\n args.bind,\n args.config,\n );\n\n return {\n bind: {},\n config: { ...args.config, args: positionalArgs },\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,qBAAgD;AAMhD,qBAA4B;AA0GrB,MAAM,sBAAsB,CAAC,sBAAwC;AAC1E,QAAM,aAAS,6BAAa,iBAAiB;AAC7C,MAAI,OAAO,SAAS,YAAY;AAC9B,UAAM,IAAI;AAAA,MACR,kDAAkD,OAAO,IAAI,OAAO,iBAAiB;AAAA,IACvF;AAAA,EACF;AAEA,SAAO,OAAO,OAAO,IAAI,CAAC,OAAO,UAAU;AACzC,QAAI,CAAC,MAAM,MAAM;AACf,YAAM,IAAI;AAAA,QACR,iCAAiC,KAAK;AAAA,MACxC;AAAA,IACF;AACA,WAAO,MAAM;AAAA,EACf,CAAC;AACH;AAOO,MAAM,YAAY,CAAC,aAAkC;AAC1D,MAAI,UAAU,SAAU,YAAO,4BAAY,QAAQ;AACnD,SAAO;AACT;AAoBA,MAAM,wBAAwB,CAC5B,OACA,MACA,WACU;AACV,QAAM,oBAAoB,OAAO;AACjC,MAAI,OAAO,sBAAsB,UAAU;AACzC,UAAM,IAAI;AAAA,MACR,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAEA,QAAM,aAAa,oBAAoB,iBAAiB;AACxD,QAAM,WAAW,IAAI,IAAI,UAAU;AACnC,QAAM,WAAW,OAAO,KAAK,IAAI;AACjC,QAAM,UAAU,IAAI,IAAI,QAAQ;AAEhC,QAAM,UAAU,WAAW,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AACxD,QAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;AAErD,MAAI,QAAQ,SAAS,KAAK,MAAM,SAAS,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,gBAAgB,SAAS;AAAA,QAC/B;AAAA,MACF,CAAC,wCAAwC,WAAW;AAAA,QAClD;AAAA,MACF,CAAC,eAAe,QAAQ,KAAK,IAAI,KAAK,QAAQ,YAC5C,MAAM,KAAK,IAAI,KAAK,QACtB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,WAAW,IAAI,CAAC,SAAS;AAC9B,UAAM,WAAW,KAAK,IAAI;AAC1B,QAAI,aAAa,QAAW;AAC1B,YAAM,IAAI;AAAA,QACR,GAAG,KAAK,+BAA+B,IAAI;AAAA,MAC7C;AAAA,IACF;AACA,WAAO,UAAU,QAAQ;AAAA,EAC3B,CAAC;AACH;AASO,MAAM,sBAAsB,CACjC,SACmB;AACnB,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,QAAM,cAAc,KAAK,aAAa;AAEtC,SAAO;AAAA,IACL,IAAI,cAAc,cAAc;AAAA,IAChC,MAAM,cAAc,EAAE,OAAO,UAAU,KAAK,QAAQ,EAAE,IAAI,CAAC;AAAA,IAC3D,QAAQ,EAAE,GAAG,KAAK,QAAQ,MAAM,eAAe;AAAA,IAC/C,GAAI,KAAK,UAAU,KAAK,OAAO,SAAS,KAAK,EAAE,QAAQ,KAAK,OAAO;AAAA,EACrE;AACF;AAaO,MAAM,4BAA4B,CACvC,SACa;AACb,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAEA,SAAO;AAAA,IACL,MAAM,CAAC;AAAA,IACP,QAAQ,EAAE,GAAG,KAAK,QAAQ,MAAM,eAAe;AAAA,IAC/C,GAAI,KAAK,UAAU,KAAK,OAAO,SAAS,KAAK,EAAE,QAAQ,KAAK,OAAO;AAAA,EACrE;AACF;","names":[]}
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
import { Ref } from '@lifi/compose-spec';
|
|
2
2
|
import { ParseAbiItem } from 'abitype';
|
|
3
3
|
import { AbiTypeToOutputKind, TypedGuard } from '../types.cjs';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { CallArgs } from './FlowBuilderCore.cjs';
|
|
5
|
+
import { AnyRefBindable, RefBindable, OutputHandle } from './handles.cjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Type-level extraction of named bind keys from a function signature string.
|
|
9
9
|
* When `TSig` is a string literal matching a Solidity function signature,
|
|
10
10
|
* resolves to a record whose keys are the parameter names and values are
|
|
11
|
-
* `
|
|
11
|
+
* `RefBindable<kind>` where the kind is derived from each parameter's Solidity
|
|
12
12
|
* type via `AbiTypeToOutputKind`. For recognised `SolType` parameters
|
|
13
13
|
* (e.g. `uint256`, `address`, `uint128`) the bind slot is precisely typed;
|
|
14
14
|
* for other Solidity types (e.g. tuples) the slot accepts any scalar handle.
|
|
15
|
-
* Falls back to `Record<string,
|
|
15
|
+
* Falls back to `Record<string, AnyRefBindable>` for non-literal strings.
|
|
16
|
+
*
|
|
17
|
+
* These slots are lowered into `config.args`, which is positional calldata
|
|
18
|
+
* rather than a node `bind` record, so a literal is not accepted here: only
|
|
19
|
+
* values that resolve to a `$ref`.
|
|
16
20
|
*/
|
|
17
|
-
type SignatureBind<TSig extends string> = string extends TSig ? Record<string,
|
|
21
|
+
type SignatureBind<TSig extends string> = string extends TSig ? Record<string, AnyRefBindable> : ParseAbiItem<TSig> extends {
|
|
18
22
|
type: 'function';
|
|
19
23
|
inputs: infer I extends readonly {
|
|
20
24
|
name: string;
|
|
21
25
|
type: string;
|
|
22
26
|
}[];
|
|
23
27
|
} ? {
|
|
24
|
-
readonly [P in I[number] as P['name']]:
|
|
25
|
-
} : Record<string,
|
|
28
|
+
readonly [P in I[number] as P['name']]: RefBindable<AbiTypeToOutputKind<P['type']>>;
|
|
29
|
+
} : Record<string, AnyRefBindable>;
|
|
26
30
|
/**
|
|
27
31
|
* Describes the output type(s) of a parsed function signature as a
|
|
28
32
|
* human-readable string for inclusion in type-level error messages.
|
|
@@ -106,14 +110,14 @@ type StaticCallResult<TSig extends string> = string extends TSig ? {
|
|
|
106
110
|
*/
|
|
107
111
|
declare const parseFunctionParams: (functionSignature: string) => string[];
|
|
108
112
|
/**
|
|
109
|
-
* Converts
|
|
113
|
+
* Converts a ref-resolving bindable to a `Ref` (`{ $ref: string }`).
|
|
110
114
|
* Handles carry a `_tag` property and are converted via `handleToRef`;
|
|
111
115
|
* `Ref` objects pass through as-is.
|
|
112
116
|
*/
|
|
113
|
-
declare const toBindRef: (bindable:
|
|
117
|
+
declare const toBindRef: (bindable: AnyRefBindable) => Ref;
|
|
114
118
|
interface BuildCallWireFormatInput {
|
|
115
|
-
readonly resource?:
|
|
116
|
-
readonly bind: Record<string,
|
|
119
|
+
readonly resource?: AnyRefBindable;
|
|
120
|
+
readonly bind: Record<string, AnyRefBindable>;
|
|
117
121
|
readonly config: Record<string, unknown>;
|
|
118
122
|
readonly guards?: readonly TypedGuard[];
|
|
119
123
|
}
|
|
@@ -132,7 +136,7 @@ interface CallWireResult {
|
|
|
132
136
|
*/
|
|
133
137
|
declare const buildCallWireFormat: (args: BuildCallWireFormatInput) => CallWireResult;
|
|
134
138
|
interface BuildStaticCallWireFormatInput {
|
|
135
|
-
readonly bind: Record<string,
|
|
139
|
+
readonly bind: Record<string, AnyRefBindable>;
|
|
136
140
|
readonly config: Record<string, unknown>;
|
|
137
141
|
readonly guards?: readonly TypedGuard[];
|
|
138
142
|
}
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
import { Ref } from '@lifi/compose-spec';
|
|
2
2
|
import { ParseAbiItem } from 'abitype';
|
|
3
3
|
import { AbiTypeToOutputKind, TypedGuard } from '../types.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { CallArgs } from './FlowBuilderCore.js';
|
|
5
|
+
import { AnyRefBindable, RefBindable, OutputHandle } from './handles.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Type-level extraction of named bind keys from a function signature string.
|
|
9
9
|
* When `TSig` is a string literal matching a Solidity function signature,
|
|
10
10
|
* resolves to a record whose keys are the parameter names and values are
|
|
11
|
-
* `
|
|
11
|
+
* `RefBindable<kind>` where the kind is derived from each parameter's Solidity
|
|
12
12
|
* type via `AbiTypeToOutputKind`. For recognised `SolType` parameters
|
|
13
13
|
* (e.g. `uint256`, `address`, `uint128`) the bind slot is precisely typed;
|
|
14
14
|
* for other Solidity types (e.g. tuples) the slot accepts any scalar handle.
|
|
15
|
-
* Falls back to `Record<string,
|
|
15
|
+
* Falls back to `Record<string, AnyRefBindable>` for non-literal strings.
|
|
16
|
+
*
|
|
17
|
+
* These slots are lowered into `config.args`, which is positional calldata
|
|
18
|
+
* rather than a node `bind` record, so a literal is not accepted here: only
|
|
19
|
+
* values that resolve to a `$ref`.
|
|
16
20
|
*/
|
|
17
|
-
type SignatureBind<TSig extends string> = string extends TSig ? Record<string,
|
|
21
|
+
type SignatureBind<TSig extends string> = string extends TSig ? Record<string, AnyRefBindable> : ParseAbiItem<TSig> extends {
|
|
18
22
|
type: 'function';
|
|
19
23
|
inputs: infer I extends readonly {
|
|
20
24
|
name: string;
|
|
21
25
|
type: string;
|
|
22
26
|
}[];
|
|
23
27
|
} ? {
|
|
24
|
-
readonly [P in I[number] as P['name']]:
|
|
25
|
-
} : Record<string,
|
|
28
|
+
readonly [P in I[number] as P['name']]: RefBindable<AbiTypeToOutputKind<P['type']>>;
|
|
29
|
+
} : Record<string, AnyRefBindable>;
|
|
26
30
|
/**
|
|
27
31
|
* Describes the output type(s) of a parsed function signature as a
|
|
28
32
|
* human-readable string for inclusion in type-level error messages.
|
|
@@ -106,14 +110,14 @@ type StaticCallResult<TSig extends string> = string extends TSig ? {
|
|
|
106
110
|
*/
|
|
107
111
|
declare const parseFunctionParams: (functionSignature: string) => string[];
|
|
108
112
|
/**
|
|
109
|
-
* Converts
|
|
113
|
+
* Converts a ref-resolving bindable to a `Ref` (`{ $ref: string }`).
|
|
110
114
|
* Handles carry a `_tag` property and are converted via `handleToRef`;
|
|
111
115
|
* `Ref` objects pass through as-is.
|
|
112
116
|
*/
|
|
113
|
-
declare const toBindRef: (bindable:
|
|
117
|
+
declare const toBindRef: (bindable: AnyRefBindable) => Ref;
|
|
114
118
|
interface BuildCallWireFormatInput {
|
|
115
|
-
readonly resource?:
|
|
116
|
-
readonly bind: Record<string,
|
|
119
|
+
readonly resource?: AnyRefBindable;
|
|
120
|
+
readonly bind: Record<string, AnyRefBindable>;
|
|
117
121
|
readonly config: Record<string, unknown>;
|
|
118
122
|
readonly guards?: readonly TypedGuard[];
|
|
119
123
|
}
|
|
@@ -132,7 +136,7 @@ interface CallWireResult {
|
|
|
132
136
|
*/
|
|
133
137
|
declare const buildCallWireFormat: (args: BuildCallWireFormatInput) => CallWireResult;
|
|
134
138
|
interface BuildStaticCallWireFormatInput {
|
|
135
|
-
readonly bind: Record<string,
|
|
139
|
+
readonly bind: Record<string, AnyRefBindable>;
|
|
136
140
|
readonly config: Record<string, unknown>;
|
|
137
141
|
readonly guards?: readonly TypedGuard[];
|
|
138
142
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/authoring/signatureArgs.ts"],"sourcesContent":["import type { Ref } from '@lifi/compose-spec';\nimport { type ParseAbiItem, parseAbiItem } from 'abitype';\n\nimport type { AbiTypeToOutputKind, TypedGuard } from '../types.js';\n\nimport type { AnyBindable, CallArgs } from './FlowBuilderCore.js';\nimport type { Bindable, OutputHandle } from './handles.js';\nimport { handleToRef } from './handles.js';\n\n/**\n * Type-level extraction of named bind keys from a function signature string.\n * When `TSig` is a string literal matching a Solidity function signature,\n * resolves to a record whose keys are the parameter names and values are\n * `Bindable<kind>` where the kind is derived from each parameter's Solidity\n * type via `AbiTypeToOutputKind`. For recognised `SolType` parameters\n * (e.g. `uint256`, `address`, `uint128`) the bind slot is precisely typed;\n * for other Solidity types (e.g. tuples) the slot accepts any scalar handle.\n * Falls back to `Record<string, AnyBindable>` for non-literal strings.\n */\nexport type SignatureBind<TSig extends string> = string extends TSig\n ? Record<string, AnyBindable>\n : ParseAbiItem<TSig> extends {\n type: 'function';\n inputs: infer I extends readonly { name: string; type: string }[];\n }\n ? {\n readonly [P in I[number] as P['name']]: Bindable<\n AbiTypeToOutputKind<P['type']>\n >;\n }\n : Record<string, AnyBindable>;\n\n// ---------------------------------------------------------------------------\n// Return-type inference from function signatures\n// ---------------------------------------------------------------------------\n\n/**\n * Describes the output type(s) of a parsed function signature as a\n * human-readable string for inclusion in type-level error messages.\n */\ntype DescribeOutputs<O extends readonly { type: string }[]> =\n O extends readonly [{ type: infer T extends string }] ? T : 'multiple values';\n\n/**\n * A string-literal type that surfaces a clear error when the user provides a\n * function signature whose return type is not supported by core.call.\n * The message is visible in IDE tooltips and compiler diagnostics.\n */\ntype UnsupportedReturnTypeError<TLabel extends string, TSig extends string> =\n ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: infer O extends readonly { type: string }[];\n }\n ? `[TypeError] ${TLabel}: return type '${DescribeOutputs<O>}' is not supported — only 'uint256' or void signatures are allowed`\n : `[TypeError] ${TLabel}: unable to parse return type from signature`;\n\n/**\n * Conditional output type for `core.call` based on the function signature's\n * return type:\n *\n * - `returns (uint256)` → `{ result: OutputHandle }`\n * - No returns clause → `{ result: undefined }`\n * - Anything else → `{ result: \"[TypeError] ...\" }` (compile-time error)\n *\n * When `TSig` is a non-literal `string` (e.g. a variable, not a string\n * constant), the type falls back to `{ result: OutputHandle | undefined }`\n * so that both void and uint256 call sites remain assignable.\n */\nexport type CoreCallResult<TSig extends string> = string extends TSig\n ? { readonly result: OutputHandle<'uint256'> | undefined }\n : ParseAbiItem<TSig> extends { type: 'function'; outputs: readonly [] }\n ? { readonly result: undefined }\n : ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: readonly [{ type: 'uint256' }];\n }\n ? { readonly result: OutputHandle<'uint256'> }\n : { readonly result: UnsupportedReturnTypeError<'core.call', TSig> };\n\n/**\n * Conditional output type for `core.staticCall`. staticCall always reads a\n * value, so void signatures are also rejected:\n *\n * - `returns (uint256)` → `{ result: OutputHandle }`\n * - No returns / other → `{ result: \"[TypeError] ...\" }` (compile-time error)\n */\nexport type StaticCallResult<TSig extends string> = string extends TSig\n ? { readonly result: OutputHandle<'uint256'> }\n : ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: readonly [{ type: 'uint256' }];\n }\n ? { readonly result: OutputHandle<'uint256'> }\n : ParseAbiItem<TSig> extends { type: 'function'; outputs: readonly [] }\n ? {\n readonly result: `[TypeError] core.staticCall: function has no return value — staticCall requires a 'returns (uint256)' signature`;\n }\n : {\n readonly result: UnsupportedReturnTypeError<'core.staticCall', TSig>;\n };\n\n/**\n * Parses a Solidity function signature and returns parameter names in order.\n * Delegates to abitype's runtime `parseAbiItem` — the same parser that powers\n * the compile-time `ParseAbiItem<TSig>` type — so runtime and type-level\n * parsing can never diverge.\n *\n * @throws if the signature is not a valid function signature or a parameter is unnamed\n */\nexport const parseFunctionParams = (functionSignature: string): string[] => {\n const parsed = parseAbiItem(functionSignature);\n if (parsed.type !== 'function') {\n throw new Error(\n `core.call: expected a function signature, got \"${parsed.type}\": \"${functionSignature}\"`,\n );\n }\n\n return parsed.inputs.map((input, index) => {\n if (!input.name) {\n throw new Error(\n `core.call: parameter at index ${index} in signature has no name. All parameters must be named for named binds to work.`,\n );\n }\n return input.name;\n });\n};\n\n/**\n * Converts any `AnyBindable` to a `Ref` (`{ $ref: string }`).\n * Handles carry a `_tag` property and are converted via `handleToRef`;\n * `Ref` objects pass through as-is.\n */\nexport const toBindRef = (bindable: AnyBindable): Ref => {\n if ('_tag' in bindable) return handleToRef(bindable);\n return bindable;\n};\n\nexport interface BuildCallWireFormatInput {\n readonly resource?: AnyBindable;\n readonly bind: Record<string, AnyBindable>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\nexport interface CallWireResult {\n readonly op: 'core.call' | 'core.invoke';\n readonly bind: Record<string, Ref>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Parses a function signature from `config`, validates bind keys against it,\n * and converts named binds to positional `$ref` args.\n */\nconst resolvePositionalArgs = (\n label: string,\n bind: Record<string, AnyBindable>,\n config: Record<string, unknown>,\n): Ref[] => {\n const functionSignature = config.functionSignature;\n if (typeof functionSignature !== 'string') {\n throw new Error(\n `${label}: config.functionSignature is required and must be a string`,\n );\n }\n\n const paramNames = parseFunctionParams(functionSignature);\n const paramSet = new Set(paramNames);\n const bindKeys = Object.keys(bind);\n const bindSet = new Set(bindKeys);\n\n const missing = paramNames.filter((n) => !bindSet.has(n));\n const extra = bindKeys.filter((k) => !paramSet.has(k));\n\n if (missing.length > 0 || extra.length > 0) {\n throw new Error(\n `${label}: bind keys [${bindKeys.join(\n ', ',\n )}] do not match signature parameters [${paramNames.join(\n ', ',\n )}]. Missing: ${missing.join(', ') || '(none)'}. Extra: ${\n extra.join(', ') || '(none)'\n }.`,\n );\n }\n\n return paramNames.map((name) => {\n const bindable = bind[name];\n if (bindable === undefined) {\n throw new Error(\n `${label}: bind value for parameter \"${name}\" is undefined`,\n );\n }\n return toBindRef(bindable);\n });\n};\n\n/**\n * Transforms the user-facing `resource` + named `bind` API into the wire\n * format that the backend expects:\n * - With resource: `core.call` with `bind: { input: <resourceRef> }`\n * - Without resource: `core.invoke` with `bind: {}`\n * - `config: { ...userConfig, args: [positional refs...] }`\n */\nexport const buildCallWireFormat = (\n args: BuildCallWireFormatInput,\n): CallWireResult => {\n const positionalArgs = resolvePositionalArgs(\n 'core.call',\n args.bind,\n args.config,\n );\n const hasResource = args.resource !== undefined;\n\n return {\n op: hasResource ? 'core.call' : 'core.invoke',\n bind: hasResource ? { input: toBindRef(args.resource) } : {},\n config: { ...args.config, args: positionalArgs },\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n};\n\nexport interface BuildStaticCallWireFormatInput {\n readonly bind: Record<string, AnyBindable>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Transforms the user-facing named `bind` API into the wire format that the\n * backend expects for `core.staticCall`. Same as `buildCallWireFormat` but\n * with no `resource` field — staticCall has no resource inputs.\n */\nexport const buildStaticCallWireFormat = (\n args: BuildStaticCallWireFormatInput,\n): CallArgs => {\n const positionalArgs = resolvePositionalArgs(\n 'core.staticCall',\n args.bind,\n args.config,\n );\n\n return {\n bind: {},\n config: { ...args.config, args: positionalArgs },\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n};\n"],"mappings":"AACA,SAA4B,oBAAoB;AAMhD,SAAS,mBAAmB;AAsGrB,MAAM,sBAAsB,CAAC,sBAAwC;AAC1E,QAAM,SAAS,aAAa,iBAAiB;AAC7C,MAAI,OAAO,SAAS,YAAY;AAC9B,UAAM,IAAI;AAAA,MACR,kDAAkD,OAAO,IAAI,OAAO,iBAAiB;AAAA,IACvF;AAAA,EACF;AAEA,SAAO,OAAO,OAAO,IAAI,CAAC,OAAO,UAAU;AACzC,QAAI,CAAC,MAAM,MAAM;AACf,YAAM,IAAI;AAAA,QACR,iCAAiC,KAAK;AAAA,MACxC;AAAA,IACF;AACA,WAAO,MAAM;AAAA,EACf,CAAC;AACH;AAOO,MAAM,YAAY,CAAC,aAA+B;AACvD,MAAI,UAAU,SAAU,QAAO,YAAY,QAAQ;AACnD,SAAO;AACT;AAoBA,MAAM,wBAAwB,CAC5B,OACA,MACA,WACU;AACV,QAAM,oBAAoB,OAAO;AACjC,MAAI,OAAO,sBAAsB,UAAU;AACzC,UAAM,IAAI;AAAA,MACR,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAEA,QAAM,aAAa,oBAAoB,iBAAiB;AACxD,QAAM,WAAW,IAAI,IAAI,UAAU;AACnC,QAAM,WAAW,OAAO,KAAK,IAAI;AACjC,QAAM,UAAU,IAAI,IAAI,QAAQ;AAEhC,QAAM,UAAU,WAAW,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AACxD,QAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;AAErD,MAAI,QAAQ,SAAS,KAAK,MAAM,SAAS,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,gBAAgB,SAAS;AAAA,QAC/B;AAAA,MACF,CAAC,wCAAwC,WAAW;AAAA,QAClD;AAAA,MACF,CAAC,eAAe,QAAQ,KAAK,IAAI,KAAK,QAAQ,YAC5C,MAAM,KAAK,IAAI,KAAK,QACtB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,WAAW,IAAI,CAAC,SAAS;AAC9B,UAAM,WAAW,KAAK,IAAI;AAC1B,QAAI,aAAa,QAAW;AAC1B,YAAM,IAAI;AAAA,QACR,GAAG,KAAK,+BAA+B,IAAI;AAAA,MAC7C;AAAA,IACF;AACA,WAAO,UAAU,QAAQ;AAAA,EAC3B,CAAC;AACH;AASO,MAAM,sBAAsB,CACjC,SACmB;AACnB,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,QAAM,cAAc,KAAK,aAAa;AAEtC,SAAO;AAAA,IACL,IAAI,cAAc,cAAc;AAAA,IAChC,MAAM,cAAc,EAAE,OAAO,UAAU,KAAK,QAAQ,EAAE,IAAI,CAAC;AAAA,IAC3D,QAAQ,EAAE,GAAG,KAAK,QAAQ,MAAM,eAAe;AAAA,IAC/C,GAAI,KAAK,UAAU,KAAK,OAAO,SAAS,KAAK,EAAE,QAAQ,KAAK,OAAO;AAAA,EACrE;AACF;AAaO,MAAM,4BAA4B,CACvC,SACa;AACb,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAEA,SAAO;AAAA,IACL,MAAM,CAAC;AAAA,IACP,QAAQ,EAAE,GAAG,KAAK,QAAQ,MAAM,eAAe;AAAA,IAC/C,GAAI,KAAK,UAAU,KAAK,OAAO,SAAS,KAAK,EAAE,QAAQ,KAAK,OAAO;AAAA,EACrE;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/authoring/signatureArgs.ts"],"sourcesContent":["import type { Ref } from '@lifi/compose-spec';\nimport { type ParseAbiItem, parseAbiItem } from 'abitype';\n\nimport type { AbiTypeToOutputKind, TypedGuard } from '../types.js';\n\nimport type { CallArgs } from './FlowBuilderCore.js';\nimport type { AnyRefBindable, OutputHandle, RefBindable } from './handles.js';\nimport { handleToRef } from './handles.js';\n\n/**\n * Type-level extraction of named bind keys from a function signature string.\n * When `TSig` is a string literal matching a Solidity function signature,\n * resolves to a record whose keys are the parameter names and values are\n * `RefBindable<kind>` where the kind is derived from each parameter's Solidity\n * type via `AbiTypeToOutputKind`. For recognised `SolType` parameters\n * (e.g. `uint256`, `address`, `uint128`) the bind slot is precisely typed;\n * for other Solidity types (e.g. tuples) the slot accepts any scalar handle.\n * Falls back to `Record<string, AnyRefBindable>` for non-literal strings.\n *\n * These slots are lowered into `config.args`, which is positional calldata\n * rather than a node `bind` record, so a literal is not accepted here: only\n * values that resolve to a `$ref`.\n */\nexport type SignatureBind<TSig extends string> = string extends TSig\n ? Record<string, AnyRefBindable>\n : ParseAbiItem<TSig> extends {\n type: 'function';\n inputs: infer I extends readonly { name: string; type: string }[];\n }\n ? {\n readonly [P in I[number] as P['name']]: RefBindable<\n AbiTypeToOutputKind<P['type']>\n >;\n }\n : Record<string, AnyRefBindable>;\n\n// ---------------------------------------------------------------------------\n// Return-type inference from function signatures\n// ---------------------------------------------------------------------------\n\n/**\n * Describes the output type(s) of a parsed function signature as a\n * human-readable string for inclusion in type-level error messages.\n */\ntype DescribeOutputs<O extends readonly { type: string }[]> =\n O extends readonly [{ type: infer T extends string }] ? T : 'multiple values';\n\n/**\n * A string-literal type that surfaces a clear error when the user provides a\n * function signature whose return type is not supported by core.call.\n * The message is visible in IDE tooltips and compiler diagnostics.\n */\ntype UnsupportedReturnTypeError<TLabel extends string, TSig extends string> =\n ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: infer O extends readonly { type: string }[];\n }\n ? `[TypeError] ${TLabel}: return type '${DescribeOutputs<O>}' is not supported — only 'uint256' or void signatures are allowed`\n : `[TypeError] ${TLabel}: unable to parse return type from signature`;\n\n/**\n * Conditional output type for `core.call` based on the function signature's\n * return type:\n *\n * - `returns (uint256)` → `{ result: OutputHandle }`\n * - No returns clause → `{ result: undefined }`\n * - Anything else → `{ result: \"[TypeError] ...\" }` (compile-time error)\n *\n * When `TSig` is a non-literal `string` (e.g. a variable, not a string\n * constant), the type falls back to `{ result: OutputHandle | undefined }`\n * so that both void and uint256 call sites remain assignable.\n */\nexport type CoreCallResult<TSig extends string> = string extends TSig\n ? { readonly result: OutputHandle<'uint256'> | undefined }\n : ParseAbiItem<TSig> extends { type: 'function'; outputs: readonly [] }\n ? { readonly result: undefined }\n : ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: readonly [{ type: 'uint256' }];\n }\n ? { readonly result: OutputHandle<'uint256'> }\n : { readonly result: UnsupportedReturnTypeError<'core.call', TSig> };\n\n/**\n * Conditional output type for `core.staticCall`. staticCall always reads a\n * value, so void signatures are also rejected:\n *\n * - `returns (uint256)` → `{ result: OutputHandle }`\n * - No returns / other → `{ result: \"[TypeError] ...\" }` (compile-time error)\n */\nexport type StaticCallResult<TSig extends string> = string extends TSig\n ? { readonly result: OutputHandle<'uint256'> }\n : ParseAbiItem<TSig> extends {\n type: 'function';\n outputs: readonly [{ type: 'uint256' }];\n }\n ? { readonly result: OutputHandle<'uint256'> }\n : ParseAbiItem<TSig> extends { type: 'function'; outputs: readonly [] }\n ? {\n readonly result: `[TypeError] core.staticCall: function has no return value — staticCall requires a 'returns (uint256)' signature`;\n }\n : {\n readonly result: UnsupportedReturnTypeError<'core.staticCall', TSig>;\n };\n\n/**\n * Parses a Solidity function signature and returns parameter names in order.\n * Delegates to abitype's runtime `parseAbiItem` — the same parser that powers\n * the compile-time `ParseAbiItem<TSig>` type — so runtime and type-level\n * parsing can never diverge.\n *\n * @throws if the signature is not a valid function signature or a parameter is unnamed\n */\nexport const parseFunctionParams = (functionSignature: string): string[] => {\n const parsed = parseAbiItem(functionSignature);\n if (parsed.type !== 'function') {\n throw new Error(\n `core.call: expected a function signature, got \"${parsed.type}\": \"${functionSignature}\"`,\n );\n }\n\n return parsed.inputs.map((input, index) => {\n if (!input.name) {\n throw new Error(\n `core.call: parameter at index ${index} in signature has no name. All parameters must be named for named binds to work.`,\n );\n }\n return input.name;\n });\n};\n\n/**\n * Converts a ref-resolving bindable to a `Ref` (`{ $ref: string }`).\n * Handles carry a `_tag` property and are converted via `handleToRef`;\n * `Ref` objects pass through as-is.\n */\nexport const toBindRef = (bindable: AnyRefBindable): Ref => {\n if ('_tag' in bindable) return handleToRef(bindable);\n return bindable;\n};\n\nexport interface BuildCallWireFormatInput {\n readonly resource?: AnyRefBindable;\n readonly bind: Record<string, AnyRefBindable>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\nexport interface CallWireResult {\n readonly op: 'core.call' | 'core.invoke';\n readonly bind: Record<string, Ref>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Parses a function signature from `config`, validates bind keys against it,\n * and converts named binds to positional `$ref` args.\n */\nconst resolvePositionalArgs = (\n label: string,\n bind: Record<string, AnyRefBindable>,\n config: Record<string, unknown>,\n): Ref[] => {\n const functionSignature = config.functionSignature;\n if (typeof functionSignature !== 'string') {\n throw new Error(\n `${label}: config.functionSignature is required and must be a string`,\n );\n }\n\n const paramNames = parseFunctionParams(functionSignature);\n const paramSet = new Set(paramNames);\n const bindKeys = Object.keys(bind);\n const bindSet = new Set(bindKeys);\n\n const missing = paramNames.filter((n) => !bindSet.has(n));\n const extra = bindKeys.filter((k) => !paramSet.has(k));\n\n if (missing.length > 0 || extra.length > 0) {\n throw new Error(\n `${label}: bind keys [${bindKeys.join(\n ', ',\n )}] do not match signature parameters [${paramNames.join(\n ', ',\n )}]. Missing: ${missing.join(', ') || '(none)'}. Extra: ${\n extra.join(', ') || '(none)'\n }.`,\n );\n }\n\n return paramNames.map((name) => {\n const bindable = bind[name];\n if (bindable === undefined) {\n throw new Error(\n `${label}: bind value for parameter \"${name}\" is undefined`,\n );\n }\n return toBindRef(bindable);\n });\n};\n\n/**\n * Transforms the user-facing `resource` + named `bind` API into the wire\n * format that the backend expects:\n * - With resource: `core.call` with `bind: { input: <resourceRef> }`\n * - Without resource: `core.invoke` with `bind: {}`\n * - `config: { ...userConfig, args: [positional refs...] }`\n */\nexport const buildCallWireFormat = (\n args: BuildCallWireFormatInput,\n): CallWireResult => {\n const positionalArgs = resolvePositionalArgs(\n 'core.call',\n args.bind,\n args.config,\n );\n const hasResource = args.resource !== undefined;\n\n return {\n op: hasResource ? 'core.call' : 'core.invoke',\n bind: hasResource ? { input: toBindRef(args.resource) } : {},\n config: { ...args.config, args: positionalArgs },\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n};\n\nexport interface BuildStaticCallWireFormatInput {\n readonly bind: Record<string, AnyRefBindable>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Transforms the user-facing named `bind` API into the wire format that the\n * backend expects for `core.staticCall`. Same as `buildCallWireFormat` but\n * with no `resource` field — staticCall has no resource inputs.\n */\nexport const buildStaticCallWireFormat = (\n args: BuildStaticCallWireFormatInput,\n): CallArgs => {\n const positionalArgs = resolvePositionalArgs(\n 'core.staticCall',\n args.bind,\n args.config,\n );\n\n return {\n bind: {},\n config: { ...args.config, args: positionalArgs },\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n};\n"],"mappings":"AACA,SAA4B,oBAAoB;AAMhD,SAAS,mBAAmB;AA0GrB,MAAM,sBAAsB,CAAC,sBAAwC;AAC1E,QAAM,SAAS,aAAa,iBAAiB;AAC7C,MAAI,OAAO,SAAS,YAAY;AAC9B,UAAM,IAAI;AAAA,MACR,kDAAkD,OAAO,IAAI,OAAO,iBAAiB;AAAA,IACvF;AAAA,EACF;AAEA,SAAO,OAAO,OAAO,IAAI,CAAC,OAAO,UAAU;AACzC,QAAI,CAAC,MAAM,MAAM;AACf,YAAM,IAAI;AAAA,QACR,iCAAiC,KAAK;AAAA,MACxC;AAAA,IACF;AACA,WAAO,MAAM;AAAA,EACf,CAAC;AACH;AAOO,MAAM,YAAY,CAAC,aAAkC;AAC1D,MAAI,UAAU,SAAU,QAAO,YAAY,QAAQ;AACnD,SAAO;AACT;AAoBA,MAAM,wBAAwB,CAC5B,OACA,MACA,WACU;AACV,QAAM,oBAAoB,OAAO;AACjC,MAAI,OAAO,sBAAsB,UAAU;AACzC,UAAM,IAAI;AAAA,MACR,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAEA,QAAM,aAAa,oBAAoB,iBAAiB;AACxD,QAAM,WAAW,IAAI,IAAI,UAAU;AACnC,QAAM,WAAW,OAAO,KAAK,IAAI;AACjC,QAAM,UAAU,IAAI,IAAI,QAAQ;AAEhC,QAAM,UAAU,WAAW,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AACxD,QAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;AAErD,MAAI,QAAQ,SAAS,KAAK,MAAM,SAAS,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,gBAAgB,SAAS;AAAA,QAC/B;AAAA,MACF,CAAC,wCAAwC,WAAW;AAAA,QAClD;AAAA,MACF,CAAC,eAAe,QAAQ,KAAK,IAAI,KAAK,QAAQ,YAC5C,MAAM,KAAK,IAAI,KAAK,QACtB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,WAAW,IAAI,CAAC,SAAS;AAC9B,UAAM,WAAW,KAAK,IAAI;AAC1B,QAAI,aAAa,QAAW;AAC1B,YAAM,IAAI;AAAA,QACR,GAAG,KAAK,+BAA+B,IAAI;AAAA,MAC7C;AAAA,IACF;AACA,WAAO,UAAU,QAAQ;AAAA,EAC3B,CAAC;AACH;AASO,MAAM,sBAAsB,CACjC,SACmB;AACnB,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,QAAM,cAAc,KAAK,aAAa;AAEtC,SAAO;AAAA,IACL,IAAI,cAAc,cAAc;AAAA,IAChC,MAAM,cAAc,EAAE,OAAO,UAAU,KAAK,QAAQ,EAAE,IAAI,CAAC;AAAA,IAC3D,QAAQ,EAAE,GAAG,KAAK,QAAQ,MAAM,eAAe;AAAA,IAC/C,GAAI,KAAK,UAAU,KAAK,OAAO,SAAS,KAAK,EAAE,QAAQ,KAAK,OAAO;AAAA,EACrE;AACF;AAaO,MAAM,4BAA4B,CACvC,SACa;AACb,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAEA,SAAO;AAAA,IACL,MAAM,CAAC;AAAA,IACP,QAAQ,EAAE,GAAG,KAAK,QAAQ,MAAM,eAAe;AAAA,IAC/C,GAAI,KAAK,UAAU,KAAK,OAAO,SAAS,KAAK,EAAE,QAAQ,KAAK,OAAO;AAAA,EACrE;AACF;","names":[]}
|
package/dist/client.cjs
CHANGED
|
@@ -23,9 +23,52 @@ __export(client_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(client_exports);
|
|
24
24
|
var import_errors = require("./errors.js");
|
|
25
25
|
var import_responseSchemas = require("./responseSchemas.js");
|
|
26
|
-
const SDK_VERSION = true ? "0.
|
|
26
|
+
const SDK_VERSION = true ? "0.5.0" : "dev";
|
|
27
27
|
const bigintReplacer = (_key, value) => typeof value === "bigint" ? value.toString() : value;
|
|
28
28
|
const isNonNullObject = (v) => typeof v === "object" && v !== null;
|
|
29
|
+
const INTEGER_STRING = /^\d+$/;
|
|
30
|
+
const decodeAmount = (value, key, field, url) => {
|
|
31
|
+
if (typeof value !== "string" || !INTEGER_STRING.test(value)) {
|
|
32
|
+
throw new import_errors.ComposeError(
|
|
33
|
+
"UNKNOWN_ERROR",
|
|
34
|
+
`Malformed compose response: producedResources.${key}.simulated.${field} is ${value === void 0 ? "absent" : "not an integer string"}`,
|
|
35
|
+
{ url }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return BigInt(value);
|
|
39
|
+
};
|
|
40
|
+
const decodeSimulatedAmounts = (data, url) => {
|
|
41
|
+
if (!isNonNullObject(data)) return;
|
|
42
|
+
const resources = data.producedResources;
|
|
43
|
+
if (!isNonNullObject(resources)) return;
|
|
44
|
+
for (const key of Object.keys(resources)) {
|
|
45
|
+
const resource = resources[key];
|
|
46
|
+
if (!isNonNullObject(resource)) continue;
|
|
47
|
+
const simulated = resource.simulated;
|
|
48
|
+
if (simulated === void 0) continue;
|
|
49
|
+
if (!isNonNullObject(simulated)) {
|
|
50
|
+
throw new import_errors.ComposeError(
|
|
51
|
+
"UNKNOWN_ERROR",
|
|
52
|
+
`Malformed compose response: producedResources.${key}.simulated is not an object`,
|
|
53
|
+
{ url }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
simulated.amountOut = decodeAmount(
|
|
57
|
+
simulated.amountOut,
|
|
58
|
+
key,
|
|
59
|
+
"amountOut",
|
|
60
|
+
url
|
|
61
|
+
);
|
|
62
|
+
if (simulated.amountOutMin !== void 0) {
|
|
63
|
+
simulated.amountOutMin = decodeAmount(
|
|
64
|
+
simulated.amountOutMin,
|
|
65
|
+
key,
|
|
66
|
+
"amountOutMin",
|
|
67
|
+
url
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
29
72
|
const parseBody = async (res, url) => {
|
|
30
73
|
const body = await res.json().catch((_) => null);
|
|
31
74
|
if (!isNonNullObject(body) || !("data" in body)) {
|
|
@@ -37,6 +80,7 @@ const parseBody = async (res, url) => {
|
|
|
37
80
|
};
|
|
38
81
|
const parseCompileSuccessBody = async (res, url) => {
|
|
39
82
|
const data = await parseBody(res, url);
|
|
83
|
+
decodeSimulatedAmounts(data, url);
|
|
40
84
|
return { ...data, status: "success" };
|
|
41
85
|
};
|
|
42
86
|
const parsePartialBody = async (res, url) => {
|
|
@@ -50,6 +94,7 @@ const parsePartialBody = async (res, url) => {
|
|
|
50
94
|
);
|
|
51
95
|
}
|
|
52
96
|
const data = envelope.data;
|
|
97
|
+
decodeSimulatedAmounts(data, url);
|
|
53
98
|
return { ...data, status: "partial", error: envelope.error };
|
|
54
99
|
};
|
|
55
100
|
const parseSimulateBody = async (res, url) => {
|
|
@@ -85,6 +130,23 @@ const postCompile = async ({ fetchFn, baseHeaders }, url, request) => {
|
|
|
85
130
|
}
|
|
86
131
|
return await parseCompileSuccessBody(res, url);
|
|
87
132
|
};
|
|
133
|
+
const getJson = async ({ fetchFn, baseHeaders }, url) => {
|
|
134
|
+
let res;
|
|
135
|
+
try {
|
|
136
|
+
res = await fetchFn(url, {
|
|
137
|
+
method: "GET",
|
|
138
|
+
headers: { ...baseHeaders }
|
|
139
|
+
});
|
|
140
|
+
} catch (err) {
|
|
141
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
142
|
+
throw new import_errors.ComposeError("NETWORK_ERROR", message, { cause: err });
|
|
143
|
+
}
|
|
144
|
+
if (!res.ok) {
|
|
145
|
+
const body = await res.text();
|
|
146
|
+
throw (0, import_errors.errorFromHttpResponse)(res.status, body, url);
|
|
147
|
+
}
|
|
148
|
+
return await parseBody(res, url);
|
|
149
|
+
};
|
|
88
150
|
const createComposeClient = (options) => {
|
|
89
151
|
if (!options.baseUrl || !/^https?:\/\//i.test(options.baseUrl)) {
|
|
90
152
|
throw new import_errors.ComposeError(
|
|
@@ -107,24 +169,8 @@ const createComposeClient = (options) => {
|
|
|
107
169
|
"x-lifi-api-key": trimmedApiKey
|
|
108
170
|
};
|
|
109
171
|
const transport = { fetchFn, baseHeaders };
|
|
110
|
-
const getManifest = async () => {
|
|
111
|
-
|
|
112
|
-
let res;
|
|
113
|
-
try {
|
|
114
|
-
res = await fetchFn(url, {
|
|
115
|
-
method: "GET",
|
|
116
|
-
headers: { ...baseHeaders }
|
|
117
|
-
});
|
|
118
|
-
} catch (err) {
|
|
119
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
120
|
-
throw new import_errors.ComposeError("NETWORK_ERROR", message, { cause: err });
|
|
121
|
-
}
|
|
122
|
-
if (!res.ok) {
|
|
123
|
-
const body = await res.text();
|
|
124
|
-
throw (0, import_errors.errorFromHttpResponse)(res.status, body, url);
|
|
125
|
-
}
|
|
126
|
-
return await parseBody(res, url);
|
|
127
|
-
};
|
|
172
|
+
const getManifest = async () => getJson(transport, `${base}/compose/manifest`);
|
|
173
|
+
const getChains = async () => getJson(transport, `${base}/chains`);
|
|
128
174
|
const compile = async (request) => postCompile(transport, `${base}/compose`, request);
|
|
129
175
|
const route = async (request) => postCompile(transport, `${base}/compose/route`, request);
|
|
130
176
|
const getZapPacks = async (options2) => {
|
|
@@ -135,22 +181,10 @@ const createComposeClient = (options) => {
|
|
|
135
181
|
params.set("protocols", list);
|
|
136
182
|
}
|
|
137
183
|
const qs = params.toString();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
method: "GET",
|
|
143
|
-
headers: { ...baseHeaders }
|
|
144
|
-
});
|
|
145
|
-
} catch (err) {
|
|
146
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
147
|
-
throw new import_errors.ComposeError("NETWORK_ERROR", message, { cause: err });
|
|
148
|
-
}
|
|
149
|
-
if (!res.ok) {
|
|
150
|
-
const body = await res.text();
|
|
151
|
-
throw (0, import_errors.errorFromHttpResponse)(res.status, body, url);
|
|
152
|
-
}
|
|
153
|
-
return await parseBody(res, url);
|
|
184
|
+
return getJson(
|
|
185
|
+
transport,
|
|
186
|
+
`${base}/compose/zap-packs${qs ? `?${qs}` : ""}`
|
|
187
|
+
);
|
|
154
188
|
};
|
|
155
189
|
const simulate = async (request) => {
|
|
156
190
|
const url = `${base}/simulate`;
|
|
@@ -171,7 +205,7 @@ const createComposeClient = (options) => {
|
|
|
171
205
|
const body = await res.text();
|
|
172
206
|
throw (0, import_errors.errorFromHttpResponse)(res.status, body, url);
|
|
173
207
|
};
|
|
174
|
-
return { getManifest, compile, route, getZapPacks, simulate };
|
|
208
|
+
return { getManifest, getChains, compile, route, getZapPacks, simulate };
|
|
175
209
|
};
|
|
176
210
|
// Annotate the CommonJS export names for ESM import in node:
|
|
177
211
|
0 && (module.exports = {
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n ComposeCompilePartialData,\n ComposeCompileRequest,\n ComposeCompileResult,\n ComposeCompileSuccessData,\n ComposeManifest,\n ComposeRouteRequest,\n SimulateRequest,\n SimulateResult,\n} from '@lifi/compose-spec';\n\nimport type { GetZapPacksOptions, ZapPackOverview } from './discovery.js';\nimport { ComposeError, errorFromHttpResponse } from './errors.js';\nimport {\n parseCompilePartialEnvelope,\n parseSimulateResult,\n} from './responseSchemas.js';\n\n// __SDK_VERSION__ is a compile-time constant injected by tsup (via `define` in tsup.config.ts)\n// and by vitest (via `define` in vitest.config.ts). Both read the version from package.json\n// at build/test time and replace this identifier with the literal string value.\n// It is sent as the `x-lifi-composer-sdk` request header so the server can identify the caller.\n// Falls back to 'dev' when running via tsx without tsup substitution (e.g. the example harness).\ndeclare const __SDK_VERSION__: string;\n\nconst SDK_VERSION: string =\n typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : 'dev';\n\n/**\n * Configuration for creating a low-level Compose API client.\n */\nexport interface ComposeClientOptions {\n /** Base URL of the Compose API. */\n readonly baseUrl: string;\n /** Optional custom `fetch` implementation. Defaults to `globalThis.fetch`. */\n readonly fetch?: typeof globalThis.fetch;\n /** LI.FI API key, sent as the `x-lifi-api-key` header on every request. Required — the Compose API rejects unauthenticated requests. */\n readonly apiKey: string;\n}\n\n/**\n * Low-level HTTP client for the Compose API.\n *\n * Handles request serialization, SDK version headers, and error mapping.\n * Prefer using {@link ComposeSdk} for the full builder experience. Use this\n * directly when you need to decouple request building from submission — e.g.\n * build via `sdk.request()` then submit via `client.compile()` with custom\n * retry logic or request inspection.\n */\nexport interface ComposeClient {\n /**\n * Fetches the server's operation manifest describing all supported operations,\n * guards, materialisers, and preconditions.\n * @returns The manifest document.\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly getManifest: () => Promise<ComposeManifest>;\n /**\n * Submits a compile request and returns the result.\n *\n * When the caller passes `simulationPolicy: 'allow-revert'` and the transaction\n * reverts in simulation, the server responds with HTTP 206 and the SDK returns a\n * partial result (`status: 'partial'`) instead of throwing. The partial result\n * includes the transaction (without `gasLimit`) and revert diagnostics.\n *\n * @param request - The full compile request including flow and run inputs.\n * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly compile: (\n request: ComposeCompileRequest,\n ) => Promise<ComposeCompileResult>;\n /**\n * Compiles a token pair into a submit-ready transaction via\n * `POST /compose/route`.\n *\n * A convenience path that skips flow authoring: the server builds a one-node\n * `lifi.zap` flow with a `directDeposit` input from the `fromToken` /\n * `toToken` pair and runs it through the same pipeline as {@link compile}.\n * The response is therefore the same discriminated result — HTTP 206 under\n * `simulationPolicy: 'allow-revert'` yields `status: 'partial'` rather than\n * throwing, exactly as it does for {@link compile}.\n *\n * As of today that flow is a single zap step: one edge of the backend's\n * routing catalog (a position entry or exit, a wrap or unwrap, a mint or\n * burn). Enumerate the pairs it covers with {@link getZapPacks}; a pair with\n * no edge is rejected with `kind: 'no_route_error'`. The endpoint is meant to\n * author richer flows later, so the single-step limit is the current state of\n * the server, not the shape of this contract.\n *\n * Use {@link compile} for what the server does not author for you: several\n * chained operations, splits, explicit preconditions, or per-op guards.\n *\n * `bigint` amounts in `amount` are serialised to decimal strings\n * automatically.\n *\n * @param request - The from/to token pair, amount, signer, and route options.\n * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.\n * @throws {@link ComposeError} on network, validation, or server errors —\n * including `NOT_FOUND` (HTTP 404) when no catalog edge covers the pair.\n */\n readonly route: (\n request: ComposeRouteRequest,\n ) => Promise<ComposeCompileResult>;\n /**\n * Fetches the available routing edges grouped by protocol.\n *\n * The edge catalog is dynamic — it reflects the current state of the\n * backend's routing snapshot (protocols, chains, token blacklists).\n * Results are not cached by the SDK; callers should cache as appropriate.\n *\n * @param options - Optional filter to restrict results to specific protocols.\n * @returns An array of {@link ZapPackOverview} objects, one per protocol.\n * @throws {@link ComposeError} on network or server errors (503 when the\n * routing catalog is not yet initialized).\n */\n readonly getZapPacks: (\n options?: GetZapPacksOptions,\n ) => Promise<readonly ZapPackOverview[]>;\n /**\n * Simulates a raw, pre-encoded transaction against `POST /simulate` and\n * reports how the watched balances change and how much inner-call gas it\n * burns.\n *\n * The result is a discriminated union on `status`:\n * - `'ok'` — the simulation ran successfully; `deltas`/`gasUsed` are populated.\n * - `'revert'` — the simulation ran but the transaction reverted on-chain. A\n * revert is a *successful simulation*, not a transport error, so it is\n * returned (HTTP 200) rather than thrown — mirroring how {@link compile}\n * returns `status: 'partial'` on a simulated revert.\n * - `'error'` — the request was well-formed but the simulation could not be\n * set up or run (HTTP 422); `message` is intentionally generic.\n *\n * `bigint` amounts in the request (`value`, requirement `balance`/`allowance`)\n * are serialised to decimal strings automatically.\n *\n * @param request - The raw transaction plus funding `requirements` and the\n * `trackedBalances` to watch.\n * @returns A {@link SimulateResult} (`ok` / `revert` / `error`).\n * @throws {@link ComposeError} on network failures, HTTP 400 (malformed\n * input), 401/403 (auth), 404, 429, and 5xx.\n */\n readonly simulate: (request: SimulateRequest) => Promise<SimulateResult>;\n}\n\nconst bigintReplacer = (_key: string, value: unknown): unknown =>\n typeof value === 'bigint' ? value.toString() : value;\n\nconst isNonNullObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null;\n\nconst parseBody = async <T>(res: Response, url: string): Promise<T> => {\n const body = await res.json().catch((_) => null);\n if (!isNonNullObject(body) || !('data' in body)) {\n throw new ComposeError('UNKNOWN_ERROR', 'Unexpected response format', {\n url,\n });\n }\n return body.data as T;\n};\n\nconst parseCompileSuccessBody = async (\n res: Response,\n url: string,\n): Promise<ComposeCompileResult> => {\n const data = await parseBody<ComposeCompileSuccessData>(res, url);\n return { ...data, status: 'success' as const };\n};\n\nconst parsePartialBody = async (\n res: Response,\n url: string,\n): Promise<ComposeCompileResult> => {\n const body = await res.json().catch((_) => null);\n const envelope = parseCompilePartialEnvelope(body);\n if (envelope === null) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n 'Unexpected partial response format',\n { url },\n );\n }\n // `data` is validated as an object by the schema; compose-spec owns its full\n // shape as a hand-authored type, so we narrow it here rather than re-declaring\n // that type as a schema. `error` is fully validated — no cast needed.\n const data = envelope.data as unknown as ComposeCompilePartialData;\n return { ...data, status: 'partial' as const, error: envelope.error };\n};\n\n// `/simulate` is un-enveloped: the discriminated body (`{ status, ... }`) is at\n// the top level, NOT wrapped in `{ data }` like `/compose`. So this reads the\n// body directly and validates it against the simulate union rather than reusing\n// `parseBody`.\nconst parseSimulateBody = async (\n res: Response,\n url: string,\n): Promise<SimulateResult> => {\n const body = await res.json().catch((_) => null);\n const result = parseSimulateResult(body);\n if (result === null) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n 'Unexpected simulate response format',\n { url },\n );\n }\n return result;\n};\n// `POST /compose` and `POST /compose/route` differ only in path and request\n// shape: both return the enveloped success data on 200 and the partial\n// envelope on 206, so they share one transport.\ntype ComposeTransport = {\n readonly fetchFn: typeof fetch;\n readonly baseHeaders: Record<string, string>;\n};\n\nconst postCompile = async (\n { fetchFn, baseHeaders }: ComposeTransport,\n url: string,\n request: ComposeCompileRequest | ComposeRouteRequest,\n): Promise<ComposeCompileResult> => {\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(request, bigintReplacer),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (res.status === 206) {\n return await parsePartialBody(res, url);\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseCompileSuccessBody(res, url);\n};\n\n/**\n * Creates a low-level Compose API client.\n *\n * @param options - Client configuration including the API base URL.\n * @returns A {@link ComposeClient} instance.\n */\nexport const createComposeClient = (\n options: ComposeClientOptions,\n): ComposeClient => {\n if (!options.baseUrl || !/^https?:\\/\\//i.test(options.baseUrl)) {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `Invalid baseUrl: expected an HTTP(S) URL, got \"${options.baseUrl}\"`,\n );\n }\n const trimmedApiKey = options.apiKey?.trim() || undefined;\n if (!trimmedApiKey) {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n 'apiKey is required: pass a LI.FI API key to createComposeSdk().',\n );\n }\n const fetchFn = options.fetch ?? globalThis.fetch;\n const base = options.baseUrl.replace(/\\/$/, '');\n\n const baseHeaders: Record<string, string> = {\n Accept: 'application/json',\n 'x-lifi-composer-sdk': SDK_VERSION,\n 'x-lifi-api-key': trimmedApiKey,\n };\n const transport: ComposeTransport = { fetchFn, baseHeaders };\n\n const getManifest = async (): Promise<ComposeManifest> => {\n const url = `${base}/compose/manifest`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'GET',\n headers: { ...baseHeaders },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<ComposeManifest>(res, url);\n };\n\n const compile = async (\n request: ComposeCompileRequest,\n ): Promise<ComposeCompileResult> =>\n postCompile(transport, `${base}/compose`, request);\n\n const route = async (\n request: ComposeRouteRequest,\n ): Promise<ComposeCompileResult> =>\n postCompile(transport, `${base}/compose/route`, request);\n\n const getZapPacks = async (\n options?: GetZapPacksOptions,\n ): Promise<readonly ZapPackOverview[]> => {\n const params = new URLSearchParams();\n if (options?.protocols !== undefined) {\n // Backend expects a single comma-separated value, not repeated keys.\n const raw = options.protocols;\n const list = typeof raw === 'string' ? raw : raw.join(',');\n params.set('protocols', list);\n }\n const qs = params.toString();\n const url = `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'GET',\n headers: { ...baseHeaders },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<readonly ZapPackOverview[]>(res, url);\n };\n\n const simulate = async (\n request: SimulateRequest,\n ): Promise<SimulateResult> => {\n const url = `${base}/simulate`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(request, bigintReplacer),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n // Only 200 (carries `ok`/`revert`) and 422 (carries the `error` member of\n // the union) have a discriminated body. 422 is deliberately intercepted\n // here (not thrown as VALIDATION_ERROR) so callers get one exhaustive\n // `switch (result.status)`. Every other status is a transport error and is\n // thrown — including HTTP 400 (malformed input, no `status` body) and any\n // unexpected 2xx.\n if (res.status === 200 || res.status === 422) {\n return await parseSimulateBody(res, url);\n }\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n };\n\n return { getManifest, compile, route, getZapPacks, simulate };\n};\n"],"mappings":"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","names":["options"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n ComposeCompilePartialData,\n ComposeCompileRequest,\n ComposeCompileResult,\n ComposeCompileSuccessData,\n ComposeManifest,\n ComposeRouteRequest,\n SimulateRequest,\n SimulateResult,\n} from '@lifi/compose-spec';\n\nimport type {\n ChainSummary,\n GetZapPacksOptions,\n ZapPackOverview,\n} from './discovery.js';\nimport { ComposeError, errorFromHttpResponse } from './errors.js';\nimport {\n parseCompilePartialEnvelope,\n parseSimulateResult,\n} from './responseSchemas.js';\n\n// __SDK_VERSION__ is a compile-time constant injected by tsup (via `define` in tsup.config.ts)\n// and by vitest (via `define` in vitest.config.ts). Both read the version from package.json\n// at build/test time and replace this identifier with the literal string value.\n// It is sent as the `x-lifi-composer-sdk` request header so the server can identify the caller.\n// Falls back to 'dev' when running via tsx without tsup substitution (e.g. the example harness).\ndeclare const __SDK_VERSION__: string;\n\nconst SDK_VERSION: string =\n typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : 'dev';\n\n/**\n * Configuration for creating a low-level Compose API client.\n */\nexport interface ComposeClientOptions {\n /** Base URL of the Compose API. */\n readonly baseUrl: string;\n /** Optional custom `fetch` implementation. Defaults to `globalThis.fetch`. */\n readonly fetch?: typeof globalThis.fetch;\n /** LI.FI API key, sent as the `x-lifi-api-key` header on every request. Required — the Compose API rejects unauthenticated requests. */\n readonly apiKey: string;\n}\n\n/**\n * Low-level HTTP client for the Compose API.\n *\n * Handles request serialization, SDK version headers, and error mapping.\n * Prefer using {@link ComposeSdk} for the full builder experience. Use this\n * directly when you need to decouple request building from submission — e.g.\n * build via `sdk.request()` then submit via `client.compile()` with custom\n * retry logic or request inspection.\n */\nexport interface ComposeClient {\n /**\n * Fetches the server's operation manifest describing all supported operations,\n * guards, materialisers, and preconditions.\n * @returns The manifest document.\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly getManifest: () => Promise<ComposeManifest>;\n /**\n * Fetches the chains the Compose API supports, ascending by `chainId`.\n *\n * A chain is listed once the server confirms the VM contract is deployed on\n * it — the same condition {@link compile}, {@link route} and\n * {@link simulate} validate against. A chain absent from this list is\n * rejected outright by those endpoints.\n *\n * The converse does not hold: a listed chain can still have no routing edge\n * for a given token pair, which {@link route} reports as `no_route_error`.\n * Use {@link getZapPacks} to enumerate the pairs that do route.\n *\n * Each entry carries a display `name` alongside its `chainId`. The name is a\n * deployment-maintained label — render it, but key on `chainId`.\n *\n * The list tracks the server's chain registry, which refreshes periodically.\n * Results are not cached by the SDK; callers should cache as appropriate.\n *\n * @returns The supported chains, ascending by `chainId`.\n * @throws {@link ComposeError} on network or server errors.\n */\n readonly getChains: () => Promise<readonly ChainSummary[]>;\n /**\n * Submits a compile request and returns the result.\n *\n * When the caller passes `simulationPolicy: 'allow-revert'` and the transaction\n * reverts in simulation, the server responds with HTTP 206 and the SDK returns a\n * partial result (`status: 'partial'`) instead of throwing. The partial result\n * includes the transaction (without `gasLimit`) and revert diagnostics.\n *\n * @param request - The full compile request including flow and run inputs.\n * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly compile: (\n request: ComposeCompileRequest,\n ) => Promise<ComposeCompileResult>;\n /**\n * Compiles a token pair into a submit-ready transaction via\n * `POST /compose/route`.\n *\n * A convenience path that skips flow authoring: the server builds a one-node\n * zap flow with a `directDeposit` input from the `fromToken` / `toToken`\n * pair and runs it through the same pipeline as {@link compile}. The\n * response is therefore the same discriminated result — HTTP 206 under\n * `simulationPolicy: 'allow-revert'` yields `status: 'partial'` rather than\n * throwing, exactly as it does for {@link compile}.\n *\n * As of today that flow is a single zap step: one edge of the backend's\n * routing catalog (a position entry or exit, a wrap or unwrap, a mint or\n * burn). Enumerate the pairs it covers with {@link getZapPacks}; a pair with\n * no edge is rejected with `kind: 'no_route_error'`. The endpoint is meant to\n * author richer flows later, so the single-step limit is the current state of\n * the server, not the shape of this contract.\n *\n * Only synchronous edges are considered unless the request sets\n * `allowAsyncRoutes: true`, so by default every produced resource comes back\n * `availability: 'now'` and a pair reachable only after settlement is a\n * `no_route_error`. Opting in adds the `lifi.zapAsync` fallback, which still\n * loses to a synchronous edge when the pair has one; read\n * `producedResources[*].availability` to see which representation you got.\n *\n * Use {@link compile} for what the server does not author for you: several\n * chained operations, splits, explicit preconditions, or per-op guards.\n *\n * `bigint` amounts in `amount` are serialised to decimal strings\n * automatically.\n *\n * @param request - The from/to token pair, amount, signer, and route options.\n * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.\n * @throws {@link ComposeError} on network, validation, or server errors —\n * including `NOT_FOUND` (HTTP 404) when no catalog edge covers the pair.\n */\n readonly route: (\n request: ComposeRouteRequest,\n ) => Promise<ComposeCompileResult>;\n /**\n * Fetches the available routing edges grouped by protocol.\n *\n * The edge catalog is dynamic — it reflects the current state of the\n * backend's routing snapshot (protocols, chains, token blacklists).\n * Results are not cached by the SDK; callers should cache as appropriate.\n *\n * @param options - Optional filter to restrict results to specific protocols.\n * @returns An array of {@link ZapPackOverview} objects, one per protocol.\n * @throws {@link ComposeError} on network or server errors (503 when the\n * routing catalog is not yet initialized).\n */\n readonly getZapPacks: (\n options?: GetZapPacksOptions,\n ) => Promise<readonly ZapPackOverview[]>;\n /**\n * Simulates a raw, pre-encoded transaction against `POST /simulate` and\n * reports how the watched balances change and how much inner-call gas it\n * burns.\n *\n * The result is a discriminated union on `status`:\n * - `'ok'` — the simulation ran successfully; `deltas`/`gasUsed` are populated.\n * - `'revert'` — the simulation ran but the transaction reverted on-chain. A\n * revert is a *successful simulation*, not a transport error, so it is\n * returned (HTTP 200) rather than thrown — mirroring how {@link compile}\n * returns `status: 'partial'` on a simulated revert.\n * - `'error'` — the request was well-formed but the simulation could not be\n * set up or run (HTTP 422); `message` is intentionally generic.\n *\n * `bigint` amounts in the request (`value`, requirement `balance`/`allowance`)\n * are serialised to decimal strings automatically.\n *\n * @param request - The raw transaction plus funding `requirements` and the\n * `trackedBalances` to watch.\n * @returns A {@link SimulateResult} (`ok` / `revert` / `error`).\n * @throws {@link ComposeError} on network failures, HTTP 400 (malformed\n * input), 401/403 (auth), 404, 429, and 5xx.\n */\n readonly simulate: (request: SimulateRequest) => Promise<SimulateResult>;\n}\n\nconst bigintReplacer = (_key: string, value: unknown): unknown =>\n typeof value === 'bigint' ? value.toString() : value;\n\nconst isNonNullObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null;\n\n// Non-negative decimal integer string, as the server sends token amounts.\nconst INTEGER_STRING = /^\\d+$/;\n\nconst decodeAmount = (\n value: unknown,\n key: string,\n field: 'amountOut' | 'amountOutMin',\n url: string,\n): bigint => {\n if (typeof value !== 'string' || !INTEGER_STRING.test(value)) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: producedResources.${key}.simulated.${field} is ${\n value === undefined ? 'absent' : 'not an integer string'\n }`,\n { url },\n );\n }\n return BigInt(value);\n};\n\n// `simulated.amountOut` / `.amountOutMin` are declared `bigint` on\n// `SimulatedAmounts` (the domain truth: `compose-core` produces real bigints)\n// but arrive as decimal strings on the wire (the backend stringifies them).\n// Decode at this seam so a consumer reading the declared type gets the value\n// the type promises. A resource without `simulated` is left untouched; a\n// `simulated` claiming a missing or non-numeric amount is a contract violation\n// and surfaces as a `ComposeError` rather than `NaN` / `BigInt(undefined)`.\nconst decodeSimulatedAmounts = (data: unknown, url: string): void => {\n if (!isNonNullObject(data)) return;\n const resources = data.producedResources;\n if (!isNonNullObject(resources)) return;\n for (const key of Object.keys(resources)) {\n const resource = resources[key];\n if (!isNonNullObject(resource)) continue;\n const simulated = resource.simulated;\n if (simulated === undefined) continue;\n if (!isNonNullObject(simulated)) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: producedResources.${key}.simulated is not an object`,\n { url },\n );\n }\n simulated.amountOut = decodeAmount(\n simulated.amountOut,\n key,\n 'amountOut',\n url,\n );\n if (simulated.amountOutMin !== undefined) {\n simulated.amountOutMin = decodeAmount(\n simulated.amountOutMin,\n key,\n 'amountOutMin',\n url,\n );\n }\n }\n};\n\nconst parseBody = async <T>(res: Response, url: string): Promise<T> => {\n const body = await res.json().catch((_) => null);\n if (!isNonNullObject(body) || !('data' in body)) {\n throw new ComposeError('UNKNOWN_ERROR', 'Unexpected response format', {\n url,\n });\n }\n return body.data as T;\n};\n\nconst parseCompileSuccessBody = async (\n res: Response,\n url: string,\n): Promise<ComposeCompileResult> => {\n const data = await parseBody<ComposeCompileSuccessData>(res, url);\n decodeSimulatedAmounts(data, url);\n return { ...data, status: 'success' as const };\n};\n\nconst parsePartialBody = async (\n res: Response,\n url: string,\n): Promise<ComposeCompileResult> => {\n const body = await res.json().catch((_) => null);\n const envelope = parseCompilePartialEnvelope(body);\n if (envelope === null) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n 'Unexpected partial response format',\n { url },\n );\n }\n // `data` is validated as an object by the schema; compose-spec owns its full\n // shape as a hand-authored type, so we narrow it here rather than re-declaring\n // that type as a schema. `error` is fully validated — no cast needed.\n const data = envelope.data as unknown as ComposeCompilePartialData;\n decodeSimulatedAmounts(data, url);\n return { ...data, status: 'partial' as const, error: envelope.error };\n};\n\n// `/simulate` is un-enveloped: the discriminated body (`{ status, ... }`) is at\n// the top level, NOT wrapped in `{ data }` like `/compose`. So this reads the\n// body directly and validates it against the simulate union rather than reusing\n// `parseBody`.\nconst parseSimulateBody = async (\n res: Response,\n url: string,\n): Promise<SimulateResult> => {\n const body = await res.json().catch((_) => null);\n const result = parseSimulateResult(body);\n if (result === null) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n 'Unexpected simulate response format',\n { url },\n );\n }\n return result;\n};\n// `POST /compose` and `POST /compose/route` differ only in path and request\n// shape: both return the enveloped success data on 200 and the partial\n// envelope on 206, so they share one transport.\ntype ComposeTransport = {\n readonly fetchFn: typeof fetch;\n readonly baseHeaders: Record<string, string>;\n};\n\nconst postCompile = async (\n { fetchFn, baseHeaders }: ComposeTransport,\n url: string,\n request: ComposeCompileRequest | ComposeRouteRequest,\n): Promise<ComposeCompileResult> => {\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(request, bigintReplacer),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (res.status === 206) {\n return await parsePartialBody(res, url);\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseCompileSuccessBody(res, url);\n};\n\n// Every enveloped GET on this API behaves identically: no body, no query\n// semantics of its own, `{ data }` on success, an error envelope otherwise.\nconst getJson = async <T>(\n { fetchFn, baseHeaders }: ComposeTransport,\n url: string,\n): Promise<T> => {\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'GET',\n headers: { ...baseHeaders },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<T>(res, url);\n};\n\n/**\n * Creates a low-level Compose API client.\n *\n * @param options - Client configuration including the API base URL.\n * @returns A {@link ComposeClient} instance.\n */\nexport const createComposeClient = (\n options: ComposeClientOptions,\n): ComposeClient => {\n if (!options.baseUrl || !/^https?:\\/\\//i.test(options.baseUrl)) {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `Invalid baseUrl: expected an HTTP(S) URL, got \"${options.baseUrl}\"`,\n );\n }\n const trimmedApiKey = options.apiKey?.trim() || undefined;\n if (!trimmedApiKey) {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n 'apiKey is required: pass a LI.FI API key to createComposeSdk().',\n );\n }\n const fetchFn = options.fetch ?? globalThis.fetch;\n const base = options.baseUrl.replace(/\\/$/, '');\n\n const baseHeaders: Record<string, string> = {\n Accept: 'application/json',\n 'x-lifi-composer-sdk': SDK_VERSION,\n 'x-lifi-api-key': trimmedApiKey,\n };\n const transport: ComposeTransport = { fetchFn, baseHeaders };\n\n const getManifest = async (): Promise<ComposeManifest> =>\n getJson<ComposeManifest>(transport, `${base}/compose/manifest`);\n\n const getChains = async (): Promise<readonly ChainSummary[]> =>\n getJson<readonly ChainSummary[]>(transport, `${base}/chains`);\n\n const compile = async (\n request: ComposeCompileRequest,\n ): Promise<ComposeCompileResult> =>\n postCompile(transport, `${base}/compose`, request);\n\n const route = async (\n request: ComposeRouteRequest,\n ): Promise<ComposeCompileResult> =>\n postCompile(transport, `${base}/compose/route`, request);\n\n const getZapPacks = async (\n options?: GetZapPacksOptions,\n ): Promise<readonly ZapPackOverview[]> => {\n const params = new URLSearchParams();\n if (options?.protocols !== undefined) {\n // Backend expects a single comma-separated value, not repeated keys.\n const raw = options.protocols;\n const list = typeof raw === 'string' ? raw : raw.join(',');\n params.set('protocols', list);\n }\n const qs = params.toString();\n return getJson<readonly ZapPackOverview[]>(\n transport,\n `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`,\n );\n };\n\n const simulate = async (\n request: SimulateRequest,\n ): Promise<SimulateResult> => {\n const url = `${base}/simulate`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(request, bigintReplacer),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n // Only 200 (carries `ok`/`revert`) and 422 (carries the `error` member of\n // the union) have a discriminated body. 422 is deliberately intercepted\n // here (not thrown as VALIDATION_ERROR) so callers get one exhaustive\n // `switch (result.status)`. Every other status is a transport error and is\n // thrown — including HTTP 400 (malformed input, no `status` body) and any\n // unexpected 2xx.\n if (res.status === 200 || res.status === 422) {\n return await parseSimulateBody(res, url);\n }\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n };\n\n return { getManifest, getChains, compile, route, getZapPacks, simulate };\n};\n"],"mappings":"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","names":["options"]}
|
package/dist/client.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComposeManifest, ComposeCompileRequest, ComposeCompileResult, ComposeRouteRequest, SimulateRequest, SimulateResult } from '@lifi/compose-spec';
|
|
2
|
-
import { GetZapPacksOptions, ZapPackOverview } from './discovery.cjs';
|
|
2
|
+
import { ChainSummary, GetZapPacksOptions, ZapPackOverview } from './discovery.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Configuration for creating a low-level Compose API client.
|
|
@@ -29,6 +29,28 @@ interface ComposeClient {
|
|
|
29
29
|
* @throws {@link ComposeError} on network, validation, or server errors.
|
|
30
30
|
*/
|
|
31
31
|
readonly getManifest: () => Promise<ComposeManifest>;
|
|
32
|
+
/**
|
|
33
|
+
* Fetches the chains the Compose API supports, ascending by `chainId`.
|
|
34
|
+
*
|
|
35
|
+
* A chain is listed once the server confirms the VM contract is deployed on
|
|
36
|
+
* it — the same condition {@link compile}, {@link route} and
|
|
37
|
+
* {@link simulate} validate against. A chain absent from this list is
|
|
38
|
+
* rejected outright by those endpoints.
|
|
39
|
+
*
|
|
40
|
+
* The converse does not hold: a listed chain can still have no routing edge
|
|
41
|
+
* for a given token pair, which {@link route} reports as `no_route_error`.
|
|
42
|
+
* Use {@link getZapPacks} to enumerate the pairs that do route.
|
|
43
|
+
*
|
|
44
|
+
* Each entry carries a display `name` alongside its `chainId`. The name is a
|
|
45
|
+
* deployment-maintained label — render it, but key on `chainId`.
|
|
46
|
+
*
|
|
47
|
+
* The list tracks the server's chain registry, which refreshes periodically.
|
|
48
|
+
* Results are not cached by the SDK; callers should cache as appropriate.
|
|
49
|
+
*
|
|
50
|
+
* @returns The supported chains, ascending by `chainId`.
|
|
51
|
+
* @throws {@link ComposeError} on network or server errors.
|
|
52
|
+
*/
|
|
53
|
+
readonly getChains: () => Promise<readonly ChainSummary[]>;
|
|
32
54
|
/**
|
|
33
55
|
* Submits a compile request and returns the result.
|
|
34
56
|
*
|
|
@@ -47,9 +69,9 @@ interface ComposeClient {
|
|
|
47
69
|
* `POST /compose/route`.
|
|
48
70
|
*
|
|
49
71
|
* A convenience path that skips flow authoring: the server builds a one-node
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
72
|
+
* zap flow with a `directDeposit` input from the `fromToken` / `toToken`
|
|
73
|
+
* pair and runs it through the same pipeline as {@link compile}. The
|
|
74
|
+
* response is therefore the same discriminated result — HTTP 206 under
|
|
53
75
|
* `simulationPolicy: 'allow-revert'` yields `status: 'partial'` rather than
|
|
54
76
|
* throwing, exactly as it does for {@link compile}.
|
|
55
77
|
*
|
|
@@ -60,6 +82,13 @@ interface ComposeClient {
|
|
|
60
82
|
* author richer flows later, so the single-step limit is the current state of
|
|
61
83
|
* the server, not the shape of this contract.
|
|
62
84
|
*
|
|
85
|
+
* Only synchronous edges are considered unless the request sets
|
|
86
|
+
* `allowAsyncRoutes: true`, so by default every produced resource comes back
|
|
87
|
+
* `availability: 'now'` and a pair reachable only after settlement is a
|
|
88
|
+
* `no_route_error`. Opting in adds the `lifi.zapAsync` fallback, which still
|
|
89
|
+
* loses to a synchronous edge when the pair has one; read
|
|
90
|
+
* `producedResources[*].availability` to see which representation you got.
|
|
91
|
+
*
|
|
63
92
|
* Use {@link compile} for what the server does not author for you: several
|
|
64
93
|
* chained operations, splits, explicit preconditions, or per-op guards.
|
|
65
94
|
*
|