@lifi/composer-sdk 0.0.1-alpha
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/LICENSE +201 -0
- package/README.md +220 -0
- package/dist/authoring/FlowBuilderCore.cjs +115 -0
- package/dist/authoring/FlowBuilderCore.cjs.map +1 -0
- package/dist/authoring/FlowBuilderCore.d.cts +114 -0
- package/dist/authoring/FlowBuilderCore.d.ts +114 -0
- package/dist/authoring/FlowBuilderCore.js +91 -0
- package/dist/authoring/FlowBuilderCore.js.map +1 -0
- package/dist/authoring/handles.cjs +43 -0
- package/dist/authoring/handles.cjs.map +1 -0
- package/dist/authoring/handles.d.cts +61 -0
- package/dist/authoring/handles.d.ts +61 -0
- package/dist/authoring/handles.js +17 -0
- package/dist/authoring/handles.js.map +1 -0
- package/dist/authoring/raw.cjs +41 -0
- package/dist/authoring/raw.cjs.map +1 -0
- package/dist/authoring/raw.d.cts +47 -0
- package/dist/authoring/raw.d.ts +47 -0
- package/dist/authoring/raw.js +16 -0
- package/dist/authoring/raw.js.map +1 -0
- package/dist/authoring/signatureArgs.cjs +114 -0
- package/dist/authoring/signatureArgs.cjs.map +1 -0
- package/dist/authoring/signatureArgs.d.cts +146 -0
- package/dist/authoring/signatureArgs.d.ts +146 -0
- package/dist/authoring/signatureArgs.js +87 -0
- package/dist/authoring/signatureArgs.js.map +1 -0
- package/dist/client.cjs +140 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +67 -0
- package/dist/client.d.ts +67 -0
- package/dist/client.js +116 -0
- package/dist/client.js.map +1 -0
- package/dist/discovery.cjs +17 -0
- package/dist/discovery.cjs.map +1 -0
- package/dist/discovery.d.cts +56 -0
- package/dist/discovery.d.ts +56 -0
- package/dist/discovery.js +1 -0
- package/dist/discovery.js.map +1 -0
- package/dist/errors.cjs +91 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.cts +77 -0
- package/dist/errors.d.ts +77 -0
- package/dist/errors.js +65 -0
- package/dist/errors.js.map +1 -0
- package/dist/generated/config.generated.cjs +17 -0
- package/dist/generated/config.generated.cjs.map +1 -0
- package/dist/generated/config.generated.d.cts +144 -0
- package/dist/generated/config.generated.d.ts +144 -0
- package/dist/generated/config.generated.js +1 -0
- package/dist/generated/config.generated.js.map +1 -0
- package/dist/generated/guards.generated.cjs +32 -0
- package/dist/generated/guards.generated.cjs.map +1 -0
- package/dist/generated/guards.generated.d.cts +4 -0
- package/dist/generated/guards.generated.d.ts +4 -0
- package/dist/generated/guards.generated.js +8 -0
- package/dist/generated/guards.generated.js.map +1 -0
- package/dist/generated/materialisers.generated.cjs +44 -0
- package/dist/generated/materialisers.generated.cjs.map +1 -0
- package/dist/generated/materialisers.generated.d.cts +4 -0
- package/dist/generated/materialisers.generated.d.ts +4 -0
- package/dist/generated/materialisers.generated.js +18 -0
- package/dist/generated/materialisers.generated.js.map +1 -0
- package/dist/generated/operations.generated.cjs +110 -0
- package/dist/generated/operations.generated.cjs.map +1 -0
- package/dist/generated/operations.generated.d.cts +337 -0
- package/dist/generated/operations.generated.d.ts +337 -0
- package/dist/generated/operations.generated.js +89 -0
- package/dist/generated/operations.generated.js.map +1 -0
- package/dist/generated/preconditions.generated.cjs +44 -0
- package/dist/generated/preconditions.generated.cjs.map +1 -0
- package/dist/generated/preconditions.generated.d.cts +4 -0
- package/dist/generated/preconditions.generated.d.ts +4 -0
- package/dist/generated/preconditions.generated.js +18 -0
- package/dist/generated/preconditions.generated.js.map +1 -0
- package/dist/guards.generated-DKKDKREA.d.ts +14 -0
- package/dist/guards.generated-hrQ1KPNv.d.cts +14 -0
- package/dist/index.cjs +76 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +99 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/materialisers.generated-BsA1uDW8.d.ts +24 -0
- package/dist/materialisers.generated-nMxTAaVi.d.cts +24 -0
- package/dist/preconditions.generated-DblS13Wq.d.cts +24 -0
- package/dist/preconditions.generated-U7YpVh30.d.ts +24 -0
- package/dist/raw-Dj_2rBSQ.d.cts +19 -0
- package/dist/raw-IxCF_bSS.d.ts +19 -0
- package/dist/raw.cjs +34 -0
- package/dist/raw.cjs.map +1 -0
- package/dist/raw.d.cts +4 -0
- package/dist/raw.d.ts +4 -0
- package/dist/raw.js +8 -0
- package/dist/raw.js.map +1 -0
- package/dist/resources-BKlFxjo8.d.cts +56 -0
- package/dist/resources-CYis1_DS.d.ts +56 -0
- package/dist/resources.cjs +39 -0
- package/dist/resources.cjs.map +1 -0
- package/dist/resources.d.cts +3 -0
- package/dist/resources.d.ts +3 -0
- package/dist/resources.js +14 -0
- package/dist/resources.js.map +1 -0
- package/dist/run/inputs.cjs +51 -0
- package/dist/run/inputs.cjs.map +1 -0
- package/dist/run/inputs.d.cts +91 -0
- package/dist/run/inputs.d.ts +91 -0
- package/dist/run/inputs.js +26 -0
- package/dist/run/inputs.js.map +1 -0
- package/dist/sdk.cjs +60 -0
- package/dist/sdk.cjs.map +1 -0
- package/dist/sdk.d.cts +112 -0
- package/dist/sdk.d.ts +112 -0
- package/dist/sdk.js +40 -0
- package/dist/sdk.js.map +1 -0
- package/dist/types.cjs +17 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +74 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +81 -0
- package/src/authoring/FlowBuilderCore.ts +270 -0
- package/src/authoring/handles.ts +81 -0
- package/src/authoring/raw.ts +60 -0
- package/src/authoring/signatureArgs.ts +249 -0
- package/src/client.ts +230 -0
- package/src/discovery.ts +51 -0
- package/src/errors.ts +150 -0
- package/src/examples/approveAndDeposit.ts +84 -0
- package/src/examples/callContract.ts +199 -0
- package/src/examples/config.ts +9 -0
- package/src/examples/consolidateToEth.ts +94 -0
- package/src/examples/consolidateToUsdc.ts +95 -0
- package/src/examples/depositFromProxy.ts +87 -0
- package/src/examples/dustSweep.ts +66 -0
- package/src/examples/lifiSwap.ts +56 -0
- package/src/examples/lifiZap.ts +60 -0
- package/src/examples/rawCallWithArithmetic.ts +78 -0
- package/src/examples/readContractState.ts +77 -0
- package/src/examples/run.ts +168 -0
- package/src/examples/splitAndZap.ts +73 -0
- package/src/examples/splitWithArithmetic.ts +93 -0
- package/src/examples/swapAndZap.ts +72 -0
- package/src/examples/swapToRecipient.ts +109 -0
- package/src/examples/swapWithAllowRevert.ts +119 -0
- package/src/examples/swapWithBalanceCheck.ts +73 -0
- package/src/examples/swapWithOutputValidation.ts +125 -0
- package/src/examples/transferTokens.ts +123 -0
- package/src/examples/untypedOpWithTypedRef.ts +77 -0
- package/src/generated/config.generated.ts +174 -0
- package/src/generated/guards.generated.ts +14 -0
- package/src/generated/materialisers.generated.ts +44 -0
- package/src/generated/operations.generated.ts +568 -0
- package/src/generated/preconditions.generated.ts +43 -0
- package/src/index.ts +149 -0
- package/src/raw.ts +10 -0
- package/src/resources.ts +61 -0
- package/src/run/inputs.ts +117 -0
- package/src/sdk.ts +173 -0
- package/src/types.ts +101 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ComposeCompilePartialData,
|
|
3
|
+
ComposeCompileRequest,
|
|
4
|
+
ComposeCompileResult,
|
|
5
|
+
ComposeCompileSuccessData,
|
|
6
|
+
ComposeManifest,
|
|
7
|
+
} from '@lifi/compose-spec';
|
|
8
|
+
|
|
9
|
+
import type { GetZapPacksOptions, ZapPackOverview } from './discovery.js';
|
|
10
|
+
import { ComposeError, errorFromHttpResponse } from './errors.js';
|
|
11
|
+
|
|
12
|
+
// __SDK_VERSION__ is a compile-time constant injected by tsup (via `define` in tsup.config.ts)
|
|
13
|
+
// and by vitest (via `define` in vitest.config.ts). Both read the version from package.json
|
|
14
|
+
// at build/test time and replace this identifier with the literal string value.
|
|
15
|
+
// It is sent as the `x-lifi-composer-sdk` request header so the server can identify the caller.
|
|
16
|
+
// Falls back to 'dev' when running via tsx without tsup substitution (e.g. the example harness).
|
|
17
|
+
declare const __SDK_VERSION__: string;
|
|
18
|
+
|
|
19
|
+
const SDK_VERSION: string =
|
|
20
|
+
typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : 'dev';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for creating a low-level Compose API client.
|
|
24
|
+
*/
|
|
25
|
+
export interface ComposeClientOptions {
|
|
26
|
+
/** Base URL of the Compose API. */
|
|
27
|
+
readonly baseUrl: string;
|
|
28
|
+
/** Optional custom `fetch` implementation. Defaults to `globalThis.fetch`. */
|
|
29
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
30
|
+
/** Optional LI.FI API key. When set, sent as the `x-lifi-api-key` header on every request. */
|
|
31
|
+
readonly apiKey?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Low-level HTTP client for the Compose API.
|
|
36
|
+
*
|
|
37
|
+
* Handles request serialization, SDK version headers, and error mapping.
|
|
38
|
+
* Prefer using {@link ComposeSdk} for the full builder experience. Use this
|
|
39
|
+
* directly when you need to decouple request building from submission — e.g.
|
|
40
|
+
* build via `sdk.request()` then submit via `client.compile()` with custom
|
|
41
|
+
* retry logic or request inspection.
|
|
42
|
+
*/
|
|
43
|
+
export interface ComposeClient {
|
|
44
|
+
/**
|
|
45
|
+
* Fetches the server's operation manifest describing all supported operations,
|
|
46
|
+
* guards, materialisers, and preconditions.
|
|
47
|
+
* @returns The manifest document.
|
|
48
|
+
* @throws {@link ComposeError} on network, validation, or server errors.
|
|
49
|
+
*/
|
|
50
|
+
readonly getManifest: () => Promise<ComposeManifest>;
|
|
51
|
+
/**
|
|
52
|
+
* Submits a compile request and returns the result.
|
|
53
|
+
*
|
|
54
|
+
* When the caller passes `simulationPolicy: 'allow-revert'` and the transaction
|
|
55
|
+
* reverts in simulation, the server responds with HTTP 206 and the SDK returns a
|
|
56
|
+
* partial result (`status: 'partial'`) instead of throwing. The partial result
|
|
57
|
+
* includes the transaction (without `gasLimit`) and revert diagnostics.
|
|
58
|
+
*
|
|
59
|
+
* @param request - The full compile request including flow and run inputs.
|
|
60
|
+
* @returns A discriminated result: `status: 'success'` or `status: 'partial'`.
|
|
61
|
+
* @throws {@link ComposeError} on network, validation, or server errors.
|
|
62
|
+
*/
|
|
63
|
+
readonly compile: (
|
|
64
|
+
request: ComposeCompileRequest,
|
|
65
|
+
) => Promise<ComposeCompileResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Fetches the available routing edges grouped by protocol.
|
|
68
|
+
*
|
|
69
|
+
* The edge catalog is dynamic — it reflects the current state of the
|
|
70
|
+
* backend's routing snapshot (protocols, chains, token blacklists).
|
|
71
|
+
* Results are not cached by the SDK; callers should cache as appropriate.
|
|
72
|
+
*
|
|
73
|
+
* @param options - Optional filter to restrict results to specific protocols.
|
|
74
|
+
* @returns An array of {@link ZapPackOverview} objects, one per protocol.
|
|
75
|
+
* @throws {@link ComposeError} on network or server errors (503 when the
|
|
76
|
+
* routing catalog is not yet initialized).
|
|
77
|
+
*/
|
|
78
|
+
readonly getZapPacks: (
|
|
79
|
+
options?: GetZapPacksOptions,
|
|
80
|
+
) => Promise<readonly ZapPackOverview[]>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const bigintReplacer = (_key: string, value: unknown): unknown =>
|
|
84
|
+
typeof value === 'bigint' ? value.toString() : value;
|
|
85
|
+
|
|
86
|
+
const isNonNullObject = (v: unknown): v is Record<string, unknown> =>
|
|
87
|
+
typeof v === 'object' && v !== null;
|
|
88
|
+
|
|
89
|
+
const parseBody = async <T>(res: Response, url: string): Promise<T> => {
|
|
90
|
+
const body = await res.json().catch((_) => null);
|
|
91
|
+
if (!isNonNullObject(body) || !('data' in body)) {
|
|
92
|
+
throw new ComposeError('UNKNOWN_ERROR', 'Unexpected response format', {
|
|
93
|
+
url,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return body.data as T;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const parseCompileSuccessBody = async (
|
|
100
|
+
res: Response,
|
|
101
|
+
url: string,
|
|
102
|
+
): Promise<ComposeCompileResult> => {
|
|
103
|
+
const data = await parseBody<ComposeCompileSuccessData>(res, url);
|
|
104
|
+
return { ...data, status: 'success' as const };
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const parsePartialBody = async (
|
|
108
|
+
res: Response,
|
|
109
|
+
url: string,
|
|
110
|
+
): Promise<ComposeCompileResult> => {
|
|
111
|
+
const body = await res.json().catch((_) => null);
|
|
112
|
+
if (
|
|
113
|
+
!isNonNullObject(body) ||
|
|
114
|
+
!('data' in body) ||
|
|
115
|
+
!isNonNullObject(body.data) ||
|
|
116
|
+
!('error' in body) ||
|
|
117
|
+
!isNonNullObject(body.error)
|
|
118
|
+
) {
|
|
119
|
+
throw new ComposeError(
|
|
120
|
+
'UNKNOWN_ERROR',
|
|
121
|
+
'Unexpected partial response format',
|
|
122
|
+
{ url },
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const data = body.data as unknown as ComposeCompilePartialData;
|
|
126
|
+
const error = body.error as unknown as { kind: string; message: string };
|
|
127
|
+
return { ...data, status: 'partial' as const, error };
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Creates a low-level Compose API client.
|
|
132
|
+
*
|
|
133
|
+
* @param options - Client configuration including the API base URL.
|
|
134
|
+
* @returns A {@link ComposeClient} instance.
|
|
135
|
+
*/
|
|
136
|
+
export const createComposeClient = (
|
|
137
|
+
options: ComposeClientOptions,
|
|
138
|
+
): ComposeClient => {
|
|
139
|
+
if (!options.baseUrl || !/^https?:\/\//i.test(options.baseUrl)) {
|
|
140
|
+
throw new ComposeError(
|
|
141
|
+
'VALIDATION_ERROR',
|
|
142
|
+
`Invalid baseUrl: expected an HTTP(S) URL, got "${options.baseUrl}"`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
146
|
+
const base = options.baseUrl.replace(/\/$/, '');
|
|
147
|
+
|
|
148
|
+
const trimmedApiKey = options.apiKey?.trim() || undefined;
|
|
149
|
+
|
|
150
|
+
const baseHeaders: Record<string, string> = {
|
|
151
|
+
Accept: 'application/json',
|
|
152
|
+
'x-lifi-composer-sdk': SDK_VERSION,
|
|
153
|
+
...(trimmedApiKey ? { 'x-lifi-api-key': trimmedApiKey } : {}),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const getManifest = async (): Promise<ComposeManifest> => {
|
|
157
|
+
const url = `${base}/compose/manifest`;
|
|
158
|
+
let res: Response;
|
|
159
|
+
try {
|
|
160
|
+
res = await fetchFn(url, {
|
|
161
|
+
method: 'GET',
|
|
162
|
+
headers: { ...baseHeaders },
|
|
163
|
+
});
|
|
164
|
+
} catch (err) {
|
|
165
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
166
|
+
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
167
|
+
}
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
const body = await res.text();
|
|
170
|
+
throw errorFromHttpResponse(res.status, body, url);
|
|
171
|
+
}
|
|
172
|
+
return await parseBody<ComposeManifest>(res, url);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const compile = async (
|
|
176
|
+
request: ComposeCompileRequest,
|
|
177
|
+
): Promise<ComposeCompileResult> => {
|
|
178
|
+
const url = `${base}/compose`;
|
|
179
|
+
let res: Response;
|
|
180
|
+
try {
|
|
181
|
+
res = await fetchFn(url, {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
headers: { ...baseHeaders, 'Content-Type': 'application/json' },
|
|
184
|
+
body: JSON.stringify(request, bigintReplacer),
|
|
185
|
+
});
|
|
186
|
+
} catch (err) {
|
|
187
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
188
|
+
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
189
|
+
}
|
|
190
|
+
if (res.status === 206) {
|
|
191
|
+
return await parsePartialBody(res, url);
|
|
192
|
+
}
|
|
193
|
+
if (!res.ok) {
|
|
194
|
+
const body = await res.text();
|
|
195
|
+
throw errorFromHttpResponse(res.status, body, url);
|
|
196
|
+
}
|
|
197
|
+
return await parseCompileSuccessBody(res, url);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const getZapPacks = async (
|
|
201
|
+
options?: GetZapPacksOptions,
|
|
202
|
+
): Promise<readonly ZapPackOverview[]> => {
|
|
203
|
+
const params = new URLSearchParams();
|
|
204
|
+
if (options?.protocols != null) {
|
|
205
|
+
// Backend expects a single comma-separated value, not repeated keys.
|
|
206
|
+
const raw = options.protocols;
|
|
207
|
+
const list = typeof raw === 'string' ? raw : raw.join(',');
|
|
208
|
+
params.set('protocols', list);
|
|
209
|
+
}
|
|
210
|
+
const qs = params.toString();
|
|
211
|
+
const url = `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`;
|
|
212
|
+
let res: Response;
|
|
213
|
+
try {
|
|
214
|
+
res = await fetchFn(url, {
|
|
215
|
+
method: 'GET',
|
|
216
|
+
headers: { ...baseHeaders },
|
|
217
|
+
});
|
|
218
|
+
} catch (err) {
|
|
219
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
220
|
+
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
221
|
+
}
|
|
222
|
+
if (!res.ok) {
|
|
223
|
+
const body = await res.text();
|
|
224
|
+
throw errorFromHttpResponse(res.status, body, url);
|
|
225
|
+
}
|
|
226
|
+
return await parseBody<readonly ZapPackOverview[]>(res, url);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
return { getManifest, compile, getZapPacks };
|
|
230
|
+
};
|
package/src/discovery.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the Compose discovery API (`GET /compose/zap-packs`).
|
|
3
|
+
*
|
|
4
|
+
* These are SDK-facing presentation types — not part of the core compile
|
|
5
|
+
* contract in compose-spec. The backend response is the source of truth;
|
|
6
|
+
* the SDK provides typed access.
|
|
7
|
+
*
|
|
8
|
+
* The `type` field on edges is a plain `string` rather than a strict union
|
|
9
|
+
* so the SDK remains forward-compatible when the backend introduces new
|
|
10
|
+
* edge types. Known values at time of writing:
|
|
11
|
+
* `"enter-position"`, `"exit-position"`, `"wrap"`, `"unwrap"`, `"mint"`, `"burn"`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A single routing edge representing a supported token-to-token path
|
|
16
|
+
* through a DeFi protocol.
|
|
17
|
+
*/
|
|
18
|
+
export interface ZapPackEdge {
|
|
19
|
+
/** Edge type (e.g. `"enter-position"`, `"exit-position"`, `"wrap"`). */
|
|
20
|
+
readonly type: string;
|
|
21
|
+
/** Input token. */
|
|
22
|
+
readonly in: { readonly address: string; readonly chainId: number };
|
|
23
|
+
/** Output token. */
|
|
24
|
+
readonly out: { readonly address: string; readonly chainId: number };
|
|
25
|
+
/** Minimum input amount accepted (stringified integer), if constrained. */
|
|
26
|
+
readonly inAmountMin?: string;
|
|
27
|
+
/** Maximum input amount accepted (stringified integer), if constrained. */
|
|
28
|
+
readonly inAmountMax?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* All routing edges for a single protocol.
|
|
33
|
+
*/
|
|
34
|
+
export interface ZapPackOverview {
|
|
35
|
+
/** Protocol identifier (e.g. `"aave"`, `"morpho"`). */
|
|
36
|
+
readonly protocol: string;
|
|
37
|
+
/** Available routing edges for this protocol. */
|
|
38
|
+
readonly edges: readonly ZapPackEdge[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Options for {@link ComposeClient.getZapPacks}.
|
|
43
|
+
*/
|
|
44
|
+
export interface GetZapPacksOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Filter to specific protocols. Pass a single string or an array.
|
|
47
|
+
* Only edges belonging to the listed protocols are returned.
|
|
48
|
+
* Omit to fetch all available protocols.
|
|
49
|
+
*/
|
|
50
|
+
readonly protocols?: string | readonly string[];
|
|
51
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { ComposeErrorKind, SimulationRevert } from '@lifi/compose-spec';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Machine-readable error codes returned by the SDK.
|
|
5
|
+
*
|
|
6
|
+
* - `NETWORK_ERROR` — The HTTP request failed (DNS, timeout, connection refused).
|
|
7
|
+
* - `VALIDATION_ERROR` — The server rejected the request (HTTP 400/422).
|
|
8
|
+
* - `UNAUTHENTICATED` — The request lacks valid authentication credentials (HTTP 401).
|
|
9
|
+
* - `FORBIDDEN` — The server understood the request but refuses to authorise it (HTTP 403).
|
|
10
|
+
* - `SERVER_ERROR` — The server returned a 5xx status.
|
|
11
|
+
* - `RATE_LIMITED` — The server returned HTTP 429.
|
|
12
|
+
* - `NOT_FOUND` — The requested resource does not exist (HTTP 404).
|
|
13
|
+
* - `UNKNOWN_ERROR` — An unexpected error that doesn't fit other categories.
|
|
14
|
+
*/
|
|
15
|
+
export type ComposeErrorCode =
|
|
16
|
+
| 'NETWORK_ERROR'
|
|
17
|
+
| 'VALIDATION_ERROR'
|
|
18
|
+
| 'UNAUTHENTICATED'
|
|
19
|
+
| 'FORBIDDEN'
|
|
20
|
+
| 'SERVER_ERROR'
|
|
21
|
+
| 'RATE_LIMITED'
|
|
22
|
+
| 'NOT_FOUND'
|
|
23
|
+
| 'UNKNOWN_ERROR';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Error class for all failures originating from the Compose SDK or API.
|
|
27
|
+
*
|
|
28
|
+
* Includes structured metadata beyond the error message to support
|
|
29
|
+
* programmatic error handling.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* try {
|
|
34
|
+
* await builder.compile(run);
|
|
35
|
+
* } catch (e) {
|
|
36
|
+
* if (isComposeError(e) && e.code === 'VALIDATION_ERROR') {
|
|
37
|
+
* console.error('Invalid request:', e.message, e.path);
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export class ComposeError extends Error {
|
|
43
|
+
override readonly name = 'ComposeError';
|
|
44
|
+
/** Machine-readable error category. */
|
|
45
|
+
readonly code: ComposeErrorCode;
|
|
46
|
+
/** HTTP status code, when the error originated from an HTTP response. */
|
|
47
|
+
readonly status?: number;
|
|
48
|
+
/** The request URL that produced the error. */
|
|
49
|
+
readonly url?: string;
|
|
50
|
+
/** Server-provided error kind for finer-grained classification. */
|
|
51
|
+
readonly kind?: ComposeErrorKind;
|
|
52
|
+
/** JSON-pointer path to the field that caused a validation error. */
|
|
53
|
+
readonly path?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Simulation revert diagnostics attached to `simulation_revert` errors.
|
|
56
|
+
* Contains the raw error bytes and decoded error candidates when the
|
|
57
|
+
* backend can parse the revert reason.
|
|
58
|
+
*/
|
|
59
|
+
readonly details?: SimulationRevert;
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
code: ComposeErrorCode,
|
|
63
|
+
message: string,
|
|
64
|
+
options?: {
|
|
65
|
+
status?: number;
|
|
66
|
+
url?: string;
|
|
67
|
+
cause?: unknown;
|
|
68
|
+
kind?: ComposeErrorKind;
|
|
69
|
+
path?: string;
|
|
70
|
+
details?: SimulationRevert;
|
|
71
|
+
},
|
|
72
|
+
) {
|
|
73
|
+
super(message, { cause: options?.cause });
|
|
74
|
+
this.code = code;
|
|
75
|
+
this.status = options?.status;
|
|
76
|
+
this.url = options?.url;
|
|
77
|
+
this.kind = options?.kind;
|
|
78
|
+
this.path = options?.path;
|
|
79
|
+
this.details = options?.details;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Type guard that narrows an unknown error to {@link ComposeError}.
|
|
85
|
+
* @param e - The value to check.
|
|
86
|
+
* @returns `true` if `e` is an instance of `ComposeError`.
|
|
87
|
+
*/
|
|
88
|
+
export const isComposeError = (e: unknown): e is ComposeError =>
|
|
89
|
+
e instanceof ComposeError ||
|
|
90
|
+
(e instanceof Error && e.name === 'ComposeError' && 'code' in e);
|
|
91
|
+
|
|
92
|
+
const STATUS_TO_CODE: ReadonlyMap<number, ComposeErrorCode> = new Map<
|
|
93
|
+
number,
|
|
94
|
+
ComposeErrorCode
|
|
95
|
+
>([
|
|
96
|
+
[400, 'VALIDATION_ERROR'],
|
|
97
|
+
[401, 'UNAUTHENTICATED'],
|
|
98
|
+
[403, 'FORBIDDEN'],
|
|
99
|
+
[404, 'NOT_FOUND'],
|
|
100
|
+
[422, 'VALIDATION_ERROR'],
|
|
101
|
+
[429, 'RATE_LIMITED'],
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
interface ServerErrorBody {
|
|
105
|
+
readonly error?: {
|
|
106
|
+
readonly kind?: string;
|
|
107
|
+
readonly message?: string;
|
|
108
|
+
readonly path?: string;
|
|
109
|
+
readonly details?: SimulationRevert;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const tryParseErrorBody = (body: string): ServerErrorBody | null => {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(body) as ServerErrorBody;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Constructs a {@link ComposeError} from an HTTP error response, extracting
|
|
123
|
+
* structured error details from the response body when available.
|
|
124
|
+
*
|
|
125
|
+
* @param status - The HTTP status code.
|
|
126
|
+
* @param body - The raw response body text.
|
|
127
|
+
* @param url - The request URL that produced the error.
|
|
128
|
+
* @returns A `ComposeError` with the appropriate error code and metadata.
|
|
129
|
+
*/
|
|
130
|
+
export const errorFromHttpResponse = (
|
|
131
|
+
status: number,
|
|
132
|
+
body: string,
|
|
133
|
+
url: string,
|
|
134
|
+
): ComposeError => {
|
|
135
|
+
const parsed = tryParseErrorBody(body);
|
|
136
|
+
const serverError = parsed?.error;
|
|
137
|
+
|
|
138
|
+
return new ComposeError(
|
|
139
|
+
STATUS_TO_CODE.get(status) ??
|
|
140
|
+
(status >= 500 ? 'SERVER_ERROR' : 'UNKNOWN_ERROR'),
|
|
141
|
+
(serverError?.message ?? body) || `HTTP ${status}`,
|
|
142
|
+
{
|
|
143
|
+
status,
|
|
144
|
+
url,
|
|
145
|
+
kind: serverError?.kind as ComposeErrorKind | undefined,
|
|
146
|
+
path: serverError?.path,
|
|
147
|
+
details: serverError?.details,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
|
|
2
|
+
|
|
3
|
+
import { createComposeSdk, materialisers, resources } from '../index.js';
|
|
4
|
+
import type { Address } from '../types.js';
|
|
5
|
+
|
|
6
|
+
import { BASE_URL } from './config.js';
|
|
7
|
+
|
|
8
|
+
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
|
|
9
|
+
// Example ERC-4626 vault address (Steakhouse USDC on Ethereum mainnet)
|
|
10
|
+
const VAULT = '0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB';
|
|
11
|
+
|
|
12
|
+
export interface ApproveAndDepositInput {
|
|
13
|
+
readonly owner: Address;
|
|
14
|
+
readonly amount: `${bigint}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Approve a vault to spend USDC, deposit into it, and track the resulting
|
|
19
|
+
* shares as a resource.
|
|
20
|
+
*
|
|
21
|
+
* Demonstrates:
|
|
22
|
+
* - core.approve to grant ERC20 allowance to the vault
|
|
23
|
+
* - core.call to invoke the vault's deposit function
|
|
24
|
+
* - core.asResource to graduate the raw return value to a tracked resource
|
|
25
|
+
*/
|
|
26
|
+
export const buildApproveAndDeposit = ({
|
|
27
|
+
owner,
|
|
28
|
+
amount,
|
|
29
|
+
}: ApproveAndDepositInput): {
|
|
30
|
+
flow: Flow;
|
|
31
|
+
request: ComposeCompileRequest;
|
|
32
|
+
} => {
|
|
33
|
+
const sdk = createComposeSdk({ baseUrl: BASE_URL });
|
|
34
|
+
|
|
35
|
+
const builder = sdk.flow(1, {
|
|
36
|
+
name: 'approve-and-deposit',
|
|
37
|
+
inputs: {
|
|
38
|
+
amountIn: resources.erc20(USDC, 1),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Approve the vault to spend our USDC.
|
|
43
|
+
builder.core.approve('approve-vault', {
|
|
44
|
+
bind: { amount: builder.inputs.amountIn },
|
|
45
|
+
config: { spender: VAULT },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Call vault.deposit(uint256 assets, address receiver) returns (uint256).
|
|
49
|
+
// The input resource (USDC) is consumed by the call.
|
|
50
|
+
// The `returns (uint256)` clause gives typed access to `.result` (shares minted).
|
|
51
|
+
const depositResult = builder.core.call('deposit', {
|
|
52
|
+
resource: builder.inputs.amountIn,
|
|
53
|
+
bind: {
|
|
54
|
+
assets: builder.inputs.amountIn,
|
|
55
|
+
receiver: builder.context.executionAddress,
|
|
56
|
+
},
|
|
57
|
+
config: {
|
|
58
|
+
target: VAULT,
|
|
59
|
+
functionSignature:
|
|
60
|
+
'function deposit(uint256 assets, address receiver) returns (uint256)',
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Graduate the raw shares handle to a tracked resource so downstream
|
|
65
|
+
// nodes can use it with slippage tracking and linearity checks.
|
|
66
|
+
builder.core.asResource('shares', {
|
|
67
|
+
bind: { handle: depositResult.result },
|
|
68
|
+
config: {
|
|
69
|
+
resource: resources.erc20(VAULT, 1),
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const flow = builder.build();
|
|
74
|
+
|
|
75
|
+
const request = sdk.request(flow, {
|
|
76
|
+
signer: owner,
|
|
77
|
+
inputs: {
|
|
78
|
+
amountIn: materialisers.directDeposit({ amount }),
|
|
79
|
+
},
|
|
80
|
+
sweepTo: builder.context.sender,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return { flow, request };
|
|
84
|
+
};
|