@lifi/composer-sdk 0.5.1 → 0.6.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 +13 -3
- package/dist/authoring/FlowBuilderCore.cjs.map +1 -1
- package/dist/authoring/FlowBuilderCore.d.cts +11 -4
- package/dist/authoring/FlowBuilderCore.d.ts +11 -4
- package/dist/authoring/FlowBuilderCore.js.map +1 -1
- package/dist/authoring/handles.cjs.map +1 -1
- package/dist/authoring/handles.d.cts +2 -2
- package/dist/authoring/handles.d.ts +2 -2
- package/dist/authoring/handles.js.map +1 -1
- package/dist/authoring/raw.cjs +5 -3
- package/dist/authoring/raw.cjs.map +1 -1
- package/dist/authoring/raw.js +5 -3
- package/dist/authoring/raw.js.map +1 -1
- package/dist/client.cjs +41 -30
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +3 -3
- package/dist/client.d.ts +3 -3
- package/dist/client.js +41 -30
- package/dist/client.js.map +1 -1
- package/dist/generated/config.generated.cjs.map +1 -1
- package/dist/generated/config.generated.d.cts +46 -5
- package/dist/generated/config.generated.d.ts +46 -5
- package/dist/generated/operations.generated.cjs +4 -0
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +16 -2
- package/dist/generated/operations.generated.d.ts +16 -2
- package/dist/generated/operations.generated.js +4 -0
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/index.cjs +4 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/outputs.cjs +33 -0
- package/dist/outputs.cjs.map +1 -0
- package/dist/outputs.d.cts +10 -0
- package/dist/outputs.d.ts +10 -0
- package/dist/outputs.js +9 -0
- package/dist/outputs.js.map +1 -0
- package/dist/responseSchemas.cjs.map +1 -1
- package/dist/responseSchemas.d.cts +4 -4
- package/dist/responseSchemas.d.ts +4 -4
- package/dist/responseSchemas.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +16 -5
- package/dist/types.d.ts +16 -5
- package/package.json +4 -3
- package/src/authoring/FlowBuilderCore.ts +12 -3
- package/src/authoring/handles.ts +2 -1
- package/src/authoring/raw.ts +4 -3
- package/src/client.ts +77 -45
- package/src/examples/flySwap.ts +3 -3
- package/src/examples/lifiZapAsync.ts +8 -7
- package/src/examples/swapAndZap.ts +7 -0
- package/src/examples/swapWithAllowRevert.ts +13 -3
- package/src/generated/config.generated.ts +49 -4
- package/src/generated/operations.generated.ts +25 -0
- package/src/index.ts +13 -2
- package/src/outputs.ts +16 -0
- package/src/responseSchemas.ts +4 -4
- package/src/types.ts +16 -4
|
@@ -8,12 +8,12 @@ import { SimulationRevert, SimulateResult } from '@lifi/compose-spec';
|
|
|
8
8
|
declare const parseSimulateResult: (body: unknown) => SimulateResult | null;
|
|
9
9
|
/**
|
|
10
10
|
* The validated members of an HTTP 206 partial compile envelope. `data` is
|
|
11
|
-
* verified to be an object but
|
|
12
|
-
* (`ComposeCompilePartialData`) as a hand-authored type, so
|
|
13
|
-
* it. `error` is fully validated.
|
|
11
|
+
* verified to be an object at runtime but left `unknown` here — compose-spec
|
|
12
|
+
* owns its full shape (`ComposeCompilePartialData`) as a hand-authored type, so
|
|
13
|
+
* the caller narrows it in one step. `error` is fully validated.
|
|
14
14
|
*/
|
|
15
15
|
interface CompilePartialEnvelope {
|
|
16
|
-
readonly data:
|
|
16
|
+
readonly data: unknown;
|
|
17
17
|
readonly error: {
|
|
18
18
|
readonly kind: string;
|
|
19
19
|
readonly message: string;
|
|
@@ -8,12 +8,12 @@ import { SimulationRevert, SimulateResult } from '@lifi/compose-spec';
|
|
|
8
8
|
declare const parseSimulateResult: (body: unknown) => SimulateResult | null;
|
|
9
9
|
/**
|
|
10
10
|
* The validated members of an HTTP 206 partial compile envelope. `data` is
|
|
11
|
-
* verified to be an object but
|
|
12
|
-
* (`ComposeCompilePartialData`) as a hand-authored type, so
|
|
13
|
-
* it. `error` is fully validated.
|
|
11
|
+
* verified to be an object at runtime but left `unknown` here — compose-spec
|
|
12
|
+
* owns its full shape (`ComposeCompilePartialData`) as a hand-authored type, so
|
|
13
|
+
* the caller narrows it in one step. `error` is fully validated.
|
|
14
14
|
*/
|
|
15
15
|
interface CompilePartialEnvelope {
|
|
16
|
-
readonly data:
|
|
16
|
+
readonly data: unknown;
|
|
17
17
|
readonly error: {
|
|
18
18
|
readonly kind: string;
|
|
19
19
|
readonly message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/responseSchemas.ts"],"sourcesContent":["import type { SimulateResult, SimulationRevert } from '@lifi/compose-spec';\nimport z from 'zod';\n\n/**\n * Zod-backed parsers for Compose API response bodies.\n *\n * Server responses are untrusted input crossing into the SDK, so the shapes the\n * client reads back are validated here rather than asserted with `as` casts. The\n * schemas are module-private (the package builds with `--isolatedDeclarations`,\n * which forbids exporting un-annotated complex values); the public surface is\n * the typed `parse*` helpers, whose explicit return types double as the contract.\n *\n * The simulate union is the one response the SDK fully owns and parses itself\n * (it is un-enveloped, unlike `/compose`), so it gets a complete schema kept in\n * lockstep with the canonical `SimulateResult` in `@lifi/compose-spec` by the\n * compile-time assertion below — the same anchoring `@lifi/api-schemas` uses for\n * its server-side simulate schema, so the two cannot silently drift.\n */\n\nconst balanceEntrySchema = z.object({\n token: z.string(),\n owner: z.string(),\n amount: z.string(),\n});\n\nconst simulateOkSchema = z.object({\n status: z.literal('ok'),\n block: z.number(),\n timestamp: z.number(),\n balancesBefore: z.array(balanceEntrySchema),\n balancesAfter: z.array(balanceEntrySchema),\n deltas: z.array(balanceEntrySchema),\n gasUsed: z.string(),\n});\n\nconst simulateRevertDecodeResultSchema = z.object({\n errorCandidates: z\n .array(\n z.object({\n decodedErrorSignature: z.string(),\n decodedParams: z.array(z.string()),\n }),\n )\n .optional(),\n error: z.string().optional(),\n});\n\nconst simulateRevertSchema = z.object({\n status: z.literal('revert'),\n block: z.number(),\n timestamp: z.number(),\n revertReason: z.string().optional(),\n code: z.number().optional(),\n rawErrorBytes: z.string().optional(),\n decodeResult: simulateRevertDecodeResultSchema.optional(),\n});\n\nconst simulateSetupErrorSchema = z.object({\n status: z.literal('error'),\n message: z.string(),\n});\n\nconst simulateResultSchema = z.discriminatedUnion('status', [\n simulateOkSchema,\n simulateRevertSchema,\n simulateSetupErrorSchema,\n]);\n\n// The type the schema actually parses out. Kept private (referencing it from an\n// exported declaration would break `--isolatedDeclarations`) and pinned to the\n// canonical `SimulateResult` inside `parseSimulateResult` below.\ntype SimulateResultWire = z.infer<typeof simulateResultSchema>;\n\n/**\n * Parses a `POST /simulate` body into a {@link SimulateResult}. Returns `null`\n * when the body does not match the `ok`/`revert`/`error` union, letting the\n * caller raise a transport-level error.\n */\nexport const parseSimulateResult = (body: unknown): SimulateResult | null => {\n const parsed = simulateResultSchema.safeParse(body);\n if (!parsed.success) return null;\n // Pin the schema to the canonical contract in both directions, so it cannot\n // silently drift (mirroring the conformance assertion `@lifi/api-schemas` runs\n // on its server-side simulate schema). Assigning the inferred wire value to a\n // `SimulateResult` checks wire→spec; `satisfies SimulateResultWire` checks\n // spec→wire. A missing or mistyped field on either side fails to compile.\n const result: SimulateResult = parsed.data;\n return result satisfies SimulateResultWire;\n};\n\n/**\n * The validated members of an HTTP 206 partial compile envelope. `data` is\n * verified to be an object but
|
|
1
|
+
{"version":3,"sources":["../src/responseSchemas.ts"],"sourcesContent":["import type { SimulateResult, SimulationRevert } from '@lifi/compose-spec';\nimport z from 'zod';\n\n/**\n * Zod-backed parsers for Compose API response bodies.\n *\n * Server responses are untrusted input crossing into the SDK, so the shapes the\n * client reads back are validated here rather than asserted with `as` casts. The\n * schemas are module-private (the package builds with `--isolatedDeclarations`,\n * which forbids exporting un-annotated complex values); the public surface is\n * the typed `parse*` helpers, whose explicit return types double as the contract.\n *\n * The simulate union is the one response the SDK fully owns and parses itself\n * (it is un-enveloped, unlike `/compose`), so it gets a complete schema kept in\n * lockstep with the canonical `SimulateResult` in `@lifi/compose-spec` by the\n * compile-time assertion below — the same anchoring `@lifi/api-schemas` uses for\n * its server-side simulate schema, so the two cannot silently drift.\n */\n\nconst balanceEntrySchema = z.object({\n token: z.string(),\n owner: z.string(),\n amount: z.string(),\n});\n\nconst simulateOkSchema = z.object({\n status: z.literal('ok'),\n block: z.number(),\n timestamp: z.number(),\n balancesBefore: z.array(balanceEntrySchema),\n balancesAfter: z.array(balanceEntrySchema),\n deltas: z.array(balanceEntrySchema),\n gasUsed: z.string(),\n});\n\nconst simulateRevertDecodeResultSchema = z.object({\n errorCandidates: z\n .array(\n z.object({\n decodedErrorSignature: z.string(),\n decodedParams: z.array(z.string()),\n }),\n )\n .optional(),\n error: z.string().optional(),\n});\n\nconst simulateRevertSchema = z.object({\n status: z.literal('revert'),\n block: z.number(),\n timestamp: z.number(),\n revertReason: z.string().optional(),\n code: z.number().optional(),\n rawErrorBytes: z.string().optional(),\n decodeResult: simulateRevertDecodeResultSchema.optional(),\n});\n\nconst simulateSetupErrorSchema = z.object({\n status: z.literal('error'),\n message: z.string(),\n});\n\nconst simulateResultSchema = z.discriminatedUnion('status', [\n simulateOkSchema,\n simulateRevertSchema,\n simulateSetupErrorSchema,\n]);\n\n// The type the schema actually parses out. Kept private (referencing it from an\n// exported declaration would break `--isolatedDeclarations`) and pinned to the\n// canonical `SimulateResult` inside `parseSimulateResult` below.\ntype SimulateResultWire = z.infer<typeof simulateResultSchema>;\n\n/**\n * Parses a `POST /simulate` body into a {@link SimulateResult}. Returns `null`\n * when the body does not match the `ok`/`revert`/`error` union, letting the\n * caller raise a transport-level error.\n */\nexport const parseSimulateResult = (body: unknown): SimulateResult | null => {\n const parsed = simulateResultSchema.safeParse(body);\n if (!parsed.success) return null;\n // Pin the schema to the canonical contract in both directions, so it cannot\n // silently drift (mirroring the conformance assertion `@lifi/api-schemas` runs\n // on its server-side simulate schema). Assigning the inferred wire value to a\n // `SimulateResult` checks wire→spec; `satisfies SimulateResultWire` checks\n // spec→wire. A missing or mistyped field on either side fails to compile.\n const result: SimulateResult = parsed.data;\n return result satisfies SimulateResultWire;\n};\n\n/**\n * The validated members of an HTTP 206 partial compile envelope. `data` is\n * verified to be an object at runtime but left `unknown` here — compose-spec\n * owns its full shape (`ComposeCompilePartialData`) as a hand-authored type, so\n * the caller narrows it in one step. `error` is fully validated.\n */\nexport interface CompilePartialEnvelope {\n readonly data: unknown;\n readonly error: { readonly kind: string; readonly message: string };\n}\n\nconst compilePartialEnvelopeSchema = z.object({\n data: z.record(z.string(), z.unknown()),\n error: z.object({ kind: z.string(), message: z.string() }),\n});\n\n/** Parses an HTTP 206 partial compile envelope; `null` when it does not match. */\nexport const parseCompilePartialEnvelope = (\n body: unknown,\n): CompilePartialEnvelope | null => {\n const parsed = compilePartialEnvelopeSchema.safeParse(body);\n return parsed.success ? parsed.data : null;\n};\n\n/**\n * A single prepared-op failure inside a `preparation_error` envelope. `kind` is\n * kept as `string` here — the narrowing to `GenericComposeErrorKind` happens in\n * `errorFromHttpResponse`, where the value is lifted onto `ComposeError`.\n */\nexport interface ServerFailedPreparedOp {\n readonly callId: string;\n readonly op: string;\n readonly kind: string;\n readonly message: string;\n readonly path?: string;\n}\n\n/** Error envelope returned on non-2xx responses. */\nexport interface ServerErrorBody {\n readonly error?: {\n readonly kind?: string;\n readonly message?: string;\n readonly path?: string;\n readonly sdkVersion?: string;\n readonly serverVersion?: string;\n readonly minimumSdkVersion?: string;\n readonly details?: SimulationRevert;\n readonly failedOps?: readonly ServerFailedPreparedOp[];\n readonly succeededOps?: readonly string[];\n };\n}\n\nconst failedPreparedOpSchema = z.object({\n callId: z.string(),\n op: z.string(),\n kind: z.string(),\n message: z.string(),\n path: z.string().optional(),\n});\n\n// `details` is passed through (`z.custom`) rather than parsed: the SDK does not\n// own the `SimulationRevert` schema and forwards the field verbatim onto\n// `ComposeError`.\n//\n// `failedOps`/`succeededOps` are optional here because every error kind shares\n// this envelope; they are only populated for `preparation_error` (422). Zod\n// strips undeclared keys, so omitting them would silently drop them.\nconst serverErrorBodySchema = z.object({\n error: z\n .object({\n kind: z.string().optional(),\n message: z.string().optional(),\n path: z.string().optional(),\n sdkVersion: z.string().optional(),\n serverVersion: z.string().optional(),\n minimumSdkVersion: z.string().optional(),\n details: z.custom<SimulationRevert>().optional(),\n failedOps: z.array(failedPreparedOpSchema).readonly().optional(),\n succeededOps: z.array(z.string()).readonly().optional(),\n })\n .optional(),\n});\n\n/** Validates an already-parsed error envelope; `null` when it does not match. */\nexport const parseServerErrorBody = (json: unknown): ServerErrorBody | null => {\n const parsed = serverErrorBodySchema.safeParse(json);\n return parsed.success ? parsed.data : null;\n};\n"],"mappings":"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","names":[]}
|
package/dist/types.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { LiteralBinding, Ref } from '@lifi/compose-spec';\nimport {\n InputSpec,\n MaterialiserInput,\n Resource,\n SolType,\n} from '@lifi/compose-spec';\n\n/**\n * A string that represents a non-negative integer (e.g. \"1000000000\").\n * Uses a template literal type so that only numeric-looking strings\n * are assignable at compile time.\n */\nexport type IntegerString = `${bigint}`;\n\n/**\n * Accepted input for integer-string config fields.\n * Callers may pass either a numeric string (\"1000000000\") or a native bigint\n * (1000000000n). Bigint values are stringified automatically during JSON\n * serialization via the SDK's `bigintReplacer`.\n */\nexport type IntegerStringInput = IntegerString | bigint;\n\n/**\n * A `0x`-prefixed hex string representing an Ethereum address.\n * Template literal type catches obviously wrong values at compile time.\n */\nexport type Address = `0x${string}`;\n\n/** The universe of output port kinds: Solidity scalar types plus `'resource'`. */\nexport type OutputKind = SolType | 'resource';\n\n/**\n * Maps an ABI parameter type string (from abitype's `ParseAbiItem`) to the\n * closest `OutputKind`. When the ABI type is one of the supported `SolType`\n * scalars, the mapping is exact; for all other Solidity types (e.g. tuples,\n * fixed-point decimals) it falls back to `SolType` — accepting any scalar\n * handle but not `'resource'` handles.\n *\n * The fallback excludes `'resource'` because every Solidity type is a scalar;\n * resource handles carry token-amount semantics that should only flow into\n * slots explicitly declared as resources.\n *\n * Extending `SolType` with new members automatically widens the exact-match\n * branch — no changes needed here.\n */\nexport type AbiTypeToOutputKind<T extends string> = T extends SolType\n ? T\n : SolType;\n\n/**\n * A typed `$ref` pointer carrying a phantom type parameter.\n *
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { LiteralBinding, Ref } from '@lifi/compose-spec';\nimport {\n InputSpec,\n MaterialiserInput,\n Resource,\n SolType,\n} from '@lifi/compose-spec';\n\n/**\n * A string that represents a non-negative integer (e.g. \"1000000000\").\n * Uses a template literal type so that only numeric-looking strings\n * are assignable at compile time.\n */\nexport type IntegerString = `${bigint}`;\n\n/**\n * Accepted input for integer-string config fields.\n * Callers may pass either a numeric string (\"1000000000\") or a native bigint\n * (1000000000n). Bigint values are stringified automatically during JSON\n * serialization via the SDK's `bigintReplacer`.\n */\nexport type IntegerStringInput = IntegerString | bigint;\n\n/**\n * A `0x`-prefixed hex string representing an Ethereum address.\n * Template literal type catches obviously wrong values at compile time.\n */\nexport type Address = `0x${string}`;\n\n/** The universe of output port kinds: Solidity scalar types plus `'resource'`. */\nexport type OutputKind = SolType | 'resource';\n\n/**\n * Maps an ABI parameter type string (from abitype's `ParseAbiItem`) to the\n * closest `OutputKind`. When the ABI type is one of the supported `SolType`\n * scalars, the mapping is exact; for all other Solidity types (e.g. tuples,\n * fixed-point decimals) it falls back to `SolType` — accepting any scalar\n * handle but not `'resource'` handles.\n *\n * The fallback excludes `'resource'` because every Solidity type is a scalar;\n * resource handles carry token-amount semantics that should only flow into\n * slots explicitly declared as resources.\n *\n * Extending `SolType` with new members automatically widens the exact-match\n * branch — no changes needed here.\n */\nexport type AbiTypeToOutputKind<T extends string> = T extends SolType\n ? T\n : SolType;\n\n/**\n * Phantom brand key for {@link TypedRef}.\n *\n * Declared, never assigned: it exists only so the type checker can tell a\n * typed ref from an arbitrary `{ $ref }` object. Keying it on a `unique\n * symbol` means no caller can produce one by writing an object literal.\n */\nexport declare const typedRefKind: unique symbol;\n\n/**\n * A typed `$ref` pointer carrying a phantom type parameter.\n *\n * On the wire, and at run time, this is exactly `{ $ref: string }` — the\n * brand is erased. The brand is *required* in the type so a plain `Ref` does\n * not satisfy `TypedRef` structurally, which is the contract `RefBindable`\n * advertises: an untyped `{ $ref }` never enters a typed bind slot. Build one\n * with `raw.ref<T>()`.\n */\nexport interface TypedRef<T extends OutputKind = OutputKind> extends Ref {\n readonly [typedRefKind]: T;\n}\n\n/**\n * A literal bind value carrying a phantom type parameter.\n *\n * The wire format accepts a constant in a bind slot as\n * `{ kind: <solType>, value: <string> }`, alongside `$ref` pointers. Use it\n * for a value that must be baked into the flow document rather than supplied\n * at run time — the destination of a flow the backend stores and re-runs\n * later (a `continuation.settle` node's `continuationFlow`) has no run to\n * take an input from.\n *\n * @typeParam T - The Solidity type of the literal (e.g. `'address'`).\n */\nexport interface TypedLiteral<\n T extends SolType = SolType,\n> extends LiteralBinding {\n readonly kind: T;\n readonly __outputKind?: T;\n}\n\n/**\n * An input declaration in a flow schema.\n * `Resource` values become resource inputs (token amounts);\n * `SolType` strings become handle inputs (scalars like addresses).\n */\nexport type InputDecl = Resource | SolType;\n\n/**\n * A record mapping input names to their declarations.\n * Used as the type parameter for generic flow builders and requests.\n */\nexport type InputSchema = Record<string, InputDecl>;\n\n/**\n * Maps an input declaration to the allowed runtime value type.\n * Resource inputs accept materialisers or literal amounts.\n * Handle inputs accept the scalar type matching their SolType.\n */\nexport type InputSpecOf<D> = D extends Resource\n ? MaterialiserInput | IntegerStringInput\n : D extends 'address'\n ? Address\n : D extends 'bool'\n ? boolean\n : D extends `${'u' | ''}int${string}`\n ? IntegerStringInput\n : D extends `bytes${string}`\n ? `0x${string}`\n : D extends 'string'\n ? string\n : InputSpec;\n\n/**\n * A guard applied to an operation, with the `port` field constrained\n * to a specific set of output port names.\n */\nexport interface TypedGuard<Port extends string = string> {\n readonly kind: string;\n readonly port: Port;\n readonly [key: string]: unknown;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/types.d.cts
CHANGED
|
@@ -35,14 +35,25 @@ type OutputKind = SolType | 'resource';
|
|
|
35
35
|
* branch — no changes needed here.
|
|
36
36
|
*/
|
|
37
37
|
type AbiTypeToOutputKind<T extends string> = T extends SolType ? T : SolType;
|
|
38
|
+
/**
|
|
39
|
+
* Phantom brand key for {@link TypedRef}.
|
|
40
|
+
*
|
|
41
|
+
* Declared, never assigned: it exists only so the type checker can tell a
|
|
42
|
+
* typed ref from an arbitrary `{ $ref }` object. Keying it on a `unique
|
|
43
|
+
* symbol` means no caller can produce one by writing an object literal.
|
|
44
|
+
*/
|
|
45
|
+
declare const typedRefKind: unique symbol;
|
|
38
46
|
/**
|
|
39
47
|
* A typed `$ref` pointer carrying a phantom type parameter.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
48
|
+
*
|
|
49
|
+
* On the wire, and at run time, this is exactly `{ $ref: string }` — the
|
|
50
|
+
* brand is erased. The brand is *required* in the type so a plain `Ref` does
|
|
51
|
+
* not satisfy `TypedRef` structurally, which is the contract `RefBindable`
|
|
52
|
+
* advertises: an untyped `{ $ref }` never enters a typed bind slot. Build one
|
|
53
|
+
* with `raw.ref<T>()`.
|
|
43
54
|
*/
|
|
44
55
|
interface TypedRef<T extends OutputKind = OutputKind> extends Ref {
|
|
45
|
-
readonly
|
|
56
|
+
readonly [typedRefKind]: T;
|
|
46
57
|
}
|
|
47
58
|
/**
|
|
48
59
|
* A literal bind value carrying a phantom type parameter.
|
|
@@ -87,4 +98,4 @@ interface TypedGuard<Port extends string = string> {
|
|
|
87
98
|
readonly [key: string]: unknown;
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
export type
|
|
101
|
+
export { type AbiTypeToOutputKind, type Address, type InputDecl, type InputSchema, type InputSpecOf, type IntegerString, type IntegerStringInput, type OutputKind, type TypedGuard, type TypedLiteral, type TypedRef, typedRefKind };
|
package/dist/types.d.ts
CHANGED
|
@@ -35,14 +35,25 @@ type OutputKind = SolType | 'resource';
|
|
|
35
35
|
* branch — no changes needed here.
|
|
36
36
|
*/
|
|
37
37
|
type AbiTypeToOutputKind<T extends string> = T extends SolType ? T : SolType;
|
|
38
|
+
/**
|
|
39
|
+
* Phantom brand key for {@link TypedRef}.
|
|
40
|
+
*
|
|
41
|
+
* Declared, never assigned: it exists only so the type checker can tell a
|
|
42
|
+
* typed ref from an arbitrary `{ $ref }` object. Keying it on a `unique
|
|
43
|
+
* symbol` means no caller can produce one by writing an object literal.
|
|
44
|
+
*/
|
|
45
|
+
declare const typedRefKind: unique symbol;
|
|
38
46
|
/**
|
|
39
47
|
* A typed `$ref` pointer carrying a phantom type parameter.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
48
|
+
*
|
|
49
|
+
* On the wire, and at run time, this is exactly `{ $ref: string }` — the
|
|
50
|
+
* brand is erased. The brand is *required* in the type so a plain `Ref` does
|
|
51
|
+
* not satisfy `TypedRef` structurally, which is the contract `RefBindable`
|
|
52
|
+
* advertises: an untyped `{ $ref }` never enters a typed bind slot. Build one
|
|
53
|
+
* with `raw.ref<T>()`.
|
|
43
54
|
*/
|
|
44
55
|
interface TypedRef<T extends OutputKind = OutputKind> extends Ref {
|
|
45
|
-
readonly
|
|
56
|
+
readonly [typedRefKind]: T;
|
|
46
57
|
}
|
|
47
58
|
/**
|
|
48
59
|
* A literal bind value carrying a phantom type parameter.
|
|
@@ -87,4 +98,4 @@ interface TypedGuard<Port extends string = string> {
|
|
|
87
98
|
readonly [key: string]: unknown;
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
export type
|
|
101
|
+
export { type AbiTypeToOutputKind, type Address, type InputDecl, type InputSchema, type InputSpecOf, type IntegerString, type IntegerStringInput, type OutputKind, type TypedGuard, type TypedLiteral, type TypedRef, typedRefKind };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifi/composer-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Public Composer SDK for building and submitting flows",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -34,8 +34,9 @@
|
|
|
34
34
|
"pack:local": "yarn build && mkdir -p out && yarn pack --out 'out/%s-%v.tgz'",
|
|
35
35
|
"generate": "tsx scripts/generate-sdk/index.ts",
|
|
36
36
|
"generate:staged": "tsx scripts/generate-sdk/index.ts --staged",
|
|
37
|
-
"test": "vitest run --config vitest.config.ts",
|
|
37
|
+
"test": "yarn check:types:test && vitest run --config vitest.config.ts",
|
|
38
38
|
"check:types": "tsc --noEmit",
|
|
39
|
+
"check:types:test": "tsc -p tsconfig.test.json --noEmit",
|
|
39
40
|
"check:sdk-drift": "vitest run --config vitest.drift.config.ts",
|
|
40
41
|
"fix": "yarn fix:lint",
|
|
41
42
|
"fix:lint": "eslint src --fix",
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
"example:staged": "tsx src/examples/run.ts --staged"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
|
-
"@lifi/compose-spec": "0.
|
|
50
|
+
"@lifi/compose-spec": "0.6.0"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
53
|
"abitype": "^1.2.3",
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
Call,
|
|
5
5
|
Flow,
|
|
6
6
|
FlowInput,
|
|
7
|
+
Ref,
|
|
7
8
|
Resource,
|
|
8
9
|
SolType,
|
|
9
10
|
} from '@lifi/compose-spec';
|
|
@@ -39,13 +40,21 @@ export interface CallArgs {
|
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Any value that can be bound to an operation parameter: an input handle, an
|
|
42
|
-
* output handle from a previous operation, a typed `$ref` pointer,
|
|
43
|
-
* literal.
|
|
43
|
+
* output handle from a previous operation, a typed `$ref` pointer, a typed
|
|
44
|
+
* literal, or a bare `{ $ref }` pointer.
|
|
45
|
+
*
|
|
46
|
+
* This is the bind type of {@link FlowBuilderInternal.call}, the entry point
|
|
47
|
+
* every generated operation lowers into — not a public authoring surface. A
|
|
48
|
+
* bare `Ref` belongs here because a generated op resolves its own typed binds
|
|
49
|
+
* to wire refs (`toBindRef` in `./signatureArgs.js`) before calling it. Typed
|
|
50
|
+
* operation slots use `Bindable<T>`, which rejects a bare `Ref`; see
|
|
51
|
+
* `RefBindable` in `./handles.js`.
|
|
44
52
|
*
|
|
45
53
|
* Use `raw.ref<T>()` to create a typed ref from a raw path string, and
|
|
46
54
|
* `raw.literal<T>()` to bake in a constant.
|
|
47
55
|
*/
|
|
48
|
-
export type AnyBindable =
|
|
56
|
+
export type AnyBindable =
|
|
57
|
+
InputHandle | OutputHandle | TypedRef | TypedLiteral | Ref;
|
|
49
58
|
|
|
50
59
|
/**
|
|
51
60
|
* Provides references to runtime context values available inside a flow.
|
package/src/authoring/handles.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BindValue,
|
|
3
|
+
Ref,
|
|
3
4
|
Resource,
|
|
4
5
|
SolType,
|
|
5
6
|
StaticSolType,
|
|
@@ -122,5 +123,5 @@ type LiteralBindable<K extends OutputKind> = K extends SolType
|
|
|
122
123
|
* `$ref` pointers, refs and literals pass through as-is.
|
|
123
124
|
*/
|
|
124
125
|
export const toBindValue = (
|
|
125
|
-
bindable: InputHandle | OutputHandle | TypedRef | TypedLiteral,
|
|
126
|
+
bindable: InputHandle | OutputHandle | TypedRef | TypedLiteral | Ref,
|
|
126
127
|
): BindValue => ('_tag' in bindable ? handleToRef(bindable) : bindable);
|
package/src/authoring/raw.ts
CHANGED
|
@@ -35,9 +35,10 @@ import type { OutputKind, TypedLiteral, TypedRef } from '../types.js';
|
|
|
35
35
|
* });
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
38
|
-
export const ref = <T extends OutputKind>(path: string): TypedRef<T> =>
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
export const ref = <T extends OutputKind>(path: string): TypedRef<T> =>
|
|
39
|
+
// The brand is phantom, so the runtime value is the bare wire ref. This is
|
|
40
|
+
// the only place a `TypedRef` is minted; the cast is what the brand buys.
|
|
41
|
+
({ $ref: path }) as TypedRef<T>;
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Creates a typed literal for use in bind slots: a constant baked into the
|
package/src/client.ts
CHANGED
|
@@ -123,11 +123,11 @@ export interface ComposeClient {
|
|
|
123
123
|
* the server, not the shape of this contract.
|
|
124
124
|
*
|
|
125
125
|
* Only synchronous edges are considered unless the request sets
|
|
126
|
-
* `allowAsyncRoutes: true`, so by default every
|
|
127
|
-
* `
|
|
126
|
+
* `allowAsyncRoutes: true`, so by default every terminal output comes back
|
|
127
|
+
* `delivery.when: 'now'` and a pair reachable only after settlement is a
|
|
128
128
|
* `no_route_error`. Opting in adds the `lifi.zapAsync` fallback, which still
|
|
129
129
|
* loses to a synchronous edge when the pair has one; read
|
|
130
|
-
* `
|
|
130
|
+
* `outputs[*].delivery.when` to see which representation you got.
|
|
131
131
|
*
|
|
132
132
|
* Use {@link compile} for what the server does not author for you: several
|
|
133
133
|
* chained operations, splits, explicit preconditions, or per-op guards.
|
|
@@ -200,16 +200,11 @@ const isNonNullObject = (v: unknown): v is Record<string, unknown> =>
|
|
|
200
200
|
// Non-negative decimal integer string, as the server sends token amounts.
|
|
201
201
|
const INTEGER_STRING = /^\d+$/;
|
|
202
202
|
|
|
203
|
-
const decodeAmount = (
|
|
204
|
-
value: unknown,
|
|
205
|
-
key: string,
|
|
206
|
-
field: 'amountOut' | 'amountOutMin',
|
|
207
|
-
url: string,
|
|
208
|
-
): bigint => {
|
|
203
|
+
const decodeAmount = (value: unknown, path: string, url: string): bigint => {
|
|
209
204
|
if (typeof value !== 'string' || !INTEGER_STRING.test(value)) {
|
|
210
205
|
throw new ComposeError(
|
|
211
206
|
'UNKNOWN_ERROR',
|
|
212
|
-
`Malformed compose response:
|
|
207
|
+
`Malformed compose response: ${path} is ${
|
|
213
208
|
value === undefined ? 'absent' : 'not an integer string'
|
|
214
209
|
}`,
|
|
215
210
|
{ url },
|
|
@@ -218,46 +213,83 @@ const decodeAmount = (
|
|
|
218
213
|
return BigInt(value);
|
|
219
214
|
};
|
|
220
215
|
|
|
221
|
-
//
|
|
222
|
-
// `
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
216
|
+
// The two records this seam reads out of a compile-family response body. Each
|
|
217
|
+
// is `unknown` because the seam is what establishes its shape: it validates
|
|
218
|
+
// every field before writing a decoded `bigint` back over the wire string.
|
|
219
|
+
interface WireAmountBody {
|
|
220
|
+
outputs?: unknown;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// One `Amount` as it arrives: each leaf is `unknown` until this seam has
|
|
224
|
+
// checked it, and is overwritten in place with the decoded `bigint`.
|
|
225
|
+
interface WireAmount {
|
|
226
|
+
estimate?: unknown;
|
|
227
|
+
minimum?: unknown;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const malformed = (path: string, what: string, url: string): ComposeError =>
|
|
231
|
+
new ComposeError(
|
|
232
|
+
'UNKNOWN_ERROR',
|
|
233
|
+
`Malformed compose response: ${path} ${what}`,
|
|
234
|
+
{ url },
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Decode one `Estimate` / `Minimum` leaf in place: `{ basis, value }` with
|
|
238
|
+
// `value` a decimal string on the wire and a `bigint` on `Amount`.
|
|
239
|
+
const decodeAmountLeaf = (
|
|
240
|
+
amount: WireAmount,
|
|
241
|
+
field: 'estimate' | 'minimum',
|
|
242
|
+
path: string,
|
|
243
|
+
url: string,
|
|
244
|
+
): void => {
|
|
245
|
+
const leaf = amount[field];
|
|
246
|
+
if (leaf === undefined) return;
|
|
247
|
+
if (!isNonNullObject(leaf)) {
|
|
248
|
+
throw malformed(`${path}.${field}`, 'is not an object', url);
|
|
249
|
+
}
|
|
250
|
+
leaf.value = decodeAmount(leaf.value, `${path}.${field}.value`, url);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// `outputs` is REQUIRED on every compile-family body and is the record a
|
|
254
|
+
// caller reads amounts from, so its structure is checked here — the body is
|
|
255
|
+
// cast, not schema-parsed — before each `value` leaf becomes a `bigint`.
|
|
256
|
+
const decodeOutputs = (data: WireAmountBody, url: string): void => {
|
|
257
|
+
const outputs = data.outputs;
|
|
258
|
+
if (!isNonNullObject(outputs)) {
|
|
259
|
+
throw malformed(
|
|
260
|
+
'outputs',
|
|
261
|
+
outputs === undefined ? 'is absent' : 'is not an object',
|
|
248
262
|
url,
|
|
249
263
|
);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
264
|
+
}
|
|
265
|
+
for (const key of Object.keys(outputs)) {
|
|
266
|
+
const entry = outputs[key];
|
|
267
|
+
if (!isNonNullObject(entry)) {
|
|
268
|
+
throw malformed(`outputs.${key}`, 'is not an object', url);
|
|
269
|
+
}
|
|
270
|
+
const amount = entry.amount;
|
|
271
|
+
if (!isNonNullObject(amount)) {
|
|
272
|
+
throw malformed(
|
|
273
|
+
`outputs.${key}.amount`,
|
|
274
|
+
amount === undefined ? 'is absent' : 'is not an object',
|
|
255
275
|
url,
|
|
256
276
|
);
|
|
257
277
|
}
|
|
278
|
+
decodeAmountLeaf(amount, 'estimate', `outputs.${key}.amount`, url);
|
|
279
|
+
decodeAmountLeaf(amount, 'minimum', `outputs.${key}.amount`, url);
|
|
258
280
|
}
|
|
259
281
|
};
|
|
260
282
|
|
|
283
|
+
// THE decode seam for a compile-family response body: every amount the wire
|
|
284
|
+
// renders as a decimal string becomes the `bigint` its declared type promises.
|
|
285
|
+
// The amount record is handled here so the body is narrowed once — a
|
|
286
|
+
// non-object body is not this function's error to raise, since the enveloping
|
|
287
|
+
// parse already rejected it.
|
|
288
|
+
const decodeWireAmounts = (data: unknown, url: string): void => {
|
|
289
|
+
if (!isNonNullObject(data)) return;
|
|
290
|
+
decodeOutputs(data, url);
|
|
291
|
+
};
|
|
292
|
+
|
|
261
293
|
const parseBody = async <T>(res: Response, url: string): Promise<T> => {
|
|
262
294
|
const body = await res.json().catch((_) => null);
|
|
263
295
|
if (!isNonNullObject(body) || !('data' in body)) {
|
|
@@ -273,7 +305,7 @@ const parseCompileSuccessBody = async (
|
|
|
273
305
|
url: string,
|
|
274
306
|
): Promise<ComposeCompileResult> => {
|
|
275
307
|
const data = await parseBody<ComposeCompileSuccessData>(res, url);
|
|
276
|
-
|
|
308
|
+
decodeWireAmounts(data, url);
|
|
277
309
|
return { ...data, status: 'success' as const };
|
|
278
310
|
};
|
|
279
311
|
|
|
@@ -293,8 +325,8 @@ const parsePartialBody = async (
|
|
|
293
325
|
// `data` is validated as an object by the schema; compose-spec owns its full
|
|
294
326
|
// shape as a hand-authored type, so we narrow it here rather than re-declaring
|
|
295
327
|
// that type as a schema. `error` is fully validated — no cast needed.
|
|
296
|
-
const data = envelope.data as
|
|
297
|
-
|
|
328
|
+
const data = envelope.data as ComposeCompilePartialData;
|
|
329
|
+
decodeWireAmounts(data, url);
|
|
298
330
|
return { ...data, status: 'partial' as const, error: envelope.error };
|
|
299
331
|
};
|
|
300
332
|
|
package/src/examples/flySwap.ts
CHANGED
|
@@ -54,14 +54,14 @@ const WETH = '0x4200000000000000000000000000000000000006';
|
|
|
54
54
|
* basis points, not percent.
|
|
55
55
|
*
|
|
56
56
|
* Widening it is safe but not free. The bottom of the band is what the compose
|
|
57
|
-
* response publishes as `
|
|
57
|
+
* response publishes as the output's `amount.minimum`, so a wider band
|
|
58
58
|
* minimum to whoever consumes your quote.
|
|
59
59
|
*
|
|
60
60
|
* ## Ignore the 50% slippage in the Fly calldata
|
|
61
61
|
*
|
|
62
62
|
* If you inspect the compiled transaction you will see a 50% slippage figure
|
|
63
63
|
* where you configured 1%. That is deliberate and it is not your setting. All
|
|
64
|
-
* Fly's own `slippage` parameter does is price its
|
|
64
|
+
* Fly's own `slippage` parameter does is price its minimum-out floor, which is an
|
|
65
65
|
* absolute amount fixed at quote time and therefore cannot scale down when a
|
|
66
66
|
* legitimately smaller input arrives. The op sends a high value so that floor
|
|
67
67
|
* can never be the binding constraint, and enforces its own rate-based bound
|
|
@@ -137,7 +137,7 @@ export const buildFlySwapExample = (): {
|
|
|
137
137
|
//
|
|
138
138
|
// No slippage guard here, and none is accepted: `amountOut` declares
|
|
139
139
|
// `providesMinimum`, so the op owns its own bound. Attaching one is a
|
|
140
|
-
// `guard_error`. The published `
|
|
140
|
+
// `guard_error`. The published `amount.minimum` is the op's, derived from the
|
|
141
141
|
// bottom of the input band.
|
|
142
142
|
builder.fly.swap('swapAllToWeth', {
|
|
143
143
|
bind: { amountIn: exitPt.amountOut },
|
|
@@ -21,15 +21,16 @@ export interface LifiZapAsyncInput {
|
|
|
21
21
|
* - `lifi.zapAsync`, the future-availability sibling of `lifi.zap`
|
|
22
22
|
* - Binding the required `recipient` input (the async op has no optional
|
|
23
23
|
* recipient — the decl is non-optional, so `bind.recipient` is mandatory)
|
|
24
|
-
* - Omitting `sweepTo`:
|
|
24
|
+
* - Omitting `sweepTo`: a future delivery governs custody, so the
|
|
25
25
|
* output is delivered externally and never enters the execution VM — a
|
|
26
26
|
* sweep would find nothing to move (custody, not amount reporting)
|
|
27
|
-
* - Amount reporting is unaffected by
|
|
28
|
-
* binds a terminal handle the output still reports `
|
|
29
|
-
* the protocol's in-transaction expected value (
|
|
30
|
-
* amount arrives after settlement
|
|
31
|
-
*
|
|
32
|
-
*
|
|
27
|
+
* - Amount reporting is unaffected by delivery timing: when the async lowering
|
|
28
|
+
* binds a terminal handle the output still reports an `amount.estimate`
|
|
29
|
+
* (`basis: 'simulated'`), the protocol's in-transaction expected value (the
|
|
30
|
+
* delivered amount arrives after settlement, when `delivery.when` is
|
|
31
|
+
* `'future'`). An `amount.minimum` appears only when a minimum source
|
|
32
|
+
* exists — for a future output that means a floor the edge lowering itself
|
|
33
|
+
* reported, never a user guard
|
|
33
34
|
* - No output guards: the reported value is the initiating-transaction
|
|
34
35
|
* estimate, not the asynchronously delivered amount a guard could enforce,
|
|
35
36
|
* so `amountOut` is excluded from the generated `TypedGuard` union
|
|
@@ -20,6 +20,13 @@ const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c';
|
|
|
20
20
|
* - Chaining a swap into a zap by threading output handles
|
|
21
21
|
* - The swap's amountOut feeds directly into the zap's amountIn
|
|
22
22
|
* - Slippage guard on the zap output (swap output has providesMinimum)
|
|
23
|
+
*
|
|
24
|
+
* Reading the result of compiling this flow:
|
|
25
|
+
* - `result.outputs['swap.amountOut']` has `role: 'consumed'` (the zap consumes
|
|
26
|
+
* it); `consumedBy` is `'zap'` and `amount.minimum` is the swap floor the
|
|
27
|
+
* router enforces on the intermediate USDC.
|
|
28
|
+
* - `result.outputs['zap.amountOut']` has `role: 'terminal'`; its
|
|
29
|
+
* `amount.minimum` carries the guard floor set above.
|
|
23
30
|
*/
|
|
24
31
|
export const buildSwapAndZapExample = (): {
|
|
25
32
|
flow: Flow;
|
|
@@ -91,18 +91,28 @@ export const handleCompileResult = async (): Promise<void> => {
|
|
|
91
91
|
|
|
92
92
|
if (result.status === 'success') {
|
|
93
93
|
// Full success — transactionRequest includes gasLimit.
|
|
94
|
-
const { transactionRequest, userProxy,
|
|
94
|
+
const { transactionRequest, userProxy, outputs } = result;
|
|
95
95
|
void transactionRequest;
|
|
96
96
|
void userProxy;
|
|
97
|
-
|
|
97
|
+
// Every linear resource output port appears in `outputs`, keyed
|
|
98
|
+
// `<nodeId>.<port>`. The swap's terminal output carries the amount.
|
|
99
|
+
const swapOut = outputs['swap.amountOut'];
|
|
100
|
+
void swapOut?.amount.estimate?.value; // simulated estimate on a 200
|
|
101
|
+
void swapOut?.amount.minimum?.value; // enforced floor, absent if none known
|
|
98
102
|
return;
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
// Partial result — simulation reverted but a transaction is still available.
|
|
102
106
|
// transactionRequest omits gasLimit; the caller must estimate gas themselves.
|
|
103
|
-
const { transactionRequest, simulationRevert, error } = result;
|
|
107
|
+
const { transactionRequest, outputs, simulationRevert, error } = result;
|
|
104
108
|
void transactionRequest;
|
|
105
109
|
|
|
110
|
+
// Nothing was measured, so the estimate is a quote and the minimum is
|
|
111
|
+
// op-sourced (or absent) rather than a guard floor.
|
|
112
|
+
const swapOut = outputs['swap.amountOut'];
|
|
113
|
+
void swapOut?.amount.estimate?.value; // quoted on a 206
|
|
114
|
+
void swapOut?.amount.minimum?.value; // op/declared floor, absent if none
|
|
115
|
+
|
|
106
116
|
// Inspect the revert reason.
|
|
107
117
|
void simulationRevert.code;
|
|
108
118
|
void simulationRevert.rawErrorBytes;
|
|
@@ -60,6 +60,55 @@ export interface LifiSwapConfig {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* a string starting with "0x"
|
|
65
|
+
*/
|
|
66
|
+
export type StartsWith0X = string;
|
|
67
|
+
/**
|
|
68
|
+
* a number between 0 and 1
|
|
69
|
+
*/
|
|
70
|
+
export type Between01 = number;
|
|
71
|
+
|
|
72
|
+
export interface LifiBridgeConfig {
|
|
73
|
+
resourceOut:
|
|
74
|
+
| {
|
|
75
|
+
kind: 'native';
|
|
76
|
+
chainId: Positive;
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
kind: 'erc20';
|
|
80
|
+
token: Address;
|
|
81
|
+
chainId: Positive;
|
|
82
|
+
};
|
|
83
|
+
destination: StartsWith0X;
|
|
84
|
+
slippage?: Between01;
|
|
85
|
+
timing?: {
|
|
86
|
+
swapStepTimingStrategies?: {
|
|
87
|
+
strategy: 'minWaitTime';
|
|
88
|
+
minWaitTimeMs: number;
|
|
89
|
+
startingExpectedResults: number;
|
|
90
|
+
reduceEveryMs: number;
|
|
91
|
+
}[];
|
|
92
|
+
routeTimingStrategies?: {
|
|
93
|
+
strategy: 'minWaitTime';
|
|
94
|
+
minWaitTimeMs: number;
|
|
95
|
+
startingExpectedResults: number;
|
|
96
|
+
reduceEveryMs: number;
|
|
97
|
+
}[];
|
|
98
|
+
};
|
|
99
|
+
bridges?: {
|
|
100
|
+
allow?: MaxItems50;
|
|
101
|
+
deny?: MaxItems50;
|
|
102
|
+
prefer?: MaxItems50;
|
|
103
|
+
};
|
|
104
|
+
exchanges?: {
|
|
105
|
+
allow?: MaxItems50;
|
|
106
|
+
deny?: MaxItems50;
|
|
107
|
+
prefer?: MaxItems50;
|
|
108
|
+
};
|
|
109
|
+
order?: 'FASTEST' | 'CHEAPEST';
|
|
110
|
+
}
|
|
111
|
+
|
|
63
112
|
export interface InvariantNumericConfig {
|
|
64
113
|
op: 'gte' | 'gt' | 'lte' | 'lt' | 'eq' | 'neq';
|
|
65
114
|
threshold: IntegerStringInput;
|
|
@@ -671,10 +720,6 @@ export type ContinuationAssertReadyConfig =
|
|
|
671
720
|
comparator: 'gte' | 'eq' | 'lte';
|
|
672
721
|
value: string;
|
|
673
722
|
};
|
|
674
|
-
/**
|
|
675
|
-
* a string starting with "0x"
|
|
676
|
-
*/
|
|
677
|
-
export type StartsWith0X = string;
|
|
678
723
|
|
|
679
724
|
export interface LifiZapConfig {
|
|
680
725
|
resourceOut:
|