@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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AppliedGuard,
|
|
3
|
+
BindValue,
|
|
3
4
|
Call,
|
|
4
5
|
Flow,
|
|
5
6
|
FlowInput,
|
|
6
|
-
Ref,
|
|
7
7
|
Resource,
|
|
8
8
|
SolType,
|
|
9
9
|
} from '@lifi/compose-spec';
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
InputSchema,
|
|
14
14
|
OutputKind,
|
|
15
15
|
TypedGuard,
|
|
16
|
+
TypedLiteral,
|
|
16
17
|
TypedRef,
|
|
17
18
|
} from '../types.js';
|
|
18
19
|
|
|
@@ -21,7 +22,7 @@ import type {
|
|
|
21
22
|
OutputHandle,
|
|
22
23
|
ResourceInputHandle,
|
|
23
24
|
} from './handles.js';
|
|
24
|
-
import {
|
|
25
|
+
import { toBindValue } from './handles.js';
|
|
25
26
|
import { ref } from './raw.js';
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -37,17 +38,14 @@ export interface CallArgs {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
* Any value that can be bound to an operation parameter: an input handle,
|
|
41
|
-
*
|
|
41
|
+
* 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, or a typed
|
|
43
|
+
* literal.
|
|
42
44
|
*
|
|
43
|
-
* Use `raw.ref<T>()` to create a typed ref from a raw path string
|
|
45
|
+
* Use `raw.ref<T>()` to create a typed ref from a raw path string, and
|
|
46
|
+
* `raw.literal<T>()` to bake in a constant.
|
|
44
47
|
*/
|
|
45
|
-
export type AnyBindable = InputHandle | OutputHandle | TypedRef;
|
|
46
|
-
|
|
47
|
-
const toRef = (bindable: AnyBindable): Ref => {
|
|
48
|
-
if ('_tag' in bindable) return handleToRef(bindable);
|
|
49
|
-
return bindable;
|
|
50
|
-
};
|
|
48
|
+
export type AnyBindable = InputHandle | OutputHandle | TypedRef | TypedLiteral;
|
|
51
49
|
|
|
52
50
|
/**
|
|
53
51
|
* Provides references to runtime context values available inside a flow.
|
|
@@ -127,7 +125,7 @@ export interface FlowBuilderCore<T extends InputSchema = InputSchema> {
|
|
|
127
125
|
id: string,
|
|
128
126
|
op: string,
|
|
129
127
|
args: {
|
|
130
|
-
readonly bind: Record<string,
|
|
128
|
+
readonly bind: Record<string, BindValue>;
|
|
131
129
|
readonly config: Record<string, unknown>;
|
|
132
130
|
readonly guards?: readonly AppliedGuard[];
|
|
133
131
|
},
|
|
@@ -196,9 +194,9 @@ export const createFlowBuilderCore = <T extends InputSchema>(
|
|
|
196
194
|
if (nodeIds.has(nodeId)) throw new Error(`Duplicate node id: "${nodeId}"`);
|
|
197
195
|
nodeIds.add(nodeId);
|
|
198
196
|
|
|
199
|
-
const bind: Record<string,
|
|
197
|
+
const bind: Record<string, BindValue> = {};
|
|
200
198
|
for (const [key, val] of Object.entries(args.bind)) {
|
|
201
|
-
bind[key] =
|
|
199
|
+
bind[key] = toBindValue(val);
|
|
202
200
|
}
|
|
203
201
|
|
|
204
202
|
const node: Call = {
|
|
@@ -230,7 +228,7 @@ export const createFlowBuilderCore = <T extends InputSchema>(
|
|
|
230
228
|
nodeId: string,
|
|
231
229
|
op: string,
|
|
232
230
|
args: {
|
|
233
|
-
readonly bind: Record<string,
|
|
231
|
+
readonly bind: Record<string, BindValue>;
|
|
234
232
|
readonly config: Record<string, unknown>;
|
|
235
233
|
readonly guards?: readonly AppliedGuard[];
|
|
236
234
|
},
|
package/src/authoring/handles.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BindValue,
|
|
3
|
+
Resource,
|
|
4
|
+
SolType,
|
|
5
|
+
StaticSolType,
|
|
6
|
+
} from '@lifi/compose-spec';
|
|
2
7
|
|
|
3
|
-
import type { OutputKind, TypedRef } from '../types.js';
|
|
8
|
+
import type { OutputKind, TypedLiteral, TypedRef } from '../types.js';
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* A handle referencing a flow input declared in the flow's input schema.
|
|
@@ -75,16 +80,47 @@ type SlotAssignable<T extends OutputKind> = T extends 'bytes32'
|
|
|
75
80
|
: T;
|
|
76
81
|
|
|
77
82
|
/**
|
|
78
|
-
* The
|
|
83
|
+
* The values accepted by a typed slot that must resolve to a `$ref`:
|
|
79
84
|
* - `OutputHandle<T>` / `InputHandle<T>` (same type)
|
|
80
85
|
* - For `'uint256'` slots, also `'resource'`-tagged handles
|
|
81
86
|
* - For `'bytes32'` slots, any static-32-byte handle (and `'resource'`)
|
|
82
87
|
* - `TypedRef<T>` (typed context refs, subject to `SlotAssignable`)
|
|
83
88
|
*
|
|
84
|
-
* Plain `{ $ref: '...' }` objects are **not** accepted
|
|
89
|
+
* Plain `{ $ref: '...' }` objects are **not** accepted - use `raw.ref<T>()`
|
|
85
90
|
* to create a typed ref when you need to reference a raw path.
|
|
86
91
|
*/
|
|
87
|
-
export type
|
|
92
|
+
export type RefBindable<T extends OutputKind> =
|
|
88
93
|
| OutputHandle<SlotAssignable<T>>
|
|
89
94
|
| InputHandle<SlotAssignable<T>>
|
|
90
95
|
| TypedRef<SlotAssignable<T>>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Any ref-resolving bindable, with no per-slot type constraint.
|
|
99
|
+
*/
|
|
100
|
+
export type AnyRefBindable = InputHandle | OutputHandle | TypedRef;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The set of values accepted by a typed operation bind slot: everything
|
|
104
|
+
* {@link RefBindable} accepts, plus a {@link TypedLiteral} constant baked
|
|
105
|
+
* into the flow document. A `'resource'` slot rejects a literal: a token
|
|
106
|
+
* resource is not a scalar constant.
|
|
107
|
+
*
|
|
108
|
+
* Use `raw.literal<T>()` to create one.
|
|
109
|
+
*/
|
|
110
|
+
export type Bindable<T extends OutputKind> =
|
|
111
|
+
RefBindable<T> | LiteralBindable<SlotAssignable<T>>;
|
|
112
|
+
|
|
113
|
+
// Distributes over the slot's assignable kinds and keeps the scalar ones: a
|
|
114
|
+
// `'resource'` slot has no scalar arm, so its literal arm collapses to
|
|
115
|
+
// `never` and a literal is rejected outright.
|
|
116
|
+
type LiteralBindable<K extends OutputKind> = K extends SolType
|
|
117
|
+
? TypedLiteral<K>
|
|
118
|
+
: never;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Converts a bindable to the wire `bind` value it denotes: handles become
|
|
122
|
+
* `$ref` pointers, refs and literals pass through as-is.
|
|
123
|
+
*/
|
|
124
|
+
export const toBindValue = (
|
|
125
|
+
bindable: InputHandle | OutputHandle | TypedRef | TypedLiteral,
|
|
126
|
+
): BindValue => ('_tag' in bindable ? handleToRef(bindable) : bindable);
|
package/src/authoring/raw.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AppliedGuard } from '@lifi/compose-spec';
|
|
1
|
+
import type { AppliedGuard, SolType } from '@lifi/compose-spec';
|
|
2
2
|
|
|
3
|
-
import type { OutputKind, TypedRef } from '../types.js';
|
|
3
|
+
import type { OutputKind, TypedLiteral, TypedRef } from '../types.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates a typed `$ref` pointer for use in bind slots.
|
|
@@ -39,6 +39,40 @@ export const ref = <T extends OutputKind>(path: string): TypedRef<T> => ({
|
|
|
39
39
|
$ref: path,
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Creates a typed literal for use in bind slots: a constant baked into the
|
|
44
|
+
* flow document rather than supplied at run time.
|
|
45
|
+
*
|
|
46
|
+
* Prefer a declared flow input and `builder.inputs.<name>` — a value the
|
|
47
|
+
* caller supplies per run stays out of the document. Use a literal when the
|
|
48
|
+
* flow has no run of its own to take the value from: the `continuationFlow`
|
|
49
|
+
* of a `continuation.settle` node is stored by the settlement backend and
|
|
50
|
+
* re-run later against the deferred proceeds, so its delivery address has to
|
|
51
|
+
* be part of the committed document.
|
|
52
|
+
*
|
|
53
|
+
* @typeParam T - The Solidity type of the literal (e.g. `'address'`).
|
|
54
|
+
* @param kind - The literal's Solidity type.
|
|
55
|
+
* @param value - The literal value, as the wire format carries it: a string.
|
|
56
|
+
* @returns A {@link TypedLiteral} accepted by `Bindable<T>` slots matching `T`.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { raw } from '@lifi/composer-sdk';
|
|
61
|
+
*
|
|
62
|
+
* builder.core.transfer('deliver', {
|
|
63
|
+
* bind: {
|
|
64
|
+
* amount: zap.amountOut,
|
|
65
|
+
* recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
|
|
66
|
+
* },
|
|
67
|
+
* config: {},
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export const literal = <T extends SolType>(
|
|
72
|
+
kind: T,
|
|
73
|
+
value: string,
|
|
74
|
+
): TypedLiteral<T> => ({ kind, value });
|
|
75
|
+
|
|
42
76
|
/**
|
|
43
77
|
* Creates a raw guard object for use in the `guards` array of an operation call.
|
|
44
78
|
*
|
|
@@ -3,32 +3,36 @@ import { type ParseAbiItem, parseAbiItem } from 'abitype';
|
|
|
3
3
|
|
|
4
4
|
import type { AbiTypeToOutputKind, TypedGuard } from '../types.js';
|
|
5
5
|
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
6
|
+
import type { CallArgs } from './FlowBuilderCore.js';
|
|
7
|
+
import type { AnyRefBindable, OutputHandle, RefBindable } from './handles.js';
|
|
8
8
|
import { handleToRef } from './handles.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Type-level extraction of named bind keys from a function signature string.
|
|
12
12
|
* When `TSig` is a string literal matching a Solidity function signature,
|
|
13
13
|
* resolves to a record whose keys are the parameter names and values are
|
|
14
|
-
* `
|
|
14
|
+
* `RefBindable<kind>` where the kind is derived from each parameter's Solidity
|
|
15
15
|
* type via `AbiTypeToOutputKind`. For recognised `SolType` parameters
|
|
16
16
|
* (e.g. `uint256`, `address`, `uint128`) the bind slot is precisely typed;
|
|
17
17
|
* for other Solidity types (e.g. tuples) the slot accepts any scalar handle.
|
|
18
|
-
* Falls back to `Record<string,
|
|
18
|
+
* Falls back to `Record<string, AnyRefBindable>` for non-literal strings.
|
|
19
|
+
*
|
|
20
|
+
* These slots are lowered into `config.args`, which is positional calldata
|
|
21
|
+
* rather than a node `bind` record, so a literal is not accepted here: only
|
|
22
|
+
* values that resolve to a `$ref`.
|
|
19
23
|
*/
|
|
20
24
|
export type SignatureBind<TSig extends string> = string extends TSig
|
|
21
|
-
? Record<string,
|
|
25
|
+
? Record<string, AnyRefBindable>
|
|
22
26
|
: ParseAbiItem<TSig> extends {
|
|
23
27
|
type: 'function';
|
|
24
28
|
inputs: infer I extends readonly { name: string; type: string }[];
|
|
25
29
|
}
|
|
26
30
|
? {
|
|
27
|
-
readonly [P in I[number] as P['name']]:
|
|
31
|
+
readonly [P in I[number] as P['name']]: RefBindable<
|
|
28
32
|
AbiTypeToOutputKind<P['type']>
|
|
29
33
|
>;
|
|
30
34
|
}
|
|
31
|
-
: Record<string,
|
|
35
|
+
: Record<string, AnyRefBindable>;
|
|
32
36
|
|
|
33
37
|
// ---------------------------------------------------------------------------
|
|
34
38
|
// Return-type inference from function signatures
|
|
@@ -126,18 +130,18 @@ export const parseFunctionParams = (functionSignature: string): string[] => {
|
|
|
126
130
|
};
|
|
127
131
|
|
|
128
132
|
/**
|
|
129
|
-
* Converts
|
|
133
|
+
* Converts a ref-resolving bindable to a `Ref` (`{ $ref: string }`).
|
|
130
134
|
* Handles carry a `_tag` property and are converted via `handleToRef`;
|
|
131
135
|
* `Ref` objects pass through as-is.
|
|
132
136
|
*/
|
|
133
|
-
export const toBindRef = (bindable:
|
|
137
|
+
export const toBindRef = (bindable: AnyRefBindable): Ref => {
|
|
134
138
|
if ('_tag' in bindable) return handleToRef(bindable);
|
|
135
139
|
return bindable;
|
|
136
140
|
};
|
|
137
141
|
|
|
138
142
|
export interface BuildCallWireFormatInput {
|
|
139
|
-
readonly resource?:
|
|
140
|
-
readonly bind: Record<string,
|
|
143
|
+
readonly resource?: AnyRefBindable;
|
|
144
|
+
readonly bind: Record<string, AnyRefBindable>;
|
|
141
145
|
readonly config: Record<string, unknown>;
|
|
142
146
|
readonly guards?: readonly TypedGuard[];
|
|
143
147
|
}
|
|
@@ -155,7 +159,7 @@ export interface CallWireResult {
|
|
|
155
159
|
*/
|
|
156
160
|
const resolvePositionalArgs = (
|
|
157
161
|
label: string,
|
|
158
|
-
bind: Record<string,
|
|
162
|
+
bind: Record<string, AnyRefBindable>,
|
|
159
163
|
config: Record<string, unknown>,
|
|
160
164
|
): Ref[] => {
|
|
161
165
|
const functionSignature = config.functionSignature;
|
|
@@ -222,7 +226,7 @@ export const buildCallWireFormat = (
|
|
|
222
226
|
};
|
|
223
227
|
|
|
224
228
|
export interface BuildStaticCallWireFormatInput {
|
|
225
|
-
readonly bind: Record<string,
|
|
229
|
+
readonly bind: Record<string, AnyRefBindable>;
|
|
226
230
|
readonly config: Record<string, unknown>;
|
|
227
231
|
readonly guards?: readonly TypedGuard[];
|
|
228
232
|
}
|
package/src/client.ts
CHANGED
|
@@ -9,7 +9,11 @@ import type {
|
|
|
9
9
|
SimulateResult,
|
|
10
10
|
} from '@lifi/compose-spec';
|
|
11
11
|
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
ChainSummary,
|
|
14
|
+
GetZapPacksOptions,
|
|
15
|
+
ZapPackOverview,
|
|
16
|
+
} from './discovery.js';
|
|
13
17
|
import { ComposeError, errorFromHttpResponse } from './errors.js';
|
|
14
18
|
import {
|
|
15
19
|
parseCompilePartialEnvelope,
|
|
@@ -55,6 +59,28 @@ export interface ComposeClient {
|
|
|
55
59
|
* @throws {@link ComposeError} on network, validation, or server errors.
|
|
56
60
|
*/
|
|
57
61
|
readonly getManifest: () => Promise<ComposeManifest>;
|
|
62
|
+
/**
|
|
63
|
+
* Fetches the chains the Compose API supports, ascending by `chainId`.
|
|
64
|
+
*
|
|
65
|
+
* A chain is listed once the server confirms the VM contract is deployed on
|
|
66
|
+
* it — the same condition {@link compile}, {@link route} and
|
|
67
|
+
* {@link simulate} validate against. A chain absent from this list is
|
|
68
|
+
* rejected outright by those endpoints.
|
|
69
|
+
*
|
|
70
|
+
* The converse does not hold: a listed chain can still have no routing edge
|
|
71
|
+
* for a given token pair, which {@link route} reports as `no_route_error`.
|
|
72
|
+
* Use {@link getZapPacks} to enumerate the pairs that do route.
|
|
73
|
+
*
|
|
74
|
+
* Each entry carries a display `name` alongside its `chainId`. The name is a
|
|
75
|
+
* deployment-maintained label — render it, but key on `chainId`.
|
|
76
|
+
*
|
|
77
|
+
* The list tracks the server's chain registry, which refreshes periodically.
|
|
78
|
+
* Results are not cached by the SDK; callers should cache as appropriate.
|
|
79
|
+
*
|
|
80
|
+
* @returns The supported chains, ascending by `chainId`.
|
|
81
|
+
* @throws {@link ComposeError} on network or server errors.
|
|
82
|
+
*/
|
|
83
|
+
readonly getChains: () => Promise<readonly ChainSummary[]>;
|
|
58
84
|
/**
|
|
59
85
|
* Submits a compile request and returns the result.
|
|
60
86
|
*
|
|
@@ -75,9 +101,9 @@ export interface ComposeClient {
|
|
|
75
101
|
* `POST /compose/route`.
|
|
76
102
|
*
|
|
77
103
|
* A convenience path that skips flow authoring: the server builds a one-node
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
104
|
+
* zap flow with a `directDeposit` input from the `fromToken` / `toToken`
|
|
105
|
+
* pair and runs it through the same pipeline as {@link compile}. The
|
|
106
|
+
* response is therefore the same discriminated result — HTTP 206 under
|
|
81
107
|
* `simulationPolicy: 'allow-revert'` yields `status: 'partial'` rather than
|
|
82
108
|
* throwing, exactly as it does for {@link compile}.
|
|
83
109
|
*
|
|
@@ -88,6 +114,13 @@ export interface ComposeClient {
|
|
|
88
114
|
* author richer flows later, so the single-step limit is the current state of
|
|
89
115
|
* the server, not the shape of this contract.
|
|
90
116
|
*
|
|
117
|
+
* Only synchronous edges are considered unless the request sets
|
|
118
|
+
* `allowAsyncRoutes: true`, so by default every produced resource comes back
|
|
119
|
+
* `availability: 'now'` and a pair reachable only after settlement is a
|
|
120
|
+
* `no_route_error`. Opting in adds the `lifi.zapAsync` fallback, which still
|
|
121
|
+
* loses to a synchronous edge when the pair has one; read
|
|
122
|
+
* `producedResources[*].availability` to see which representation you got.
|
|
123
|
+
*
|
|
91
124
|
* Use {@link compile} for what the server does not author for you: several
|
|
92
125
|
* chained operations, splits, explicit preconditions, or per-op guards.
|
|
93
126
|
*
|
|
@@ -149,6 +182,67 @@ const bigintReplacer = (_key: string, value: unknown): unknown =>
|
|
|
149
182
|
const isNonNullObject = (v: unknown): v is Record<string, unknown> =>
|
|
150
183
|
typeof v === 'object' && v !== null;
|
|
151
184
|
|
|
185
|
+
// Non-negative decimal integer string, as the server sends token amounts.
|
|
186
|
+
const INTEGER_STRING = /^\d+$/;
|
|
187
|
+
|
|
188
|
+
const decodeAmount = (
|
|
189
|
+
value: unknown,
|
|
190
|
+
key: string,
|
|
191
|
+
field: 'amountOut' | 'amountOutMin',
|
|
192
|
+
url: string,
|
|
193
|
+
): bigint => {
|
|
194
|
+
if (typeof value !== 'string' || !INTEGER_STRING.test(value)) {
|
|
195
|
+
throw new ComposeError(
|
|
196
|
+
'UNKNOWN_ERROR',
|
|
197
|
+
`Malformed compose response: producedResources.${key}.simulated.${field} is ${
|
|
198
|
+
value === undefined ? 'absent' : 'not an integer string'
|
|
199
|
+
}`,
|
|
200
|
+
{ url },
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return BigInt(value);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// `simulated.amountOut` / `.amountOutMin` are declared `bigint` on
|
|
207
|
+
// `SimulatedAmounts` (the domain truth: `compose-core` produces real bigints)
|
|
208
|
+
// but arrive as decimal strings on the wire (the backend stringifies them).
|
|
209
|
+
// Decode at this seam so a consumer reading the declared type gets the value
|
|
210
|
+
// the type promises. A resource without `simulated` is left untouched; a
|
|
211
|
+
// `simulated` claiming a missing or non-numeric amount is a contract violation
|
|
212
|
+
// and surfaces as a `ComposeError` rather than `NaN` / `BigInt(undefined)`.
|
|
213
|
+
const decodeSimulatedAmounts = (data: unknown, url: string): void => {
|
|
214
|
+
if (!isNonNullObject(data)) return;
|
|
215
|
+
const resources = data.producedResources;
|
|
216
|
+
if (!isNonNullObject(resources)) return;
|
|
217
|
+
for (const key of Object.keys(resources)) {
|
|
218
|
+
const resource = resources[key];
|
|
219
|
+
if (!isNonNullObject(resource)) continue;
|
|
220
|
+
const simulated = resource.simulated;
|
|
221
|
+
if (simulated === undefined) continue;
|
|
222
|
+
if (!isNonNullObject(simulated)) {
|
|
223
|
+
throw new ComposeError(
|
|
224
|
+
'UNKNOWN_ERROR',
|
|
225
|
+
`Malformed compose response: producedResources.${key}.simulated is not an object`,
|
|
226
|
+
{ url },
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
simulated.amountOut = decodeAmount(
|
|
230
|
+
simulated.amountOut,
|
|
231
|
+
key,
|
|
232
|
+
'amountOut',
|
|
233
|
+
url,
|
|
234
|
+
);
|
|
235
|
+
if (simulated.amountOutMin !== undefined) {
|
|
236
|
+
simulated.amountOutMin = decodeAmount(
|
|
237
|
+
simulated.amountOutMin,
|
|
238
|
+
key,
|
|
239
|
+
'amountOutMin',
|
|
240
|
+
url,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
152
246
|
const parseBody = async <T>(res: Response, url: string): Promise<T> => {
|
|
153
247
|
const body = await res.json().catch((_) => null);
|
|
154
248
|
if (!isNonNullObject(body) || !('data' in body)) {
|
|
@@ -164,6 +258,7 @@ const parseCompileSuccessBody = async (
|
|
|
164
258
|
url: string,
|
|
165
259
|
): Promise<ComposeCompileResult> => {
|
|
166
260
|
const data = await parseBody<ComposeCompileSuccessData>(res, url);
|
|
261
|
+
decodeSimulatedAmounts(data, url);
|
|
167
262
|
return { ...data, status: 'success' as const };
|
|
168
263
|
};
|
|
169
264
|
|
|
@@ -184,6 +279,7 @@ const parsePartialBody = async (
|
|
|
184
279
|
// shape as a hand-authored type, so we narrow it here rather than re-declaring
|
|
185
280
|
// that type as a schema. `error` is fully validated — no cast needed.
|
|
186
281
|
const data = envelope.data as unknown as ComposeCompilePartialData;
|
|
282
|
+
decodeSimulatedAmounts(data, url);
|
|
187
283
|
return { ...data, status: 'partial' as const, error: envelope.error };
|
|
188
284
|
};
|
|
189
285
|
|
|
@@ -240,6 +336,29 @@ const postCompile = async (
|
|
|
240
336
|
return await parseCompileSuccessBody(res, url);
|
|
241
337
|
};
|
|
242
338
|
|
|
339
|
+
// Every enveloped GET on this API behaves identically: no body, no query
|
|
340
|
+
// semantics of its own, `{ data }` on success, an error envelope otherwise.
|
|
341
|
+
const getJson = async <T>(
|
|
342
|
+
{ fetchFn, baseHeaders }: ComposeTransport,
|
|
343
|
+
url: string,
|
|
344
|
+
): Promise<T> => {
|
|
345
|
+
let res: Response;
|
|
346
|
+
try {
|
|
347
|
+
res = await fetchFn(url, {
|
|
348
|
+
method: 'GET',
|
|
349
|
+
headers: { ...baseHeaders },
|
|
350
|
+
});
|
|
351
|
+
} catch (err) {
|
|
352
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
353
|
+
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
354
|
+
}
|
|
355
|
+
if (!res.ok) {
|
|
356
|
+
const body = await res.text();
|
|
357
|
+
throw errorFromHttpResponse(res.status, body, url);
|
|
358
|
+
}
|
|
359
|
+
return await parseBody<T>(res, url);
|
|
360
|
+
};
|
|
361
|
+
|
|
243
362
|
/**
|
|
244
363
|
* Creates a low-level Compose API client.
|
|
245
364
|
*
|
|
@@ -272,24 +391,11 @@ export const createComposeClient = (
|
|
|
272
391
|
};
|
|
273
392
|
const transport: ComposeTransport = { fetchFn, baseHeaders };
|
|
274
393
|
|
|
275
|
-
const getManifest = async (): Promise<ComposeManifest> =>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
method: 'GET',
|
|
281
|
-
headers: { ...baseHeaders },
|
|
282
|
-
});
|
|
283
|
-
} catch (err) {
|
|
284
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
285
|
-
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
286
|
-
}
|
|
287
|
-
if (!res.ok) {
|
|
288
|
-
const body = await res.text();
|
|
289
|
-
throw errorFromHttpResponse(res.status, body, url);
|
|
290
|
-
}
|
|
291
|
-
return await parseBody<ComposeManifest>(res, url);
|
|
292
|
-
};
|
|
394
|
+
const getManifest = async (): Promise<ComposeManifest> =>
|
|
395
|
+
getJson<ComposeManifest>(transport, `${base}/compose/manifest`);
|
|
396
|
+
|
|
397
|
+
const getChains = async (): Promise<readonly ChainSummary[]> =>
|
|
398
|
+
getJson<readonly ChainSummary[]>(transport, `${base}/chains`);
|
|
293
399
|
|
|
294
400
|
const compile = async (
|
|
295
401
|
request: ComposeCompileRequest,
|
|
@@ -312,22 +418,10 @@ export const createComposeClient = (
|
|
|
312
418
|
params.set('protocols', list);
|
|
313
419
|
}
|
|
314
420
|
const qs = params.toString();
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
method: 'GET',
|
|
320
|
-
headers: { ...baseHeaders },
|
|
321
|
-
});
|
|
322
|
-
} catch (err) {
|
|
323
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
324
|
-
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
325
|
-
}
|
|
326
|
-
if (!res.ok) {
|
|
327
|
-
const body = await res.text();
|
|
328
|
-
throw errorFromHttpResponse(res.status, body, url);
|
|
329
|
-
}
|
|
330
|
-
return await parseBody<readonly ZapPackOverview[]>(res, url);
|
|
421
|
+
return getJson<readonly ZapPackOverview[]>(
|
|
422
|
+
transport,
|
|
423
|
+
`${base}/compose/zap-packs${qs ? `?${qs}` : ''}`,
|
|
424
|
+
);
|
|
331
425
|
};
|
|
332
426
|
|
|
333
427
|
const simulate = async (
|
|
@@ -358,5 +452,5 @@ export const createComposeClient = (
|
|
|
358
452
|
throw errorFromHttpResponse(res.status, body, url);
|
|
359
453
|
};
|
|
360
454
|
|
|
361
|
-
return { getManifest, compile, route, getZapPacks, simulate };
|
|
455
|
+
return { getManifest, getChains, compile, route, getZapPacks, simulate };
|
|
362
456
|
};
|
package/src/discovery.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Types for the Compose discovery API (`GET /compose/zap-packs`).
|
|
2
|
+
* Types for the Compose discovery API (`GET /compose/zap-packs`, `GET /chains`).
|
|
3
3
|
*
|
|
4
4
|
* These are SDK-facing presentation types — not part of the core compile
|
|
5
5
|
* contract in compose-spec. The backend response is the source of truth;
|
|
@@ -11,6 +11,23 @@
|
|
|
11
11
|
* `"enter-position"`, `"exit-position"`, `"wrap"`, `"unwrap"`, `"mint"`, `"burn"`.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* A chain the Compose deployment supports.
|
|
16
|
+
*/
|
|
17
|
+
export interface ChainSummary {
|
|
18
|
+
/** EVM chain ID. The stable identifier — key on this. */
|
|
19
|
+
readonly chainId: number;
|
|
20
|
+
/**
|
|
21
|
+
* Human-readable label, suitable for display in a chain selector.
|
|
22
|
+
*
|
|
23
|
+
* Maintained by the deployment rather than derived from canonical chain
|
|
24
|
+
* branding, so it can differ from a chain's marketed name and can change
|
|
25
|
+
* without notice. Never treat it as an identifier, and never match on it.
|
|
26
|
+
* A chain the deployment has no configured label for reports `Chain <id>`.
|
|
27
|
+
*/
|
|
28
|
+
readonly name: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
/**
|
|
15
32
|
* A single routing edge representing a supported token-to-token path
|
|
16
33
|
* through a DeFi protocol.
|