@lifi/composer-sdk 0.4.0 → 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 +98 -5
- 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 +6 -1
- 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 +5 -1
- 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 +94 -58
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +61 -2
- package/dist/client.d.ts +61 -2
- package/dist/client.js +94 -58
- package/dist/client.js.map +1 -1
- package/dist/discovery.cjs.map +1 -1
- package/dist/discovery.d.cts +31 -2
- package/dist/discovery.d.ts +31 -2
- package/dist/generated/config.generated.cjs.map +1 -1
- package/dist/generated/config.generated.d.cts +35 -3
- package/dist/generated/config.generated.d.ts +35 -3
- package/dist/generated/operations.generated.cjs +9 -0
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +33 -2
- package/dist/generated/operations.generated.d.ts +33 -2
- package/dist/generated/operations.generated.js +9 -0
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/index.cjs +5 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -7
- package/dist/index.d.ts +26 -7
- package/dist/index.js +4 -1
- 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/routeAmount-DdbJYJyM.d.cts +55 -0
- package/dist/routeAmount-DdbJYJyM.d.ts +55 -0
- package/dist/routeAmount.cjs +38 -0
- package/dist/routeAmount.cjs.map +1 -0
- package/dist/routeAmount.d.cts +2 -0
- package/dist/routeAmount.d.ts +2 -0
- package/dist/routeAmount.js +13 -0
- package/dist/routeAmount.js.map +1 -0
- 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 +3 -0
- package/dist/sdk.cjs.map +1 -1
- package/dist/sdk.d.cts +42 -2
- package/dist/sdk.d.ts +42 -2
- package/dist/sdk.js +3 -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 +6 -13
- package/src/authoring/FlowBuilderCore.ts +13 -15
- package/src/authoring/handles.ts +41 -5
- package/src/authoring/raw.ts +39 -4
- package/src/authoring/signatureArgs.ts +17 -13
- package/src/client.ts +204 -58
- package/src/discovery.ts +31 -1
- package/src/examples/flySwap.ts +167 -0
- package/src/examples/lifiZap.ts +4 -0
- package/src/examples/lifiZapAsync.ts +88 -0
- package/src/examples/routeTokenPair.ts +58 -0
- package/src/examples/run.ts +56 -6
- package/src/examples/supportedChains.ts +122 -0
- package/src/generated/config.generated.ts +45 -3
- package/src/generated/operations.generated.ts +56 -8
- package/src/index.ts +33 -1
- package/src/raw.ts +1 -1
- package/src/routeAmount.ts +61 -0
- package/src/run/inputs.ts +30 -11
- package/src/run/simulate.ts +14 -2
- package/src/sdk.ts +47 -0
- package/src/types.ts +20 -1
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.
|
|
@@ -35,8 +35,43 @@ import type { OutputKind, TypedRef } from '../types.js';
|
|
|
35
35
|
* });
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
38
|
-
export const ref = <T extends OutputKind>(path: string): TypedRef<T> =>
|
|
39
|
-
|
|
38
|
+
export const ref = <T extends OutputKind>(path: string): TypedRef<T> => ({
|
|
39
|
+
$ref: path,
|
|
40
|
+
});
|
|
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 });
|
|
40
75
|
|
|
41
76
|
/**
|
|
42
77
|
* Creates a raw guard object for use in the `guards` array of an operation call.
|
|
@@ -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
|
@@ -4,11 +4,16 @@ import type {
|
|
|
4
4
|
ComposeCompileResult,
|
|
5
5
|
ComposeCompileSuccessData,
|
|
6
6
|
ComposeManifest,
|
|
7
|
+
ComposeRouteRequest,
|
|
7
8
|
SimulateRequest,
|
|
8
9
|
SimulateResult,
|
|
9
10
|
} from '@lifi/compose-spec';
|
|
10
11
|
|
|
11
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
ChainSummary,
|
|
14
|
+
GetZapPacksOptions,
|
|
15
|
+
ZapPackOverview,
|
|
16
|
+
} from './discovery.js';
|
|
12
17
|
import { ComposeError, errorFromHttpResponse } from './errors.js';
|
|
13
18
|
import {
|
|
14
19
|
parseCompilePartialEnvelope,
|
|
@@ -54,6 +59,28 @@ export interface ComposeClient {
|
|
|
54
59
|
* @throws {@link ComposeError} on network, validation, or server errors.
|
|
55
60
|
*/
|
|
56
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[]>;
|
|
57
84
|
/**
|
|
58
85
|
* Submits a compile request and returns the result.
|
|
59
86
|
*
|
|
@@ -69,6 +96,45 @@ export interface ComposeClient {
|
|
|
69
96
|
readonly compile: (
|
|
70
97
|
request: ComposeCompileRequest,
|
|
71
98
|
) => Promise<ComposeCompileResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Compiles a token pair into a submit-ready transaction via
|
|
101
|
+
* `POST /compose/route`.
|
|
102
|
+
*
|
|
103
|
+
* A convenience path that skips flow authoring: the server builds a one-node
|
|
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
|
|
107
|
+
* `simulationPolicy: 'allow-revert'` yields `status: 'partial'` rather than
|
|
108
|
+
* throwing, exactly as it does for {@link compile}.
|
|
109
|
+
*
|
|
110
|
+
* As of today that flow is a single zap step: one edge of the backend's
|
|
111
|
+
* routing catalog (a position entry or exit, a wrap or unwrap, a mint or
|
|
112
|
+
* burn). Enumerate the pairs it covers with {@link getZapPacks}; a pair with
|
|
113
|
+
* no edge is rejected with `kind: 'no_route_error'`. The endpoint is meant to
|
|
114
|
+
* author richer flows later, so the single-step limit is the current state of
|
|
115
|
+
* the server, not the shape of this contract.
|
|
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
|
+
*
|
|
124
|
+
* Use {@link compile} for what the server does not author for you: several
|
|
125
|
+
* chained operations, splits, explicit preconditions, or per-op guards.
|
|
126
|
+
*
|
|
127
|
+
* `bigint` amounts in `amount` are serialised to decimal strings
|
|
128
|
+
* automatically.
|
|
129
|
+
*
|
|
130
|
+
* @param request - The from/to token pair, amount, signer, and route options.
|
|
131
|
+
* @returns A discriminated result: `status: 'success'` or `status: 'partial'`.
|
|
132
|
+
* @throws {@link ComposeError} on network, validation, or server errors —
|
|
133
|
+
* including `NOT_FOUND` (HTTP 404) when no catalog edge covers the pair.
|
|
134
|
+
*/
|
|
135
|
+
readonly route: (
|
|
136
|
+
request: ComposeRouteRequest,
|
|
137
|
+
) => Promise<ComposeCompileResult>;
|
|
72
138
|
/**
|
|
73
139
|
* Fetches the available routing edges grouped by protocol.
|
|
74
140
|
*
|
|
@@ -116,6 +182,67 @@ const bigintReplacer = (_key: string, value: unknown): unknown =>
|
|
|
116
182
|
const isNonNullObject = (v: unknown): v is Record<string, unknown> =>
|
|
117
183
|
typeof v === 'object' && v !== null;
|
|
118
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
|
+
|
|
119
246
|
const parseBody = async <T>(res: Response, url: string): Promise<T> => {
|
|
120
247
|
const body = await res.json().catch((_) => null);
|
|
121
248
|
if (!isNonNullObject(body) || !('data' in body)) {
|
|
@@ -131,6 +258,7 @@ const parseCompileSuccessBody = async (
|
|
|
131
258
|
url: string,
|
|
132
259
|
): Promise<ComposeCompileResult> => {
|
|
133
260
|
const data = await parseBody<ComposeCompileSuccessData>(res, url);
|
|
261
|
+
decodeSimulatedAmounts(data, url);
|
|
134
262
|
return { ...data, status: 'success' as const };
|
|
135
263
|
};
|
|
136
264
|
|
|
@@ -151,6 +279,7 @@ const parsePartialBody = async (
|
|
|
151
279
|
// shape as a hand-authored type, so we narrow it here rather than re-declaring
|
|
152
280
|
// that type as a schema. `error` is fully validated — no cast needed.
|
|
153
281
|
const data = envelope.data as unknown as ComposeCompilePartialData;
|
|
282
|
+
decodeSimulatedAmounts(data, url);
|
|
154
283
|
return { ...data, status: 'partial' as const, error: envelope.error };
|
|
155
284
|
};
|
|
156
285
|
|
|
@@ -173,6 +302,62 @@ const parseSimulateBody = async (
|
|
|
173
302
|
}
|
|
174
303
|
return result;
|
|
175
304
|
};
|
|
305
|
+
// `POST /compose` and `POST /compose/route` differ only in path and request
|
|
306
|
+
// shape: both return the enveloped success data on 200 and the partial
|
|
307
|
+
// envelope on 206, so they share one transport.
|
|
308
|
+
type ComposeTransport = {
|
|
309
|
+
readonly fetchFn: typeof fetch;
|
|
310
|
+
readonly baseHeaders: Record<string, string>;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const postCompile = async (
|
|
314
|
+
{ fetchFn, baseHeaders }: ComposeTransport,
|
|
315
|
+
url: string,
|
|
316
|
+
request: ComposeCompileRequest | ComposeRouteRequest,
|
|
317
|
+
): Promise<ComposeCompileResult> => {
|
|
318
|
+
let res: Response;
|
|
319
|
+
try {
|
|
320
|
+
res = await fetchFn(url, {
|
|
321
|
+
method: 'POST',
|
|
322
|
+
headers: { ...baseHeaders, 'Content-Type': 'application/json' },
|
|
323
|
+
body: JSON.stringify(request, bigintReplacer),
|
|
324
|
+
});
|
|
325
|
+
} catch (err) {
|
|
326
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
327
|
+
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
328
|
+
}
|
|
329
|
+
if (res.status === 206) {
|
|
330
|
+
return await parsePartialBody(res, url);
|
|
331
|
+
}
|
|
332
|
+
if (!res.ok) {
|
|
333
|
+
const body = await res.text();
|
|
334
|
+
throw errorFromHttpResponse(res.status, body, url);
|
|
335
|
+
}
|
|
336
|
+
return await parseCompileSuccessBody(res, url);
|
|
337
|
+
};
|
|
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
|
+
};
|
|
176
361
|
|
|
177
362
|
/**
|
|
178
363
|
* Creates a low-level Compose API client.
|
|
@@ -204,50 +389,23 @@ export const createComposeClient = (
|
|
|
204
389
|
'x-lifi-composer-sdk': SDK_VERSION,
|
|
205
390
|
'x-lifi-api-key': trimmedApiKey,
|
|
206
391
|
};
|
|
392
|
+
const transport: ComposeTransport = { fetchFn, baseHeaders };
|
|
207
393
|
|
|
208
|
-
const getManifest = async (): Promise<ComposeManifest> =>
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
method: 'GET',
|
|
214
|
-
headers: { ...baseHeaders },
|
|
215
|
-
});
|
|
216
|
-
} catch (err) {
|
|
217
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
218
|
-
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
219
|
-
}
|
|
220
|
-
if (!res.ok) {
|
|
221
|
-
const body = await res.text();
|
|
222
|
-
throw errorFromHttpResponse(res.status, body, url);
|
|
223
|
-
}
|
|
224
|
-
return await parseBody<ComposeManifest>(res, url);
|
|
225
|
-
};
|
|
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`);
|
|
226
399
|
|
|
227
400
|
const compile = async (
|
|
228
401
|
request: ComposeCompileRequest,
|
|
229
|
-
): Promise<ComposeCompileResult> =>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
body: JSON.stringify(request, bigintReplacer),
|
|
237
|
-
});
|
|
238
|
-
} catch (err) {
|
|
239
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
240
|
-
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
241
|
-
}
|
|
242
|
-
if (res.status === 206) {
|
|
243
|
-
return await parsePartialBody(res, url);
|
|
244
|
-
}
|
|
245
|
-
if (!res.ok) {
|
|
246
|
-
const body = await res.text();
|
|
247
|
-
throw errorFromHttpResponse(res.status, body, url);
|
|
248
|
-
}
|
|
249
|
-
return await parseCompileSuccessBody(res, url);
|
|
250
|
-
};
|
|
402
|
+
): Promise<ComposeCompileResult> =>
|
|
403
|
+
postCompile(transport, `${base}/compose`, request);
|
|
404
|
+
|
|
405
|
+
const route = async (
|
|
406
|
+
request: ComposeRouteRequest,
|
|
407
|
+
): Promise<ComposeCompileResult> =>
|
|
408
|
+
postCompile(transport, `${base}/compose/route`, request);
|
|
251
409
|
|
|
252
410
|
const getZapPacks = async (
|
|
253
411
|
options?: GetZapPacksOptions,
|
|
@@ -260,22 +418,10 @@ export const createComposeClient = (
|
|
|
260
418
|
params.set('protocols', list);
|
|
261
419
|
}
|
|
262
420
|
const qs = params.toString();
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
method: 'GET',
|
|
268
|
-
headers: { ...baseHeaders },
|
|
269
|
-
});
|
|
270
|
-
} catch (err) {
|
|
271
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
272
|
-
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
273
|
-
}
|
|
274
|
-
if (!res.ok) {
|
|
275
|
-
const body = await res.text();
|
|
276
|
-
throw errorFromHttpResponse(res.status, body, url);
|
|
277
|
-
}
|
|
278
|
-
return await parseBody<readonly ZapPackOverview[]>(res, url);
|
|
421
|
+
return getJson<readonly ZapPackOverview[]>(
|
|
422
|
+
transport,
|
|
423
|
+
`${base}/compose/zap-packs${qs ? `?${qs}` : ''}`,
|
|
424
|
+
);
|
|
279
425
|
};
|
|
280
426
|
|
|
281
427
|
const simulate = async (
|
|
@@ -306,5 +452,5 @@ export const createComposeClient = (
|
|
|
306
452
|
throw errorFromHttpResponse(res.status, body, url);
|
|
307
453
|
};
|
|
308
454
|
|
|
309
|
-
return { getManifest, compile, getZapPacks, simulate };
|
|
455
|
+
return { getManifest, getChains, compile, route, getZapPacks, simulate };
|
|
310
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.
|
|
@@ -26,6 +43,19 @@ export interface ZapPackEdge {
|
|
|
26
43
|
readonly inAmountMin?: string;
|
|
27
44
|
/** Maximum input amount accepted (stringified integer), if constrained. */
|
|
28
45
|
readonly inAmountMax?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Recipient binding obligation. `"required"` means the zap must bind its
|
|
48
|
+
* `recipient` input. The output is externally owned, terminal, and unswept.
|
|
49
|
+
* The loose string type preserves forward compatibility with new values.
|
|
50
|
+
*/
|
|
51
|
+
readonly recipient?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Output availability. `"future"` means the output becomes available only
|
|
54
|
+
* after the initiating transaction settles; route via `lifi.zapAsync` with
|
|
55
|
+
* a bound recipient. Absent means the output is available synchronously.
|
|
56
|
+
* The loose string type preserves forward compatibility with new values.
|
|
57
|
+
*/
|
|
58
|
+
readonly availability?: string;
|
|
29
59
|
}
|
|
30
60
|
|
|
31
61
|
/**
|