@lifi/composer-sdk 0.4.0 → 0.4.1
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 +95 -5
- package/dist/authoring/raw.cjs +3 -1
- package/dist/authoring/raw.cjs.map +1 -1
- package/dist/authoring/raw.js +3 -1
- package/dist/authoring/raw.js.map +1 -1
- package/dist/client.cjs +26 -24
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +31 -1
- package/dist/client.d.ts +31 -1
- package/dist/client.js +26 -24
- package/dist/client.js.map +1 -1
- package/dist/discovery.cjs.map +1 -1
- package/dist/discovery.d.cts +13 -0
- package/dist/discovery.d.ts +13 -0
- package/dist/generated/config.generated.cjs.map +1 -1
- package/dist/generated/config.generated.d.cts +15 -3
- package/dist/generated/config.generated.d.ts +15 -3
- package/dist/generated/operations.generated.cjs +3 -0
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +17 -2
- package/dist/generated/operations.generated.d.ts +17 -2
- package/dist/generated/operations.generated.js +3 -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 +22 -3
- package/dist/index.d.ts +22 -3
- package/dist/index.js +4 -1
- package/dist/index.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/sdk.cjs +1 -0
- package/dist/sdk.cjs.map +1 -1
- package/dist/sdk.d.cts +14 -1
- package/dist/sdk.d.ts +14 -1
- package/dist/sdk.js +1 -0
- package/dist/sdk.js.map +1 -1
- package/package.json +5 -5
- package/src/authoring/raw.ts +3 -2
- package/src/client.ts +75 -23
- package/src/discovery.ts +13 -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 +30 -5
- package/src/generated/config.generated.ts +21 -3
- package/src/generated/operations.generated.ts +30 -8
- package/src/index.ts +23 -0
- package/src/routeAmount.ts +61 -0
- package/src/sdk.ts +17 -0
package/README.md
CHANGED
|
@@ -122,9 +122,11 @@ if (result.status === 'success') {
|
|
|
122
122
|
## API surface
|
|
123
123
|
|
|
124
124
|
**SDK factory**
|
|
125
|
+
|
|
125
126
|
- `createComposeSdk({ baseUrl, apiKey, fetch? })` — creates the SDK instance
|
|
126
127
|
|
|
127
128
|
**Flow building**
|
|
129
|
+
|
|
128
130
|
- `sdk.flow(chainId, options)` — creates a `FlowBuilder`
|
|
129
131
|
- `builder.<namespace>.<operation>(id, { bind, config })` — adds an operation, returns typed `OutputHandle<T>` per port
|
|
130
132
|
- `builder.untypedOp(id, op, args)` — escape hatch for operations not in the manifest (returns `void`; use `raw.ref<T>()` to reference its outputs)
|
|
@@ -132,12 +134,15 @@ if (result.status === 'success') {
|
|
|
132
134
|
- `sdk.request(flow, { signer, inputs, preconditions, sweepTo, ... })` — builds a compile request
|
|
133
135
|
|
|
134
136
|
**HTTP client**
|
|
137
|
+
|
|
135
138
|
- `sdk.client.compile(request)` — sends the flow to the backend and returns a `ComposeCompileResult` (discriminated union: `status: 'success'` or `status: 'partial'`)
|
|
136
139
|
- `sdk.client.getManifest()` — fetches the operation manifest
|
|
137
|
-
- `sdk.client.getZapPacks(options?)` — fetches the available routing edges grouped by protocol, returning `ZapPackOverview[]`. The catalog is dynamic
|
|
140
|
+
- `sdk.client.getZapPacks(options?)` — fetches the available routing edges grouped by protocol, returning `ZapPackOverview[]`. The catalog is dynamic and is not cached by the SDK. Filter through `GetZapPacksOptions`. An edge with `recipient: 'required'` requires the zap's `recipient` binding, delivers the output externally, and produces an output that cannot be chained or swept. An edge with `availability: 'future'` produces an output that settles only after the initiating transaction; route it via `lifi.zapAsync` with a bound `recipient`. Availability governs custody only — a future output still reports `simulated.amountOut` (an in-transaction estimate; the delivered amount arrives after settlement), with `simulated.amountOutMin` present only when a minimum source exists.
|
|
138
141
|
- `sdk.client.simulate(request)` / `sdk.simulate(request)` — simulates a raw, pre-encoded transaction and returns a `SimulateResult` (discriminated union: `status: 'ok' | 'revert' | 'error'`). See [Simulating a raw transaction](#simulating-a-raw-transaction).
|
|
142
|
+
- `sdk.client.route(request)` / `sdk.route(request)` — compiles a single from/to token pair on one chain via `POST /compose/route`, returning the same `ComposeCompileResult` as `compile()`. See [Routing a token pair](#routing-a-token-pair).
|
|
139
143
|
|
|
140
144
|
**Helpers**
|
|
145
|
+
|
|
141
146
|
- `resources.erc20(address, chainId)` / `resources.native(chainId)` — resource constructors
|
|
142
147
|
- `guards.*` — guard factories (e.g., slippage)
|
|
143
148
|
- `materialisers.*` — materialiser factories (directDeposit, balanceOf, call)
|
|
@@ -145,6 +150,87 @@ if (result.status === 'success') {
|
|
|
145
150
|
- `raw.ref<T>(path)` — create a typed `$ref` pointer for use in bind slots (escape hatch for `untypedOp` outputs)
|
|
146
151
|
- `raw.guard(kind, config?)` / `raw.materialiser(kind, config?)` — low-level factories for guards and materialisers
|
|
147
152
|
- `buildSimulateRequest({ result, chainId, signer, trackedBalances, requirements?, block?, value? })` — assembles a `SimulateRequest` from a compile result (pure, no I/O)
|
|
153
|
+
- `routeAmount.exact(amount)` / `routeAmount.all(simAmount)` — amount constructors for `sdk.route(...)`
|
|
154
|
+
|
|
155
|
+
## Routing a token pair
|
|
156
|
+
|
|
157
|
+
Authoring a flow is the general path: you decide the operations and how they chain. `sdk.route(...)` is the shortcut for the case where the backend can decide for you — you hand it a from/to token pair, an amount, and a signer, and it returns the same submit-ready `ComposeCompileResult` that `compile()` returns.
|
|
158
|
+
|
|
159
|
+
As of today the backend authors a single zap step for that pair: one edge of its routing catalog — entering or exiting a protocol position, a wrap or unwrap, a mint or burn. It is not a swap aggregator, so a pair with no catalog edge (WETH to USDC, for instance) comes back as `no_route_error`. The endpoint is meant to author more complex flows in future releases, so read the single-step limit as where the server is today rather than as the boundary of the API.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { createComposeSdk, routeAmount } from '@lifi/composer-sdk';
|
|
163
|
+
|
|
164
|
+
const sdk = createComposeSdk({
|
|
165
|
+
baseUrl: 'https://composer.li.quest',
|
|
166
|
+
apiKey: process.env.LIFI_API_KEY!,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const result = await sdk.route({
|
|
170
|
+
chainId: 1,
|
|
171
|
+
fromToken: '0x0000000000000000000000000000000000000000', // native ETH
|
|
172
|
+
toToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH — a `wrap` edge
|
|
173
|
+
amount: routeAmount.exact(100_000_000_000_000_000n), // 0.1 ETH
|
|
174
|
+
signer: '0xYourAddress...',
|
|
175
|
+
slippageBps: 100,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (result.status === 'success') {
|
|
179
|
+
console.log(result.transactionRequest); // to, data, value, gasLimit
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Discovering routable pairs
|
|
184
|
+
|
|
185
|
+
The catalog is dynamic — protocols, chains, and token blacklists move — so discover pairs instead of hardcoding them:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
const packs = await sdk.client.getZapPacks({ protocols: 'aave' });
|
|
189
|
+
|
|
190
|
+
for (const pack of packs) {
|
|
191
|
+
for (const edge of pack.edges) {
|
|
192
|
+
console.log(pack.protocol, edge.type, edge.in.address, '->', edge.out.address);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Every edge is one routable pair: `edge.in` and `edge.out` are the `fromToken` and `toToken` of a `route` call on `edge.in.chainId`, and `inAmountMin` / `inAmountMax` bound the amount when the protocol constrains it. Two edge fields change how you call `route`:
|
|
198
|
+
|
|
199
|
+
- **`recipient: 'required'`** — the protocol mints to a named receiver, so set `sweepTo` when the output should not land with the signer.
|
|
200
|
+
- **`availability: 'future'`** — the output only exists once the transaction settles. `route` cannot compile these; they need `lifi.zapAsync` on the authoring path.
|
|
201
|
+
|
|
202
|
+
### Amounts
|
|
203
|
+
|
|
204
|
+
The `amount` field is a discriminated union with two variants, built by `routeAmount`:
|
|
205
|
+
|
|
206
|
+
- **`routeAmount.exact(amount)`** — spend exactly `amount` of `fromToken`, in its smallest unit. Use this when you know the figure.
|
|
207
|
+
- **`routeAmount.all(simAmount)`** — spend the signer's whole `fromToken` balance, resolved on-chain at execution time. `simAmount` is only an estimate: the quote, the price impact, and the returned approvals are computed against it, but the amount actually moved is the real balance when the transaction lands. Pass your best estimate of the balance — a wildly wrong figure produces a poor quote. Not supported for a native `fromToken`: a gas coin arrives via `msg.value`, so there is no balance to sweep, and the server rejects the request.
|
|
208
|
+
|
|
209
|
+
Both accept a `bigint` or a decimal string. `bigint` values are serialised for you.
|
|
210
|
+
|
|
211
|
+
### Options
|
|
212
|
+
|
|
213
|
+
`sdk.route(...)` shares its optional fields with the authoring path, and the server applies the same defaults. Omit a field to take the default; the SDK sends only what you set.
|
|
214
|
+
|
|
215
|
+
- **`slippageBps`** — tolerance in basis points. Defaults to `100` (1%).
|
|
216
|
+
- **`maxPriceImpactBps`** — reject the route if the priced impact exceeds this. No cap by default.
|
|
217
|
+
- **`integratorFeeBps`** — your fee, in basis points, taken from the input amount before the route executes. Requires an integration-scoped `apiKey`; a non-zero value without one is rejected.
|
|
218
|
+
- **`referrer`** — attribution string passed to the routing provider.
|
|
219
|
+
- **`sweepTo`** — address that receives the output and any leftover dust. Accepts a literal address or `{ $ref: 'context.sender' }`. Defaults to the signer.
|
|
220
|
+
- **`checkOnChainAllowances`** — when `true`, the server reads current allowances and omits approvals that are already sufficient. Defaults to `false`.
|
|
221
|
+
- **`simulationPolicy`** — `'strict'` (default) or `'allow-revert'`, exactly as on the authoring path. See [Simulation policy and partial results](#simulation-policy-and-partial-results).
|
|
222
|
+
- **`simulateUserProgram`** — when `true`, additionally simulates the final user-facing transaction after the structured simulation succeeds. Defaults to `false`.
|
|
223
|
+
|
|
224
|
+
### When to author a flow instead
|
|
225
|
+
|
|
226
|
+
`route` gives you one server-authored step. Reach for `sdk.flow(...)` when you need anything it does not express: several operations chained together, splitting one input across several destinations, guards on intermediate outputs, explicit preconditions, arithmetic, funding from proxy-held balances, or an asynchronous position (`availability: 'future'`) as the destination. Swapping WETH to USDC and then depositing the USDC into Aave is a flow; depositing USDC straight into Aave is a route.
|
|
227
|
+
|
|
228
|
+
### Errors
|
|
229
|
+
|
|
230
|
+
`route` maps failures the same way `compile` does — see [Error handling](#error-handling). Two are specific to this path:
|
|
231
|
+
|
|
232
|
+
- **`NOT_FOUND`** with `kind: 'no_route_error'` — the backend has no routing edge for the pair on that chain. `sdk.client.getZapPacks()` reports which edges exist.
|
|
233
|
+
- **`VALIDATION_ERROR`** — the pair is routable but the request is rejected, for example a price impact above `maxPriceImpactBps`, or a simulation revert under the default `'strict'` policy.
|
|
148
234
|
|
|
149
235
|
## Simulation policy and partial results
|
|
150
236
|
|
|
@@ -164,12 +250,12 @@ if (result.status === 'success') {
|
|
|
164
250
|
} else {
|
|
165
251
|
// result.status === 'partial'
|
|
166
252
|
// Simulation reverted, but a transaction is still available (without gasLimit).
|
|
167
|
-
console.log(result.error.kind);
|
|
253
|
+
console.log(result.error.kind); // 'simulation_revert'
|
|
168
254
|
console.log(result.error.message); // human-readable revert description
|
|
169
255
|
|
|
170
256
|
// Revert diagnostics
|
|
171
257
|
const revert = result.simulationRevert;
|
|
172
|
-
console.log(revert.code);
|
|
258
|
+
console.log(revert.code); // e.g. 3
|
|
173
259
|
console.log(revert.rawErrorBytes); // raw ABI-encoded error
|
|
174
260
|
|
|
175
261
|
// Decoded error candidates (when available)
|
|
@@ -186,6 +272,7 @@ if (result.status === 'success') {
|
|
|
186
272
|
```
|
|
187
273
|
|
|
188
274
|
The `simulationPolicy` field accepts two values:
|
|
275
|
+
|
|
189
276
|
- `'strict'` (default) — revert causes a thrown `ComposeError` with code `VALIDATION_ERROR`
|
|
190
277
|
- `'allow-revert'` — revert returns a partial result with `status: 'partial'`
|
|
191
278
|
|
|
@@ -201,7 +288,7 @@ const result = await builder.compile({
|
|
|
201
288
|
|
|
202
289
|
## Simulating a raw transaction
|
|
203
290
|
|
|
204
|
-
`sdk.client.simulate(...)` (and the `sdk.simulate(...)` pass-through) answer a question the compile pipeline does not:
|
|
291
|
+
`sdk.client.simulate(...)` (and the `sdk.simulate(...)` pass-through) answer a question the compile pipeline does not: _if I send this exact transaction, how do specific token balances change and how much gas does it burn?_ It takes a raw, pre-encoded transaction (a `to`, hex `data`, optional native `value`), funds a sender, runs it in one `eth_call`, and reports the watched balances before/after, their signed deltas, and the inner-call gas.
|
|
205
292
|
|
|
206
293
|
```ts
|
|
207
294
|
import { createComposeSdk } from '@lifi/composer-sdk';
|
|
@@ -255,7 +342,7 @@ switch (result.status) {
|
|
|
255
342
|
|
|
256
343
|
`requirements` is the funding-instruction union (`Erc20Balance`, `NativeBalance`, `Erc20Allowance`); use the zero address as a `trackedBalances` `token` to watch native balance. Amount fields accept `bigint` (serialised to decimal strings) or strings. The caps `SIMULATE_MAX_TRACKED_BALANCES` and `SIMULATE_MAX_REQUIREMENTS` (both `40`) are exported for reference.
|
|
257
344
|
|
|
258
|
-
Unlike `compile`, a `revert` is returned (not thrown): a revert is a
|
|
345
|
+
Unlike `compile`, a `revert` is returned (not thrown): a revert is a _successful simulation_ whose execution reverted. Only transport failures and HTTP 400/401/403/404/429/5xx throw `ComposeError`.
|
|
259
346
|
|
|
260
347
|
To simulate a transaction you just compiled, `buildSimulateRequest(...)` assembles the request without re-typing the transaction fields. `chainId` and `signer` are required (a compile result carries neither), and `trackedBalances`/`requirements` cannot be inferred from a flow:
|
|
261
348
|
|
|
@@ -325,6 +412,7 @@ The `src/examples/` directory contains complete working examples:
|
|
|
325
412
|
|
|
326
413
|
- **lifiSwap** — Single token swap (WETH to USDC)
|
|
327
414
|
- **lifiZap** — Swap into a DeFi position
|
|
415
|
+
- **lifiZapAsync** — Zap into a position whose output settles after the initiating transaction (future availability)
|
|
328
416
|
- **swapAndZap** — Multi-step: swap then deposit
|
|
329
417
|
- **splitAndZap** — Split a resource and zap each portion into a different vault
|
|
330
418
|
- **splitWithArithmetic** — Split then verify with add/subtract/assertEqual assertions
|
|
@@ -347,6 +435,7 @@ The `src/examples/` directory contains complete working examples:
|
|
|
347
435
|
- **aaveClaimRewards** — Claim accrued Aave rewards and forward the claimed amount to a recipient
|
|
348
436
|
- **aaveSetEMode** — Switch the proxy's Aave v3 eMode category
|
|
349
437
|
- **untypedOpWithTypedRef** — Insert an untyped operation node via `untypedOp`, then bridge its output into typed operations using `raw.ref<T>()`
|
|
438
|
+
- **routeTokenPair** — Compile a from/to token pair via `sdk.route(...)`, with both the `exact` and `all` amount variants
|
|
350
439
|
|
|
351
440
|
## Staging channel
|
|
352
441
|
|
|
@@ -370,6 +459,7 @@ const sdk = createComposeSdk({
|
|
|
370
459
|
Calling a staged operation against a backend that does not have it enabled fails at runtime as a service-level error; the typed surface being present does not guarantee backend availability.
|
|
371
460
|
|
|
372
461
|
The channel and the backend URL are independent:
|
|
462
|
+
|
|
373
463
|
- The `staging` **channel** is durable — it is a permanent property of `main`, and which operations it carries rotates over time as ops graduate to `latest`.
|
|
374
464
|
- The `baseUrl` is **per-environment and disposable** — it is a runtime argument, not a build-time property, so the same staging build can target whichever backend currently has the ops enabled.
|
|
375
465
|
|
package/dist/authoring/raw.cjs
CHANGED
|
@@ -22,7 +22,9 @@ __export(raw_exports, {
|
|
|
22
22
|
ref: () => ref
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(raw_exports);
|
|
25
|
-
const ref = (path) => ({
|
|
25
|
+
const ref = (path) => ({
|
|
26
|
+
$ref: path
|
|
27
|
+
});
|
|
26
28
|
const rawGuard = (kind, config = {}) => {
|
|
27
29
|
if ("kind" in config) {
|
|
28
30
|
throw new Error(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedRef } from '../types.js';\n\n/**\n * Creates a typed `$ref` pointer for use in bind slots.\n *\n * This is an **escape hatch** for referencing values that the typed builder\n * API doesn't cover — for example, outputs of an `untypedOp` node or custom\n * context paths. The caller is responsible for choosing the correct type\n * parameter; no runtime validation occurs.\n *\n * Prefer typed handles (`OutputHandle`, `InputHandle`) and `builder.context`\n * whenever possible. Use `ref` only when you need to construct a `$ref` path\n * manually and want it accepted by a `Bindable<T>` slot.\n *\n * @typeParam T - The output kind this ref represents (e.g. `'address'`, `'uint256'`, `'resource'`).\n * @param path - The `$ref` path (e.g. `\"context.sender\"`, `\"myNode.result\"`).\n * @returns A {@link TypedRef} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * // Reference an untypedOp output in a typed operation:\n * builder.untypedOp('custom', 'some.op', {\n * bind: { x: { $ref: 'input.token' } },\n * config: {},\n * });\n * builder.core.add('sum', {\n * bind: {\n * a: raw.ref<'uint256'>('custom.result'),\n * b: someTypedHandle,\n * },\n * });\n * ```\n */\nexport const ref = <T extends OutputKind>(path: string): TypedRef<T>
|
|
1
|
+
{"version":3,"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedRef } from '../types.js';\n\n/**\n * Creates a typed `$ref` pointer for use in bind slots.\n *\n * This is an **escape hatch** for referencing values that the typed builder\n * API doesn't cover — for example, outputs of an `untypedOp` node or custom\n * context paths. The caller is responsible for choosing the correct type\n * parameter; no runtime validation occurs.\n *\n * Prefer typed handles (`OutputHandle`, `InputHandle`) and `builder.context`\n * whenever possible. Use `ref` only when you need to construct a `$ref` path\n * manually and want it accepted by a `Bindable<T>` slot.\n *\n * @typeParam T - The output kind this ref represents (e.g. `'address'`, `'uint256'`, `'resource'`).\n * @param path - The `$ref` path (e.g. `\"context.sender\"`, `\"myNode.result\"`).\n * @returns A {@link TypedRef} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * // Reference an untypedOp output in a typed operation:\n * builder.untypedOp('custom', 'some.op', {\n * bind: { x: { $ref: 'input.token' } },\n * config: {},\n * });\n * builder.core.add('sum', {\n * bind: {\n * a: raw.ref<'uint256'>('custom.result'),\n * b: someTypedHandle,\n * },\n * });\n * ```\n */\nexport const ref = <T extends OutputKind>(path: string): TypedRef<T> => ({\n $ref: path,\n});\n\n/**\n * Creates a raw guard object for use in the `guards` array of an operation call.\n *\n * @param kind - The guard type (e.g. `\"slippage\"`).\n * @param config - Guard-specific configuration (e.g. `{ toleranceBps: 300 }`).\n * @returns An {@link AppliedGuard} object.\n */\nexport const rawGuard = (\n kind: string,\n config: Record<string, unknown> = {},\n): AppliedGuard => {\n if ('kind' in config) {\n throw new Error(\n `rawGuard: 'kind' must not appear in config (got \"${String(\n config.kind,\n )}\"). Pass it as the first argument instead.`,\n );\n }\n return { ...config, kind };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqCO,MAAM,MAAM,CAAuB,UAA+B;AAAA,EACvE,MAAM;AACR;AASO,MAAM,WAAW,CACtB,MACA,SAAkC,CAAC,MAClB;AACjB,MAAI,UAAU,QAAQ;AACpB,UAAM,IAAI;AAAA,MACR,oDAAoD;AAAA,QAClD,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,EAAE,GAAG,QAAQ,KAAK;AAC3B;","names":[]}
|
package/dist/authoring/raw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedRef } from '../types.js';\n\n/**\n * Creates a typed `$ref` pointer for use in bind slots.\n *\n * This is an **escape hatch** for referencing values that the typed builder\n * API doesn't cover — for example, outputs of an `untypedOp` node or custom\n * context paths. The caller is responsible for choosing the correct type\n * parameter; no runtime validation occurs.\n *\n * Prefer typed handles (`OutputHandle`, `InputHandle`) and `builder.context`\n * whenever possible. Use `ref` only when you need to construct a `$ref` path\n * manually and want it accepted by a `Bindable<T>` slot.\n *\n * @typeParam T - The output kind this ref represents (e.g. `'address'`, `'uint256'`, `'resource'`).\n * @param path - The `$ref` path (e.g. `\"context.sender\"`, `\"myNode.result\"`).\n * @returns A {@link TypedRef} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * // Reference an untypedOp output in a typed operation:\n * builder.untypedOp('custom', 'some.op', {\n * bind: { x: { $ref: 'input.token' } },\n * config: {},\n * });\n * builder.core.add('sum', {\n * bind: {\n * a: raw.ref<'uint256'>('custom.result'),\n * b: someTypedHandle,\n * },\n * });\n * ```\n */\nexport const ref = <T extends OutputKind>(path: string): TypedRef<T>
|
|
1
|
+
{"version":3,"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedRef } from '../types.js';\n\n/**\n * Creates a typed `$ref` pointer for use in bind slots.\n *\n * This is an **escape hatch** for referencing values that the typed builder\n * API doesn't cover — for example, outputs of an `untypedOp` node or custom\n * context paths. The caller is responsible for choosing the correct type\n * parameter; no runtime validation occurs.\n *\n * Prefer typed handles (`OutputHandle`, `InputHandle`) and `builder.context`\n * whenever possible. Use `ref` only when you need to construct a `$ref` path\n * manually and want it accepted by a `Bindable<T>` slot.\n *\n * @typeParam T - The output kind this ref represents (e.g. `'address'`, `'uint256'`, `'resource'`).\n * @param path - The `$ref` path (e.g. `\"context.sender\"`, `\"myNode.result\"`).\n * @returns A {@link TypedRef} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * // Reference an untypedOp output in a typed operation:\n * builder.untypedOp('custom', 'some.op', {\n * bind: { x: { $ref: 'input.token' } },\n * config: {},\n * });\n * builder.core.add('sum', {\n * bind: {\n * a: raw.ref<'uint256'>('custom.result'),\n * b: someTypedHandle,\n * },\n * });\n * ```\n */\nexport const ref = <T extends OutputKind>(path: string): TypedRef<T> => ({\n $ref: path,\n});\n\n/**\n * Creates a raw guard object for use in the `guards` array of an operation call.\n *\n * @param kind - The guard type (e.g. `\"slippage\"`).\n * @param config - Guard-specific configuration (e.g. `{ toleranceBps: 300 }`).\n * @returns An {@link AppliedGuard} object.\n */\nexport const rawGuard = (\n kind: string,\n config: Record<string, unknown> = {},\n): AppliedGuard => {\n if ('kind' in config) {\n throw new Error(\n `rawGuard: 'kind' must not appear in config (got \"${String(\n config.kind,\n )}\"). Pass it as the first argument instead.`,\n );\n }\n return { ...config, kind };\n};\n"],"mappings":"AAqCO,MAAM,MAAM,CAAuB,UAA+B;AAAA,EACvE,MAAM;AACR;AASO,MAAM,WAAW,CACtB,MACA,SAAkC,CAAC,MAClB;AACjB,MAAI,UAAU,QAAQ;AACpB,UAAM,IAAI;AAAA,MACR,oDAAoD;AAAA,QAClD,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,EAAE,GAAG,QAAQ,KAAK;AAC3B;","names":[]}
|
package/dist/client.cjs
CHANGED
|
@@ -23,7 +23,7 @@ __export(client_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(client_exports);
|
|
24
24
|
var import_errors = require("./errors.js");
|
|
25
25
|
var import_responseSchemas = require("./responseSchemas.js");
|
|
26
|
-
const SDK_VERSION = true ? "0.4.
|
|
26
|
+
const SDK_VERSION = true ? "0.4.1" : "dev";
|
|
27
27
|
const bigintReplacer = (_key, value) => typeof value === "bigint" ? value.toString() : value;
|
|
28
28
|
const isNonNullObject = (v) => typeof v === "object" && v !== null;
|
|
29
29
|
const parseBody = async (res, url) => {
|
|
@@ -64,6 +64,27 @@ const parseSimulateBody = async (res, url) => {
|
|
|
64
64
|
}
|
|
65
65
|
return result;
|
|
66
66
|
};
|
|
67
|
+
const postCompile = async ({ fetchFn, baseHeaders }, url, request) => {
|
|
68
|
+
let res;
|
|
69
|
+
try {
|
|
70
|
+
res = await fetchFn(url, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { ...baseHeaders, "Content-Type": "application/json" },
|
|
73
|
+
body: JSON.stringify(request, bigintReplacer)
|
|
74
|
+
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
77
|
+
throw new import_errors.ComposeError("NETWORK_ERROR", message, { cause: err });
|
|
78
|
+
}
|
|
79
|
+
if (res.status === 206) {
|
|
80
|
+
return await parsePartialBody(res, url);
|
|
81
|
+
}
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
const body = await res.text();
|
|
84
|
+
throw (0, import_errors.errorFromHttpResponse)(res.status, body, url);
|
|
85
|
+
}
|
|
86
|
+
return await parseCompileSuccessBody(res, url);
|
|
87
|
+
};
|
|
67
88
|
const createComposeClient = (options) => {
|
|
68
89
|
if (!options.baseUrl || !/^https?:\/\//i.test(options.baseUrl)) {
|
|
69
90
|
throw new import_errors.ComposeError(
|
|
@@ -85,6 +106,7 @@ const createComposeClient = (options) => {
|
|
|
85
106
|
"x-lifi-composer-sdk": SDK_VERSION,
|
|
86
107
|
"x-lifi-api-key": trimmedApiKey
|
|
87
108
|
};
|
|
109
|
+
const transport = { fetchFn, baseHeaders };
|
|
88
110
|
const getManifest = async () => {
|
|
89
111
|
const url = `${base}/compose/manifest`;
|
|
90
112
|
let res;
|
|
@@ -103,28 +125,8 @@ const createComposeClient = (options) => {
|
|
|
103
125
|
}
|
|
104
126
|
return await parseBody(res, url);
|
|
105
127
|
};
|
|
106
|
-
const compile = async (request) => {
|
|
107
|
-
|
|
108
|
-
let res;
|
|
109
|
-
try {
|
|
110
|
-
res = await fetchFn(url, {
|
|
111
|
-
method: "POST",
|
|
112
|
-
headers: { ...baseHeaders, "Content-Type": "application/json" },
|
|
113
|
-
body: JSON.stringify(request, bigintReplacer)
|
|
114
|
-
});
|
|
115
|
-
} catch (err) {
|
|
116
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
-
throw new import_errors.ComposeError("NETWORK_ERROR", message, { cause: err });
|
|
118
|
-
}
|
|
119
|
-
if (res.status === 206) {
|
|
120
|
-
return await parsePartialBody(res, url);
|
|
121
|
-
}
|
|
122
|
-
if (!res.ok) {
|
|
123
|
-
const body = await res.text();
|
|
124
|
-
throw (0, import_errors.errorFromHttpResponse)(res.status, body, url);
|
|
125
|
-
}
|
|
126
|
-
return await parseCompileSuccessBody(res, url);
|
|
127
|
-
};
|
|
128
|
+
const compile = async (request) => postCompile(transport, `${base}/compose`, request);
|
|
129
|
+
const route = async (request) => postCompile(transport, `${base}/compose/route`, request);
|
|
128
130
|
const getZapPacks = async (options2) => {
|
|
129
131
|
const params = new URLSearchParams();
|
|
130
132
|
if (options2?.protocols !== void 0) {
|
|
@@ -169,7 +171,7 @@ const createComposeClient = (options) => {
|
|
|
169
171
|
const body = await res.text();
|
|
170
172
|
throw (0, import_errors.errorFromHttpResponse)(res.status, body, url);
|
|
171
173
|
};
|
|
172
|
-
return { getManifest, compile, getZapPacks, simulate };
|
|
174
|
+
return { getManifest, compile, route, getZapPacks, simulate };
|
|
173
175
|
};
|
|
174
176
|
// Annotate the CommonJS export names for ESM import in node:
|
|
175
177
|
0 && (module.exports = {
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n ComposeCompilePartialData,\n ComposeCompileRequest,\n ComposeCompileResult,\n ComposeCompileSuccessData,\n ComposeManifest,\n SimulateRequest,\n SimulateResult,\n} from '@lifi/compose-spec';\n\nimport type { GetZapPacksOptions, ZapPackOverview } from './discovery.js';\nimport { ComposeError, errorFromHttpResponse } from './errors.js';\nimport {\n parseCompilePartialEnvelope,\n parseSimulateResult,\n} from './responseSchemas.js';\n\n// __SDK_VERSION__ is a compile-time constant injected by tsup (via `define` in tsup.config.ts)\n// and by vitest (via `define` in vitest.config.ts). Both read the version from package.json\n// at build/test time and replace this identifier with the literal string value.\n// It is sent as the `x-lifi-composer-sdk` request header so the server can identify the caller.\n// Falls back to 'dev' when running via tsx without tsup substitution (e.g. the example harness).\ndeclare const __SDK_VERSION__: string;\n\nconst SDK_VERSION: string =\n typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : 'dev';\n\n/**\n * Configuration for creating a low-level Compose API client.\n */\nexport interface ComposeClientOptions {\n /** Base URL of the Compose API. */\n readonly baseUrl: string;\n /** Optional custom `fetch` implementation. Defaults to `globalThis.fetch`. */\n readonly fetch?: typeof globalThis.fetch;\n /** LI.FI API key, sent as the `x-lifi-api-key` header on every request. Required — the Compose API rejects unauthenticated requests. */\n readonly apiKey: string;\n}\n\n/**\n * Low-level HTTP client for the Compose API.\n *\n * Handles request serialization, SDK version headers, and error mapping.\n * Prefer using {@link ComposeSdk} for the full builder experience. Use this\n * directly when you need to decouple request building from submission — e.g.\n * build via `sdk.request()` then submit via `client.compile()` with custom\n * retry logic or request inspection.\n */\nexport interface ComposeClient {\n /**\n * Fetches the server's operation manifest describing all supported operations,\n * guards, materialisers, and preconditions.\n * @returns The manifest document.\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly getManifest: () => Promise<ComposeManifest>;\n /**\n * Submits a compile request and returns the result.\n *\n * When the caller passes `simulationPolicy: 'allow-revert'` and the transaction\n * reverts in simulation, the server responds with HTTP 206 and the SDK returns a\n * partial result (`status: 'partial'`) instead of throwing. The partial result\n * includes the transaction (without `gasLimit`) and revert diagnostics.\n *\n * @param request - The full compile request including flow and run inputs.\n * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly compile: (\n request: ComposeCompileRequest,\n ) => Promise<ComposeCompileResult>;\n /**\n * Fetches the available routing edges grouped by protocol.\n *\n * The edge catalog is dynamic — it reflects the current state of the\n * backend's routing snapshot (protocols, chains, token blacklists).\n * Results are not cached by the SDK; callers should cache as appropriate.\n *\n * @param options - Optional filter to restrict results to specific protocols.\n * @returns An array of {@link ZapPackOverview} objects, one per protocol.\n * @throws {@link ComposeError} on network or server errors (503 when the\n * routing catalog is not yet initialized).\n */\n readonly getZapPacks: (\n options?: GetZapPacksOptions,\n ) => Promise<readonly ZapPackOverview[]>;\n /**\n * Simulates a raw, pre-encoded transaction against `POST /simulate` and\n * reports how the watched balances change and how much inner-call gas it\n * burns.\n *\n * The result is a discriminated union on `status`:\n * - `'ok'` — the simulation ran successfully; `deltas`/`gasUsed` are populated.\n * - `'revert'` — the simulation ran but the transaction reverted on-chain. A\n * revert is a *successful simulation*, not a transport error, so it is\n * returned (HTTP 200) rather than thrown — mirroring how {@link compile}\n * returns `status: 'partial'` on a simulated revert.\n * - `'error'` — the request was well-formed but the simulation could not be\n * set up or run (HTTP 422); `message` is intentionally generic.\n *\n * `bigint` amounts in the request (`value`, requirement `balance`/`allowance`)\n * are serialised to decimal strings automatically.\n *\n * @param request - The raw transaction plus funding `requirements` and the\n * `trackedBalances` to watch.\n * @returns A {@link SimulateResult} (`ok` / `revert` / `error`).\n * @throws {@link ComposeError} on network failures, HTTP 400 (malformed\n * input), 401/403 (auth), 404, 429, and 5xx.\n */\n readonly simulate: (request: SimulateRequest) => Promise<SimulateResult>;\n}\n\nconst bigintReplacer = (_key: string, value: unknown): unknown =>\n typeof value === 'bigint' ? value.toString() : value;\n\nconst isNonNullObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null;\n\nconst parseBody = async <T>(res: Response, url: string): Promise<T> => {\n const body = await res.json().catch((_) => null);\n if (!isNonNullObject(body) || !('data' in body)) {\n throw new ComposeError('UNKNOWN_ERROR', 'Unexpected response format', {\n url,\n });\n }\n return body.data as T;\n};\n\nconst parseCompileSuccessBody = async (\n res: Response,\n url: string,\n): Promise<ComposeCompileResult> => {\n const data = await parseBody<ComposeCompileSuccessData>(res, url);\n return { ...data, status: 'success' as const };\n};\n\nconst parsePartialBody = async (\n res: Response,\n url: string,\n): Promise<ComposeCompileResult> => {\n const body = await res.json().catch((_) => null);\n const envelope = parseCompilePartialEnvelope(body);\n if (envelope === null) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n 'Unexpected partial response format',\n { url },\n );\n }\n // `data` is validated as an object by the schema; compose-spec owns its full\n // shape as a hand-authored type, so we narrow it here rather than re-declaring\n // that type as a schema. `error` is fully validated — no cast needed.\n const data = envelope.data as unknown as ComposeCompilePartialData;\n return { ...data, status: 'partial' as const, error: envelope.error };\n};\n\n// `/simulate` is un-enveloped: the discriminated body (`{ status, ... }`) is at\n// the top level, NOT wrapped in `{ data }` like `/compose`. So this reads the\n// body directly and validates it against the simulate union rather than reusing\n// `parseBody`.\nconst parseSimulateBody = async (\n res: Response,\n url: string,\n): Promise<SimulateResult> => {\n const body = await res.json().catch((_) => null);\n const result = parseSimulateResult(body);\n if (result === null) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n 'Unexpected simulate response format',\n { url },\n );\n }\n return result;\n};\n\n/**\n * Creates a low-level Compose API client.\n *\n * @param options - Client configuration including the API base URL.\n * @returns A {@link ComposeClient} instance.\n */\nexport const createComposeClient = (\n options: ComposeClientOptions,\n): ComposeClient => {\n if (!options.baseUrl || !/^https?:\\/\\//i.test(options.baseUrl)) {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `Invalid baseUrl: expected an HTTP(S) URL, got \"${options.baseUrl}\"`,\n );\n }\n const trimmedApiKey = options.apiKey?.trim() || undefined;\n if (!trimmedApiKey) {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n 'apiKey is required: pass a LI.FI API key to createComposeSdk().',\n );\n }\n const fetchFn = options.fetch ?? globalThis.fetch;\n const base = options.baseUrl.replace(/\\/$/, '');\n\n const baseHeaders: Record<string, string> = {\n Accept: 'application/json',\n 'x-lifi-composer-sdk': SDK_VERSION,\n 'x-lifi-api-key': trimmedApiKey,\n };\n\n const getManifest = async (): Promise<ComposeManifest> => {\n const url = `${base}/compose/manifest`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'GET',\n headers: { ...baseHeaders },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<ComposeManifest>(res, url);\n };\n\n const compile = async (\n request: ComposeCompileRequest,\n ): Promise<ComposeCompileResult> => {\n const url = `${base}/compose`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(request, bigintReplacer),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (res.status === 206) {\n return await parsePartialBody(res, url);\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseCompileSuccessBody(res, url);\n };\n\n const getZapPacks = async (\n options?: GetZapPacksOptions,\n ): Promise<readonly ZapPackOverview[]> => {\n const params = new URLSearchParams();\n if (options?.protocols !== undefined) {\n // Backend expects a single comma-separated value, not repeated keys.\n const raw = options.protocols;\n const list = typeof raw === 'string' ? raw : raw.join(',');\n params.set('protocols', list);\n }\n const qs = params.toString();\n const url = `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'GET',\n headers: { ...baseHeaders },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<readonly ZapPackOverview[]>(res, url);\n };\n\n const simulate = async (\n request: SimulateRequest,\n ): Promise<SimulateResult> => {\n const url = `${base}/simulate`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(request, bigintReplacer),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n // Only 200 (carries `ok`/`revert`) and 422 (carries the `error` member of\n // the union) have a discriminated body. 422 is deliberately intercepted\n // here (not thrown as VALIDATION_ERROR) so callers get one exhaustive\n // `switch (result.status)`. Every other status is a transport error and is\n // thrown — including HTTP 400 (malformed input, no `status` body) and any\n // unexpected 2xx.\n if (res.status === 200 || res.status === 422) {\n return await parseSimulateBody(res, url);\n }\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n };\n\n return { getManifest, compile, getZapPacks, simulate };\n};\n"],"mappings":"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","names":["options"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n ComposeCompilePartialData,\n ComposeCompileRequest,\n ComposeCompileResult,\n ComposeCompileSuccessData,\n ComposeManifest,\n ComposeRouteRequest,\n SimulateRequest,\n SimulateResult,\n} from '@lifi/compose-spec';\n\nimport type { GetZapPacksOptions, ZapPackOverview } from './discovery.js';\nimport { ComposeError, errorFromHttpResponse } from './errors.js';\nimport {\n parseCompilePartialEnvelope,\n parseSimulateResult,\n} from './responseSchemas.js';\n\n// __SDK_VERSION__ is a compile-time constant injected by tsup (via `define` in tsup.config.ts)\n// and by vitest (via `define` in vitest.config.ts). Both read the version from package.json\n// at build/test time and replace this identifier with the literal string value.\n// It is sent as the `x-lifi-composer-sdk` request header so the server can identify the caller.\n// Falls back to 'dev' when running via tsx without tsup substitution (e.g. the example harness).\ndeclare const __SDK_VERSION__: string;\n\nconst SDK_VERSION: string =\n typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : 'dev';\n\n/**\n * Configuration for creating a low-level Compose API client.\n */\nexport interface ComposeClientOptions {\n /** Base URL of the Compose API. */\n readonly baseUrl: string;\n /** Optional custom `fetch` implementation. Defaults to `globalThis.fetch`. */\n readonly fetch?: typeof globalThis.fetch;\n /** LI.FI API key, sent as the `x-lifi-api-key` header on every request. Required — the Compose API rejects unauthenticated requests. */\n readonly apiKey: string;\n}\n\n/**\n * Low-level HTTP client for the Compose API.\n *\n * Handles request serialization, SDK version headers, and error mapping.\n * Prefer using {@link ComposeSdk} for the full builder experience. Use this\n * directly when you need to decouple request building from submission — e.g.\n * build via `sdk.request()` then submit via `client.compile()` with custom\n * retry logic or request inspection.\n */\nexport interface ComposeClient {\n /**\n * Fetches the server's operation manifest describing all supported operations,\n * guards, materialisers, and preconditions.\n * @returns The manifest document.\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly getManifest: () => Promise<ComposeManifest>;\n /**\n * Submits a compile request and returns the result.\n *\n * When the caller passes `simulationPolicy: 'allow-revert'` and the transaction\n * reverts in simulation, the server responds with HTTP 206 and the SDK returns a\n * partial result (`status: 'partial'`) instead of throwing. The partial result\n * includes the transaction (without `gasLimit`) and revert diagnostics.\n *\n * @param request - The full compile request including flow and run inputs.\n * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly compile: (\n request: ComposeCompileRequest,\n ) => Promise<ComposeCompileResult>;\n /**\n * Compiles a token pair into a submit-ready transaction via\n * `POST /compose/route`.\n *\n * A convenience path that skips flow authoring: the server builds a one-node\n * `lifi.zap` flow with a `directDeposit` input from the `fromToken` /\n * `toToken` pair and runs it through the same pipeline as {@link compile}.\n * The response is therefore the same discriminated result — HTTP 206 under\n * `simulationPolicy: 'allow-revert'` yields `status: 'partial'` rather than\n * throwing, exactly as it does for {@link compile}.\n *\n * As of today that flow is a single zap step: one edge of the backend's\n * routing catalog (a position entry or exit, a wrap or unwrap, a mint or\n * burn). Enumerate the pairs it covers with {@link getZapPacks}; a pair with\n * no edge is rejected with `kind: 'no_route_error'`. The endpoint is meant to\n * author richer flows later, so the single-step limit is the current state of\n * the server, not the shape of this contract.\n *\n * Use {@link compile} for what the server does not author for you: several\n * chained operations, splits, explicit preconditions, or per-op guards.\n *\n * `bigint` amounts in `amount` are serialised to decimal strings\n * automatically.\n *\n * @param request - The from/to token pair, amount, signer, and route options.\n * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.\n * @throws {@link ComposeError} on network, validation, or server errors —\n * including `NOT_FOUND` (HTTP 404) when no catalog edge covers the pair.\n */\n readonly route: (\n request: ComposeRouteRequest,\n ) => Promise<ComposeCompileResult>;\n /**\n * Fetches the available routing edges grouped by protocol.\n *\n * The edge catalog is dynamic — it reflects the current state of the\n * backend's routing snapshot (protocols, chains, token blacklists).\n * Results are not cached by the SDK; callers should cache as appropriate.\n *\n * @param options - Optional filter to restrict results to specific protocols.\n * @returns An array of {@link ZapPackOverview} objects, one per protocol.\n * @throws {@link ComposeError} on network or server errors (503 when the\n * routing catalog is not yet initialized).\n */\n readonly getZapPacks: (\n options?: GetZapPacksOptions,\n ) => Promise<readonly ZapPackOverview[]>;\n /**\n * Simulates a raw, pre-encoded transaction against `POST /simulate` and\n * reports how the watched balances change and how much inner-call gas it\n * burns.\n *\n * The result is a discriminated union on `status`:\n * - `'ok'` — the simulation ran successfully; `deltas`/`gasUsed` are populated.\n * - `'revert'` — the simulation ran but the transaction reverted on-chain. A\n * revert is a *successful simulation*, not a transport error, so it is\n * returned (HTTP 200) rather than thrown — mirroring how {@link compile}\n * returns `status: 'partial'` on a simulated revert.\n * - `'error'` — the request was well-formed but the simulation could not be\n * set up or run (HTTP 422); `message` is intentionally generic.\n *\n * `bigint` amounts in the request (`value`, requirement `balance`/`allowance`)\n * are serialised to decimal strings automatically.\n *\n * @param request - The raw transaction plus funding `requirements` and the\n * `trackedBalances` to watch.\n * @returns A {@link SimulateResult} (`ok` / `revert` / `error`).\n * @throws {@link ComposeError} on network failures, HTTP 400 (malformed\n * input), 401/403 (auth), 404, 429, and 5xx.\n */\n readonly simulate: (request: SimulateRequest) => Promise<SimulateResult>;\n}\n\nconst bigintReplacer = (_key: string, value: unknown): unknown =>\n typeof value === 'bigint' ? value.toString() : value;\n\nconst isNonNullObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null;\n\nconst parseBody = async <T>(res: Response, url: string): Promise<T> => {\n const body = await res.json().catch((_) => null);\n if (!isNonNullObject(body) || !('data' in body)) {\n throw new ComposeError('UNKNOWN_ERROR', 'Unexpected response format', {\n url,\n });\n }\n return body.data as T;\n};\n\nconst parseCompileSuccessBody = async (\n res: Response,\n url: string,\n): Promise<ComposeCompileResult> => {\n const data = await parseBody<ComposeCompileSuccessData>(res, url);\n return { ...data, status: 'success' as const };\n};\n\nconst parsePartialBody = async (\n res: Response,\n url: string,\n): Promise<ComposeCompileResult> => {\n const body = await res.json().catch((_) => null);\n const envelope = parseCompilePartialEnvelope(body);\n if (envelope === null) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n 'Unexpected partial response format',\n { url },\n );\n }\n // `data` is validated as an object by the schema; compose-spec owns its full\n // shape as a hand-authored type, so we narrow it here rather than re-declaring\n // that type as a schema. `error` is fully validated — no cast needed.\n const data = envelope.data as unknown as ComposeCompilePartialData;\n return { ...data, status: 'partial' as const, error: envelope.error };\n};\n\n// `/simulate` is un-enveloped: the discriminated body (`{ status, ... }`) is at\n// the top level, NOT wrapped in `{ data }` like `/compose`. So this reads the\n// body directly and validates it against the simulate union rather than reusing\n// `parseBody`.\nconst parseSimulateBody = async (\n res: Response,\n url: string,\n): Promise<SimulateResult> => {\n const body = await res.json().catch((_) => null);\n const result = parseSimulateResult(body);\n if (result === null) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n 'Unexpected simulate response format',\n { url },\n );\n }\n return result;\n};\n// `POST /compose` and `POST /compose/route` differ only in path and request\n// shape: both return the enveloped success data on 200 and the partial\n// envelope on 206, so they share one transport.\ntype ComposeTransport = {\n readonly fetchFn: typeof fetch;\n readonly baseHeaders: Record<string, string>;\n};\n\nconst postCompile = async (\n { fetchFn, baseHeaders }: ComposeTransport,\n url: string,\n request: ComposeCompileRequest | ComposeRouteRequest,\n): Promise<ComposeCompileResult> => {\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(request, bigintReplacer),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (res.status === 206) {\n return await parsePartialBody(res, url);\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseCompileSuccessBody(res, url);\n};\n\n/**\n * Creates a low-level Compose API client.\n *\n * @param options - Client configuration including the API base URL.\n * @returns A {@link ComposeClient} instance.\n */\nexport const createComposeClient = (\n options: ComposeClientOptions,\n): ComposeClient => {\n if (!options.baseUrl || !/^https?:\\/\\//i.test(options.baseUrl)) {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `Invalid baseUrl: expected an HTTP(S) URL, got \"${options.baseUrl}\"`,\n );\n }\n const trimmedApiKey = options.apiKey?.trim() || undefined;\n if (!trimmedApiKey) {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n 'apiKey is required: pass a LI.FI API key to createComposeSdk().',\n );\n }\n const fetchFn = options.fetch ?? globalThis.fetch;\n const base = options.baseUrl.replace(/\\/$/, '');\n\n const baseHeaders: Record<string, string> = {\n Accept: 'application/json',\n 'x-lifi-composer-sdk': SDK_VERSION,\n 'x-lifi-api-key': trimmedApiKey,\n };\n const transport: ComposeTransport = { fetchFn, baseHeaders };\n\n const getManifest = async (): Promise<ComposeManifest> => {\n const url = `${base}/compose/manifest`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'GET',\n headers: { ...baseHeaders },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<ComposeManifest>(res, url);\n };\n\n const compile = async (\n request: ComposeCompileRequest,\n ): Promise<ComposeCompileResult> =>\n postCompile(transport, `${base}/compose`, request);\n\n const route = async (\n request: ComposeRouteRequest,\n ): Promise<ComposeCompileResult> =>\n postCompile(transport, `${base}/compose/route`, request);\n\n const getZapPacks = async (\n options?: GetZapPacksOptions,\n ): Promise<readonly ZapPackOverview[]> => {\n const params = new URLSearchParams();\n if (options?.protocols !== undefined) {\n // Backend expects a single comma-separated value, not repeated keys.\n const raw = options.protocols;\n const list = typeof raw === 'string' ? raw : raw.join(',');\n params.set('protocols', list);\n }\n const qs = params.toString();\n const url = `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'GET',\n headers: { ...baseHeaders },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<readonly ZapPackOverview[]>(res, url);\n };\n\n const simulate = async (\n request: SimulateRequest,\n ): Promise<SimulateResult> => {\n const url = `${base}/simulate`;\n let res: Response;\n try {\n res = await fetchFn(url, {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(request, bigintReplacer),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n // Only 200 (carries `ok`/`revert`) and 422 (carries the `error` member of\n // the union) have a discriminated body. 422 is deliberately intercepted\n // here (not thrown as VALIDATION_ERROR) so callers get one exhaustive\n // `switch (result.status)`. Every other status is a transport error and is\n // thrown — including HTTP 400 (malformed input, no `status` body) and any\n // unexpected 2xx.\n if (res.status === 200 || res.status === 422) {\n return await parseSimulateBody(res, url);\n }\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n };\n\n return { getManifest, compile, route, getZapPacks, simulate };\n};\n"],"mappings":"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","names":["options"]}
|
package/dist/client.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComposeManifest, ComposeCompileRequest, ComposeCompileResult, SimulateRequest, SimulateResult } from '@lifi/compose-spec';
|
|
1
|
+
import { ComposeManifest, ComposeCompileRequest, ComposeCompileResult, ComposeRouteRequest, SimulateRequest, SimulateResult } from '@lifi/compose-spec';
|
|
2
2
|
import { GetZapPacksOptions, ZapPackOverview } from './discovery.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -42,6 +42,36 @@ interface ComposeClient {
|
|
|
42
42
|
* @throws {@link ComposeError} on network, validation, or server errors.
|
|
43
43
|
*/
|
|
44
44
|
readonly compile: (request: ComposeCompileRequest) => Promise<ComposeCompileResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Compiles a token pair into a submit-ready transaction via
|
|
47
|
+
* `POST /compose/route`.
|
|
48
|
+
*
|
|
49
|
+
* A convenience path that skips flow authoring: the server builds a one-node
|
|
50
|
+
* `lifi.zap` flow with a `directDeposit` input from the `fromToken` /
|
|
51
|
+
* `toToken` pair and runs it through the same pipeline as {@link compile}.
|
|
52
|
+
* The response is therefore the same discriminated result — HTTP 206 under
|
|
53
|
+
* `simulationPolicy: 'allow-revert'` yields `status: 'partial'` rather than
|
|
54
|
+
* throwing, exactly as it does for {@link compile}.
|
|
55
|
+
*
|
|
56
|
+
* As of today that flow is a single zap step: one edge of the backend's
|
|
57
|
+
* routing catalog (a position entry or exit, a wrap or unwrap, a mint or
|
|
58
|
+
* burn). Enumerate the pairs it covers with {@link getZapPacks}; a pair with
|
|
59
|
+
* no edge is rejected with `kind: 'no_route_error'`. The endpoint is meant to
|
|
60
|
+
* author richer flows later, so the single-step limit is the current state of
|
|
61
|
+
* the server, not the shape of this contract.
|
|
62
|
+
*
|
|
63
|
+
* Use {@link compile} for what the server does not author for you: several
|
|
64
|
+
* chained operations, splits, explicit preconditions, or per-op guards.
|
|
65
|
+
*
|
|
66
|
+
* `bigint` amounts in `amount` are serialised to decimal strings
|
|
67
|
+
* automatically.
|
|
68
|
+
*
|
|
69
|
+
* @param request - The from/to token pair, amount, signer, and route options.
|
|
70
|
+
* @returns A discriminated result: `status: 'success'` or `status: 'partial'`.
|
|
71
|
+
* @throws {@link ComposeError} on network, validation, or server errors —
|
|
72
|
+
* including `NOT_FOUND` (HTTP 404) when no catalog edge covers the pair.
|
|
73
|
+
*/
|
|
74
|
+
readonly route: (request: ComposeRouteRequest) => Promise<ComposeCompileResult>;
|
|
45
75
|
/**
|
|
46
76
|
* Fetches the available routing edges grouped by protocol.
|
|
47
77
|
*
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComposeManifest, ComposeCompileRequest, ComposeCompileResult, SimulateRequest, SimulateResult } from '@lifi/compose-spec';
|
|
1
|
+
import { ComposeManifest, ComposeCompileRequest, ComposeCompileResult, ComposeRouteRequest, SimulateRequest, SimulateResult } from '@lifi/compose-spec';
|
|
2
2
|
import { GetZapPacksOptions, ZapPackOverview } from './discovery.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -42,6 +42,36 @@ interface ComposeClient {
|
|
|
42
42
|
* @throws {@link ComposeError} on network, validation, or server errors.
|
|
43
43
|
*/
|
|
44
44
|
readonly compile: (request: ComposeCompileRequest) => Promise<ComposeCompileResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Compiles a token pair into a submit-ready transaction via
|
|
47
|
+
* `POST /compose/route`.
|
|
48
|
+
*
|
|
49
|
+
* A convenience path that skips flow authoring: the server builds a one-node
|
|
50
|
+
* `lifi.zap` flow with a `directDeposit` input from the `fromToken` /
|
|
51
|
+
* `toToken` pair and runs it through the same pipeline as {@link compile}.
|
|
52
|
+
* The response is therefore the same discriminated result — HTTP 206 under
|
|
53
|
+
* `simulationPolicy: 'allow-revert'` yields `status: 'partial'` rather than
|
|
54
|
+
* throwing, exactly as it does for {@link compile}.
|
|
55
|
+
*
|
|
56
|
+
* As of today that flow is a single zap step: one edge of the backend's
|
|
57
|
+
* routing catalog (a position entry or exit, a wrap or unwrap, a mint or
|
|
58
|
+
* burn). Enumerate the pairs it covers with {@link getZapPacks}; a pair with
|
|
59
|
+
* no edge is rejected with `kind: 'no_route_error'`. The endpoint is meant to
|
|
60
|
+
* author richer flows later, so the single-step limit is the current state of
|
|
61
|
+
* the server, not the shape of this contract.
|
|
62
|
+
*
|
|
63
|
+
* Use {@link compile} for what the server does not author for you: several
|
|
64
|
+
* chained operations, splits, explicit preconditions, or per-op guards.
|
|
65
|
+
*
|
|
66
|
+
* `bigint` amounts in `amount` are serialised to decimal strings
|
|
67
|
+
* automatically.
|
|
68
|
+
*
|
|
69
|
+
* @param request - The from/to token pair, amount, signer, and route options.
|
|
70
|
+
* @returns A discriminated result: `status: 'success'` or `status: 'partial'`.
|
|
71
|
+
* @throws {@link ComposeError} on network, validation, or server errors —
|
|
72
|
+
* including `NOT_FOUND` (HTTP 404) when no catalog edge covers the pair.
|
|
73
|
+
*/
|
|
74
|
+
readonly route: (request: ComposeRouteRequest) => Promise<ComposeCompileResult>;
|
|
45
75
|
/**
|
|
46
76
|
* Fetches the available routing edges grouped by protocol.
|
|
47
77
|
*
|
package/dist/client.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
parseCompilePartialEnvelope,
|
|
4
4
|
parseSimulateResult
|
|
5
5
|
} from "./responseSchemas.js";
|
|
6
|
-
const SDK_VERSION = true ? "0.4.
|
|
6
|
+
const SDK_VERSION = true ? "0.4.1" : "dev";
|
|
7
7
|
const bigintReplacer = (_key, value) => typeof value === "bigint" ? value.toString() : value;
|
|
8
8
|
const isNonNullObject = (v) => typeof v === "object" && v !== null;
|
|
9
9
|
const parseBody = async (res, url) => {
|
|
@@ -44,6 +44,27 @@ const parseSimulateBody = async (res, url) => {
|
|
|
44
44
|
}
|
|
45
45
|
return result;
|
|
46
46
|
};
|
|
47
|
+
const postCompile = async ({ fetchFn, baseHeaders }, url, request) => {
|
|
48
|
+
let res;
|
|
49
|
+
try {
|
|
50
|
+
res = await fetchFn(url, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { ...baseHeaders, "Content-Type": "application/json" },
|
|
53
|
+
body: JSON.stringify(request, bigintReplacer)
|
|
54
|
+
});
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57
|
+
throw new ComposeError("NETWORK_ERROR", message, { cause: err });
|
|
58
|
+
}
|
|
59
|
+
if (res.status === 206) {
|
|
60
|
+
return await parsePartialBody(res, url);
|
|
61
|
+
}
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
const body = await res.text();
|
|
64
|
+
throw errorFromHttpResponse(res.status, body, url);
|
|
65
|
+
}
|
|
66
|
+
return await parseCompileSuccessBody(res, url);
|
|
67
|
+
};
|
|
47
68
|
const createComposeClient = (options) => {
|
|
48
69
|
if (!options.baseUrl || !/^https?:\/\//i.test(options.baseUrl)) {
|
|
49
70
|
throw new ComposeError(
|
|
@@ -65,6 +86,7 @@ const createComposeClient = (options) => {
|
|
|
65
86
|
"x-lifi-composer-sdk": SDK_VERSION,
|
|
66
87
|
"x-lifi-api-key": trimmedApiKey
|
|
67
88
|
};
|
|
89
|
+
const transport = { fetchFn, baseHeaders };
|
|
68
90
|
const getManifest = async () => {
|
|
69
91
|
const url = `${base}/compose/manifest`;
|
|
70
92
|
let res;
|
|
@@ -83,28 +105,8 @@ const createComposeClient = (options) => {
|
|
|
83
105
|
}
|
|
84
106
|
return await parseBody(res, url);
|
|
85
107
|
};
|
|
86
|
-
const compile = async (request) => {
|
|
87
|
-
|
|
88
|
-
let res;
|
|
89
|
-
try {
|
|
90
|
-
res = await fetchFn(url, {
|
|
91
|
-
method: "POST",
|
|
92
|
-
headers: { ...baseHeaders, "Content-Type": "application/json" },
|
|
93
|
-
body: JSON.stringify(request, bigintReplacer)
|
|
94
|
-
});
|
|
95
|
-
} catch (err) {
|
|
96
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
97
|
-
throw new ComposeError("NETWORK_ERROR", message, { cause: err });
|
|
98
|
-
}
|
|
99
|
-
if (res.status === 206) {
|
|
100
|
-
return await parsePartialBody(res, url);
|
|
101
|
-
}
|
|
102
|
-
if (!res.ok) {
|
|
103
|
-
const body = await res.text();
|
|
104
|
-
throw errorFromHttpResponse(res.status, body, url);
|
|
105
|
-
}
|
|
106
|
-
return await parseCompileSuccessBody(res, url);
|
|
107
|
-
};
|
|
108
|
+
const compile = async (request) => postCompile(transport, `${base}/compose`, request);
|
|
109
|
+
const route = async (request) => postCompile(transport, `${base}/compose/route`, request);
|
|
108
110
|
const getZapPacks = async (options2) => {
|
|
109
111
|
const params = new URLSearchParams();
|
|
110
112
|
if (options2?.protocols !== void 0) {
|
|
@@ -149,7 +151,7 @@ const createComposeClient = (options) => {
|
|
|
149
151
|
const body = await res.text();
|
|
150
152
|
throw errorFromHttpResponse(res.status, body, url);
|
|
151
153
|
};
|
|
152
|
-
return { getManifest, compile, getZapPacks, simulate };
|
|
154
|
+
return { getManifest, compile, route, getZapPacks, simulate };
|
|
153
155
|
};
|
|
154
156
|
export {
|
|
155
157
|
createComposeClient
|