@lifi/composer-sdk 0.9.0 → 0.11.3

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.
Files changed (58) hide show
  1. package/README.md +37 -3
  2. package/dist/authoring/raw.cjs +12 -7
  3. package/dist/authoring/raw.cjs.map +1 -1
  4. package/dist/authoring/raw.d.cts +12 -7
  5. package/dist/authoring/raw.d.ts +12 -7
  6. package/dist/authoring/raw.js +12 -7
  7. package/dist/authoring/raw.js.map +1 -1
  8. package/dist/client.cjs +11 -1
  9. package/dist/client.cjs.map +1 -1
  10. package/dist/client.js +11 -1
  11. package/dist/client.js.map +1 -1
  12. package/dist/errors.cjs +35 -13
  13. package/dist/errors.cjs.map +1 -1
  14. package/dist/errors.d.cts +19 -1
  15. package/dist/errors.d.ts +19 -1
  16. package/dist/errors.js +35 -14
  17. package/dist/errors.js.map +1 -1
  18. package/dist/generated/config.generated.d.cts +96 -1
  19. package/dist/generated/config.generated.d.ts +96 -1
  20. package/dist/generated/operations.generated.cjs +10 -0
  21. package/dist/generated/operations.generated.cjs.map +1 -1
  22. package/dist/generated/operations.generated.d.cts +56 -1
  23. package/dist/generated/operations.generated.d.ts +56 -1
  24. package/dist/generated/operations.generated.js +10 -0
  25. package/dist/generated/operations.generated.js.map +1 -1
  26. package/dist/index.cjs +1 -0
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +4 -4
  29. package/dist/index.d.ts +4 -4
  30. package/dist/index.js +2 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/outputs.cjs +3 -2
  33. package/dist/outputs.cjs.map +1 -1
  34. package/dist/outputs.d.cts +3 -2
  35. package/dist/outputs.d.ts +3 -2
  36. package/dist/outputs.js +3 -2
  37. package/dist/outputs.js.map +1 -1
  38. package/dist/responseSchemas.cjs.map +1 -1
  39. package/dist/responseSchemas.d.cts +2 -2
  40. package/dist/responseSchemas.d.ts +2 -2
  41. package/dist/responseSchemas.js.map +1 -1
  42. package/dist/run/inputs.cjs.map +1 -1
  43. package/dist/run/inputs.d.cts +3 -3
  44. package/dist/run/inputs.d.ts +3 -3
  45. package/dist/run/inputs.js.map +1 -1
  46. package/package.json +3 -3
  47. package/src/authoring/raw.ts +12 -7
  48. package/src/client.ts +24 -1
  49. package/src/errors.ts +48 -17
  50. package/src/examples/continuationBridgeSettle.ts +100 -0
  51. package/src/examples/continuationSettle.ts +113 -0
  52. package/src/examples/run.ts +8 -0
  53. package/src/generated/config.generated.ts +109 -1
  54. package/src/generated/operations.generated.ts +98 -0
  55. package/src/index.ts +5 -1
  56. package/src/outputs.ts +3 -2
  57. package/src/responseSchemas.ts +9 -5
  58. package/src/run/inputs.ts +3 -3
package/README.md CHANGED
@@ -109,7 +109,7 @@ if (result.status === 'success') {
109
109
 
110
110
  **Runtime inputs (materialisers)**: a materialiser tells the backend how to fund a flow input when the transaction executes.
111
111
 
112
- - `directDeposit({ amount })` pulls exactly `amount`.
112
+ - `directDeposit({ amount })` pulls exactly `amount`. For a token the compose token registry flags non-exact (stETH, Aave aTokens), a transfer credits a wei or two off, so the deposit instead pulls up to `amount` and asserts it received at least `amount` less 4 wei.
113
113
  - `directDeposit({ amount, allowNonExact: true })` permits a capped ERC-20 deposit of up to `amount`; `directDeposit({ allowNonExact: true })` deposits with no on-chain cap (ERC-20 only).
114
114
  - `balanceOf({ owner })` reads a current balance.
115
115
  - `call({ target, calldata })` measures the balance delta after an arbitrary contract call.
@@ -118,7 +118,7 @@ if (result.status === 'success') {
118
118
 
119
119
  **Guards**: runtime checks on an operation's output, attached through its `guards` field. `guards.slippage({ port, bps })` floors an output at `bps` below its simulated amount. The `port` argument is typed against the ports a guard can protect. For example, `lifi.swap`'s `amountOut` already carries the aggregator's own floor, so it rejects a slippage guard at compile time.
120
120
 
121
- **Outputs**: a compile result carries `outputs`, keyed `'<nodeId>.<portName>'`, with one entry per resource output. An entry is either:
121
+ **Outputs**: a compile result carries `outputs`, keyed `'<nodeId>.<portName>'`, with one entry per resource output, plus one entry per resource input no node consumes, keyed `'input.<name>'`. An entry is either:
122
122
 
123
123
  - `role: 'terminal'`: delivered to `owner`. `delivery.when` says whether it lands with this transaction (`'now'`) or later (`'future'`).
124
124
  - `role: 'consumed'`: spent by the node named in `consumedBy`.
@@ -144,7 +144,7 @@ Narrow on `role` before reading those fields. `amount.estimate` and `amount.mini
144
144
 
145
145
  - `compile(request)`: `POST /compose`. Returns a `ComposeCompileResult` with `status: 'success'` or `status: 'partial'`.
146
146
  - `route(request)`: `POST /compose/route`. Returns the same union.
147
- - `getManifest()`: `GET /compose/manifest`, the live operation registry.
147
+ - `getManifest()`: `GET /compose/manifest`, the live operation registry. From 0.10.2 the response also carries `routing` (`maxRouteHops`, `swapHops`), the deployment's `POST /compose/route` bounds; an older server omits it.
148
148
  - `getChains()`: `GET /chains`. Returns `readonly ChainSummary[]` (`{ chainId, name }`), ascending by `chainId`.
149
149
  - A chain is listed once the VM contract is confirmed deployed on it. `compile`, `route`, and `simulate` validate against the same condition, so they reject an unlisted chain. A listed chain can still have no routing edge for a given pair.
150
150
  - `name` is a display label that can change, so key on `chainId`. A chain with no configured label reports `Chain <id>`.
@@ -395,6 +395,38 @@ const sim = await sdk.client.simulate(req);
395
395
 
396
396
  Runnable versions: [`src/examples/simulateRawTransaction.ts`](src/examples/simulateRawTransaction.ts) and [`src/examples/simulateCompiledSwap.ts`](src/examples/simulateCompiledSwap.ts).
397
397
 
398
+ ## Deferred settlement (continuations)
399
+
400
+ `builder.continuation.settle(id, { bind: { input }, config })` escrows a resource and commits to an outcome that an off-chain executor settles later by running the node's `continuationFlow` (the *leg*) from the escrow. The leg is an ordinary flow built with `sdk.flow(...)`, with one input, the funding resource, on the escrow chain:
401
+
402
+ ```ts
403
+ // The leg supplies the escrowed WETH to Aave; the aBasWETH it mints stays in
404
+ // the leg's proxy, and the compiler's epilogue forwards it at commit.
405
+ const leg = sdk.flow(8453, { inputs: { funding: resources.erc20(WETH, 8453) } });
406
+ leg.lifi.zap('supply', {
407
+ bind: { amountIn: leg.inputs.funding },
408
+ config: { resourceOut: resources.erc20(A_BAS_WETH, 8453) },
409
+ guards: [guards.slippage({ port: 'amountOut', bps: 50 })],
410
+ });
411
+
412
+ builder.continuation.settle('settle', {
413
+ bind: { input: swap.amountOut },
414
+ config: {
415
+ outcomes: [{ token: A_BAS_WETH, destination: owner, chainId: 8453 }],
416
+ continuationFlow: leg.build(),
417
+ validity: { expiresAtMs: Date.now() + 60 * 60 * 1000 },
418
+ },
419
+ });
420
+ ```
421
+
422
+ Three rules decide whether a settlement can ever fill, and the compiler rejects a flow that breaks them:
423
+
424
+ - The leg leaves each outcome token in its own proxy: a leg that only delivers its funding has no nodes, and a node that needs a receiver binds `leg.context.executionAddress`. Never name `destination` or any payee. At commit the compiler writes the leg's epilogue, which forwards the outcome to the settlement contract (it verifies the committed floor and pays `destination`) and the residual to `refund`. A leg that carries its own `epilogue` is rejected.
425
+ - The leg must not reference `context.sender`: inside the leg it is the solver's executor, not the user.
426
+ - The leg declares exactly one input, the funding resource.
427
+
428
+ The compile response carries the escrow terms in `settlements[id].escrow` and any `fundEscrow` action in `actions`. The leg contract, funding modes (inline, external, bridge-delivered), expiry and refund, and monitoring are in [`docs/continuations.md`](docs/continuations.md).
429
+
398
430
  ## Error handling
399
431
 
400
432
  HTTP and transport failures are thrown as a `ComposeError`. Builder mistakes (a duplicate node id, a missing output port) throw a plain `Error` at the call site. Narrow with `isComposeError`:
@@ -487,6 +519,8 @@ Each file in `src/examples/` is a complete flow:
487
519
  - **routeTokenPair**: `sdk.route(...)` with both the `exact` and `all` amount variants
488
520
  - **supportedChains**: cache `sdk.getSupportedChains()`, then fail an unsupported chain locally with a message naming the alternatives
489
521
  - **simulateRawTransaction** / **simulateCompiledSwap**: raw and compiled simulation
522
+ - **continuationSettle**: swap USDC to WETH on Base, then escrow the WETH for a deferred settlement whose leg supplies it to Aave and delivers the aBasWETH position
523
+ - **continuationBridgeSettle**: bridge USDC from Arbitrum into a settle escrow on Base; when the delivery lands, the leg supplies it to Aave and delivers the aBasUSDC position
490
524
 
491
525
  ## Staging channel
492
526
 
@@ -39,11 +39,15 @@ const ref = (path) => ({ $ref: path });
39
39
  * flow document rather than supplied at run time.
40
40
  *
41
41
  * Prefer a declared flow input and `builder.inputs.<name>` — a value the
42
- * caller supplies per run stays out of the document. Use a literal when the
43
- * flow has no run of its own to take the value from: the `continuationFlow`
44
- * of a `continuation.settle` node is stored by the settlement backend and
45
- * re-run later against the deferred proceeds, so its delivery address has to
46
- * be part of the committed document.
42
+ * caller supplies per run stays out of the document. Use a literal for a
43
+ * constant that belongs to the document itself, such as a fixed treasury
44
+ * address.
45
+ *
46
+ * Never use a literal for the payee of a `continuation.settle` leg
47
+ * (`continuationFlow`). The leg produces its outcome into its own proxy
48
+ * (`leg.context.executionAddress`) and names no recipient; the composer
49
+ * appends a settlement epilogue at commit that forwards the outcome to the
50
+ * provider's recipient and the residual to `refund`.
47
51
  *
48
52
  * @typeParam T - The Solidity type of the literal (e.g. `'address'`).
49
53
  * @param kind - The literal's Solidity type.
@@ -54,9 +58,10 @@ const ref = (path) => ({ $ref: path });
54
58
  * ```ts
55
59
  * import { raw } from '@lifi/composer-sdk';
56
60
  *
57
- * builder.core.transfer('deliver', {
61
+ * // A fixed fee to a treasury baked into the flow document.
62
+ * builder.core.transfer('fee', {
58
63
  * bind: {
59
- * amount: zap.amountOut,
64
+ * amount: builder.inputs.fee,
60
65
  * recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
61
66
  * },
62
67
  * config: {},
@@ -1 +1 @@
1
- {"version":3,"file":"raw.cjs","names":[],"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard, SolType } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedLiteral, 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 // The brand is phantom, so the runtime value is the bare wire ref. This is\n // the only place a `TypedRef` is minted; the cast is what the brand buys.\n ({ $ref: path }) as TypedRef<T>;\n\n/**\n * Creates a typed literal for use in bind slots: a constant baked into the\n * flow document rather than supplied at run time.\n *\n * Prefer a declared flow input and `builder.inputs.<name>` — a value the\n * caller supplies per run stays out of the document. Use a literal when the\n * flow has no run of its own to take the value from: the `continuationFlow`\n * of a `continuation.settle` node is stored by the settlement backend and\n * re-run later against the deferred proceeds, so its delivery address has to\n * be part of the committed document.\n *\n * @typeParam T - The Solidity type of the literal (e.g. `'address'`).\n * @param kind - The literal's Solidity type.\n * @param value - The literal value, as the wire format carries it: a string.\n * @returns A {@link TypedLiteral} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * builder.core.transfer('deliver', {\n * bind: {\n * amount: zap.amountOut,\n * recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),\n * },\n * config: {},\n * });\n * ```\n */\nexport const literal = <T extends SolType>(\n kind: T,\n value: string,\n): TypedLiteral<T> => ({ kind, value });\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,OAA6B,UAGvC,EAAE,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BhB,MAAa,WACX,MACA,WACqB;CAAE;CAAM;AAAM;;;;;;;;AASrC,MAAa,YACX,MACA,SAAkC,CAAC,MAClB;CACjB,IAAI,UAAU,QACZ,MAAM,IAAI,MACR,oDAAoD,OAClD,OAAO,IACT,EAAE,2CACJ;CAEF,OAAO;EAAE,GAAG;EAAQ;CAAK;AAC3B"}
1
+ {"version":3,"file":"raw.cjs","names":[],"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard, SolType } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedLiteral, 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 // The brand is phantom, so the runtime value is the bare wire ref. This is\n // the only place a `TypedRef` is minted; the cast is what the brand buys.\n ({ $ref: path }) as TypedRef<T>;\n\n/**\n * Creates a typed literal for use in bind slots: a constant baked into the\n * flow document rather than supplied at run time.\n *\n * Prefer a declared flow input and `builder.inputs.<name>` — a value the\n * caller supplies per run stays out of the document. Use a literal for a\n * constant that belongs to the document itself, such as a fixed treasury\n * address.\n *\n * Never use a literal for the payee of a `continuation.settle` leg\n * (`continuationFlow`). The leg produces its outcome into its own proxy\n * (`leg.context.executionAddress`) and names no recipient; the composer\n * appends a settlement epilogue at commit that forwards the outcome to the\n * provider's recipient and the residual to `refund`.\n *\n * @typeParam T - The Solidity type of the literal (e.g. `'address'`).\n * @param kind - The literal's Solidity type.\n * @param value - The literal value, as the wire format carries it: a string.\n * @returns A {@link TypedLiteral} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * // A fixed fee to a treasury baked into the flow document.\n * builder.core.transfer('fee', {\n * bind: {\n * amount: builder.inputs.fee,\n * recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),\n * },\n * config: {},\n * });\n * ```\n */\nexport const literal = <T extends SolType>(\n kind: T,\n value: string,\n): TypedLiteral<T> => ({ kind, value });\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,OAA6B,UAGvC,EAAE,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoChB,MAAa,WACX,MACA,WACqB;CAAE;CAAM;AAAM;;;;;;;;AASrC,MAAa,YACX,MACA,SAAkC,CAAC,MAClB;CACjB,IAAI,UAAU,QACZ,MAAM,IAAI,MACR,oDAAoD,OAClD,OAAO,IACT,EAAE,2CACJ;CAEF,OAAO;EAAE,GAAG;EAAQ;CAAK;AAC3B"}
@@ -40,11 +40,15 @@ export declare const ref: <T extends OutputKind>(path: string) => TypedRef<T>;
40
40
  * flow document rather than supplied at run time.
41
41
  *
42
42
  * Prefer a declared flow input and `builder.inputs.<name>` — a value the
43
- * caller supplies per run stays out of the document. Use a literal when the
44
- * flow has no run of its own to take the value from: the `continuationFlow`
45
- * of a `continuation.settle` node is stored by the settlement backend and
46
- * re-run later against the deferred proceeds, so its delivery address has to
47
- * be part of the committed document.
43
+ * caller supplies per run stays out of the document. Use a literal for a
44
+ * constant that belongs to the document itself, such as a fixed treasury
45
+ * address.
46
+ *
47
+ * Never use a literal for the payee of a `continuation.settle` leg
48
+ * (`continuationFlow`). The leg produces its outcome into its own proxy
49
+ * (`leg.context.executionAddress`) and names no recipient; the composer
50
+ * appends a settlement epilogue at commit that forwards the outcome to the
51
+ * provider's recipient and the residual to `refund`.
48
52
  *
49
53
  * @typeParam T - The Solidity type of the literal (e.g. `'address'`).
50
54
  * @param kind - The literal's Solidity type.
@@ -55,9 +59,10 @@ export declare const ref: <T extends OutputKind>(path: string) => TypedRef<T>;
55
59
  * ```ts
56
60
  * import { raw } from '@lifi/composer-sdk';
57
61
  *
58
- * builder.core.transfer('deliver', {
62
+ * // A fixed fee to a treasury baked into the flow document.
63
+ * builder.core.transfer('fee', {
59
64
  * bind: {
60
- * amount: zap.amountOut,
65
+ * amount: builder.inputs.fee,
61
66
  * recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
62
67
  * },
63
68
  * config: {},
@@ -40,11 +40,15 @@ export declare const ref: <T extends OutputKind>(path: string) => TypedRef<T>;
40
40
  * flow document rather than supplied at run time.
41
41
  *
42
42
  * Prefer a declared flow input and `builder.inputs.<name>` — a value the
43
- * caller supplies per run stays out of the document. Use a literal when the
44
- * flow has no run of its own to take the value from: the `continuationFlow`
45
- * of a `continuation.settle` node is stored by the settlement backend and
46
- * re-run later against the deferred proceeds, so its delivery address has to
47
- * be part of the committed document.
43
+ * caller supplies per run stays out of the document. Use a literal for a
44
+ * constant that belongs to the document itself, such as a fixed treasury
45
+ * address.
46
+ *
47
+ * Never use a literal for the payee of a `continuation.settle` leg
48
+ * (`continuationFlow`). The leg produces its outcome into its own proxy
49
+ * (`leg.context.executionAddress`) and names no recipient; the composer
50
+ * appends a settlement epilogue at commit that forwards the outcome to the
51
+ * provider's recipient and the residual to `refund`.
48
52
  *
49
53
  * @typeParam T - The Solidity type of the literal (e.g. `'address'`).
50
54
  * @param kind - The literal's Solidity type.
@@ -55,9 +59,10 @@ export declare const ref: <T extends OutputKind>(path: string) => TypedRef<T>;
55
59
  * ```ts
56
60
  * import { raw } from '@lifi/composer-sdk';
57
61
  *
58
- * builder.core.transfer('deliver', {
62
+ * // A fixed fee to a treasury baked into the flow document.
63
+ * builder.core.transfer('fee', {
59
64
  * bind: {
60
- * amount: zap.amountOut,
65
+ * amount: builder.inputs.fee,
61
66
  * recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
62
67
  * },
63
68
  * config: {},
@@ -38,11 +38,15 @@ const ref = (path) => ({ $ref: path });
38
38
  * flow document rather than supplied at run time.
39
39
  *
40
40
  * Prefer a declared flow input and `builder.inputs.<name>` — a value the
41
- * caller supplies per run stays out of the document. Use a literal when the
42
- * flow has no run of its own to take the value from: the `continuationFlow`
43
- * of a `continuation.settle` node is stored by the settlement backend and
44
- * re-run later against the deferred proceeds, so its delivery address has to
45
- * be part of the committed document.
41
+ * caller supplies per run stays out of the document. Use a literal for a
42
+ * constant that belongs to the document itself, such as a fixed treasury
43
+ * address.
44
+ *
45
+ * Never use a literal for the payee of a `continuation.settle` leg
46
+ * (`continuationFlow`). The leg produces its outcome into its own proxy
47
+ * (`leg.context.executionAddress`) and names no recipient; the composer
48
+ * appends a settlement epilogue at commit that forwards the outcome to the
49
+ * provider's recipient and the residual to `refund`.
46
50
  *
47
51
  * @typeParam T - The Solidity type of the literal (e.g. `'address'`).
48
52
  * @param kind - The literal's Solidity type.
@@ -53,9 +57,10 @@ const ref = (path) => ({ $ref: path });
53
57
  * ```ts
54
58
  * import { raw } from '@lifi/composer-sdk';
55
59
  *
56
- * builder.core.transfer('deliver', {
60
+ * // A fixed fee to a treasury baked into the flow document.
61
+ * builder.core.transfer('fee', {
57
62
  * bind: {
58
- * amount: zap.amountOut,
63
+ * amount: builder.inputs.fee,
59
64
  * recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
60
65
  * },
61
66
  * config: {},
@@ -1 +1 @@
1
- {"version":3,"file":"raw.js","names":[],"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard, SolType } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedLiteral, 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 // The brand is phantom, so the runtime value is the bare wire ref. This is\n // the only place a `TypedRef` is minted; the cast is what the brand buys.\n ({ $ref: path }) as TypedRef<T>;\n\n/**\n * Creates a typed literal for use in bind slots: a constant baked into the\n * flow document rather than supplied at run time.\n *\n * Prefer a declared flow input and `builder.inputs.<name>` — a value the\n * caller supplies per run stays out of the document. Use a literal when the\n * flow has no run of its own to take the value from: the `continuationFlow`\n * of a `continuation.settle` node is stored by the settlement backend and\n * re-run later against the deferred proceeds, so its delivery address has to\n * be part of the committed document.\n *\n * @typeParam T - The Solidity type of the literal (e.g. `'address'`).\n * @param kind - The literal's Solidity type.\n * @param value - The literal value, as the wire format carries it: a string.\n * @returns A {@link TypedLiteral} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * builder.core.transfer('deliver', {\n * bind: {\n * amount: zap.amountOut,\n * recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),\n * },\n * config: {},\n * });\n * ```\n */\nexport const literal = <T extends SolType>(\n kind: T,\n value: string,\n): TypedLiteral<T> => ({ kind, value });\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,OAA6B,UAGvC,EAAE,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BhB,MAAa,WACX,MACA,WACqB;CAAE;CAAM;AAAM;;;;;;;;AASrC,MAAa,YACX,MACA,SAAkC,CAAC,MAClB;CACjB,IAAI,UAAU,QACZ,MAAM,IAAI,MACR,oDAAoD,OAClD,OAAO,IACT,EAAE,2CACJ;CAEF,OAAO;EAAE,GAAG;EAAQ;CAAK;AAC3B"}
1
+ {"version":3,"file":"raw.js","names":[],"sources":["../../src/authoring/raw.ts"],"sourcesContent":["import type { AppliedGuard, SolType } from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedLiteral, 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 // The brand is phantom, so the runtime value is the bare wire ref. This is\n // the only place a `TypedRef` is minted; the cast is what the brand buys.\n ({ $ref: path }) as TypedRef<T>;\n\n/**\n * Creates a typed literal for use in bind slots: a constant baked into the\n * flow document rather than supplied at run time.\n *\n * Prefer a declared flow input and `builder.inputs.<name>` — a value the\n * caller supplies per run stays out of the document. Use a literal for a\n * constant that belongs to the document itself, such as a fixed treasury\n * address.\n *\n * Never use a literal for the payee of a `continuation.settle` leg\n * (`continuationFlow`). The leg produces its outcome into its own proxy\n * (`leg.context.executionAddress`) and names no recipient; the composer\n * appends a settlement epilogue at commit that forwards the outcome to the\n * provider's recipient and the residual to `refund`.\n *\n * @typeParam T - The Solidity type of the literal (e.g. `'address'`).\n * @param kind - The literal's Solidity type.\n * @param value - The literal value, as the wire format carries it: a string.\n * @returns A {@link TypedLiteral} accepted by `Bindable<T>` slots matching `T`.\n *\n * @example\n * ```ts\n * import { raw } from '@lifi/composer-sdk';\n *\n * // A fixed fee to a treasury baked into the flow document.\n * builder.core.transfer('fee', {\n * bind: {\n * amount: builder.inputs.fee,\n * recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),\n * },\n * config: {},\n * });\n * ```\n */\nexport const literal = <T extends SolType>(\n kind: T,\n value: string,\n): TypedLiteral<T> => ({ kind, value });\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,OAA6B,UAGvC,EAAE,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoChB,MAAa,WACX,MACA,WACqB;CAAE;CAAM;AAAM;;;;;;;;AASrC,MAAa,YACX,MACA,SAAkC,CAAC,MAClB;CACjB,IAAI,UAAU,QACZ,MAAM,IAAI,MACR,oDAAoD,OAClD,OAAO,IACT,EAAE,2CACJ;CAEF,OAAO;EAAE,GAAG;EAAQ;CAAK;AAC3B"}
package/dist/client.cjs CHANGED
@@ -3,7 +3,7 @@ const require_responseSchemas = require("./responseSchemas.cjs");
3
3
  const require_errors = require("./errors.cjs");
4
4
  let _lifi_compose_spec = require("@lifi/compose-spec");
5
5
  //#region src/client.ts
6
- const SDK_VERSION = "0.9.0";
6
+ const SDK_VERSION = "0.11.3";
7
7
  const SDK_VERSION_TRIPLE = (0, _lifi_compose_spec.parseVersion)(SDK_VERSION);
8
8
  /**
9
9
  * `JSON.stringify` replacer that renders `bigint` amounts as decimal strings.
@@ -38,9 +38,19 @@ const decodeOutputs = (data, url) => {
38
38
  decodeAmountLeaf(amount, "minimum", `outputs.${key}.amount`, url);
39
39
  }
40
40
  };
41
+ const decodeEpilogue = (data, url) => {
42
+ const epilogue = data.epilogue;
43
+ if (epilogue === void 0) return;
44
+ if (!isNonNullObject(epilogue) || !Array.isArray(epilogue.forwards)) throw malformed("epilogue", "is not an object with forwards", url);
45
+ epilogue.forwards.forEach((forward, index) => {
46
+ if (!isNonNullObject(forward)) throw malformed(`epilogue.forwards[${index}]`, "is not an object", url);
47
+ forward.amount = decodeAmount(forward.amount, `epilogue.forwards[${index}].amount`, url);
48
+ });
49
+ };
41
50
  const decodeWireAmounts = (data, url) => {
42
51
  if (!isNonNullObject(data)) return;
43
52
  decodeOutputs(data, url);
53
+ decodeEpilogue(data, url);
44
54
  };
45
55
  const parseBody = async (res, url) => {
46
56
  const body = await res.json().catch((_) => null);
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","names":["parseVersion","ComposeError","parseCompilePartialEnvelope","parseSimulateResult","COMPOSE_VERSION_HEADER","checkCompatibility","errorFromHttpResponse","COMPOSER_SDK_VERSION_HEADER"],"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';\nimport {\n checkCompatibility,\n COMPOSE_VERSION_HEADER,\n COMPOSER_SDK_VERSION_HEADER,\n parseVersion,\n} from '@lifi/compose-spec';\n\nimport type {\n ChainSummary,\n GetZapPacksOptions,\n ZapPackOverview,\n} 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 tsdown (via `define` in tsdown.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 `COMPOSER_SDK_VERSION_HEADER` request header so the server can identify the caller.\n// Falls back to 'dev' when running via tsx without tsdown 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// `undefined` for the `dev` sentinel, which disables the response version check.\nconst SDK_VERSION_TRIPLE = parseVersion(SDK_VERSION);\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 * Fetches the chains the Compose API supports, ascending by `chainId`.\n *\n * A chain is listed once the server confirms the VM contract is deployed on\n * it — the same condition {@link compile}, {@link route} and\n * {@link simulate} validate against. A chain absent from this list is\n * rejected outright by those endpoints.\n *\n * The converse does not hold: a listed chain can still have no routing edge\n * for a given token pair, which {@link route} reports as `no_route_error`.\n * Use {@link getZapPacks} to enumerate the pairs that do route.\n *\n * Each entry carries a display `name` alongside its `chainId`. The name is a\n * deployment-maintained label — render it, but key on `chainId`.\n *\n * The list tracks the server's chain registry, which refreshes periodically.\n * Results are not cached by the SDK; callers should cache as appropriate.\n *\n * @returns The supported chains, ascending by `chainId`.\n * @throws {@link ComposeError} on network or server errors.\n */\n readonly getChains: () => Promise<readonly ChainSummary[]>;\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 * zap flow with a `directDeposit` input from the `fromToken` / `toToken`\n * pair and runs it through the same pipeline as {@link compile}. The\n * 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 * The server plans a short chain of catalog edges for the pair (a position\n * entry or exit, a wrap or unwrap, a mint or burn), optionally opened by a\n * `lifi.swap`. {@link getZapPacks} lists the single edges; the pairs the\n * planner connects are their closure up to the hop budget, which\n * {@link buildReachability} computes locally. A pair the planner cannot\n * connect is rejected with `kind: 'no_route_error'`.\n *\n * Only synchronous edges are considered unless the request sets\n * `allowAsyncRoutes: true`, so by default every terminal output comes back\n * `delivery.when: 'now'` and a pair reachable only after settlement is a\n * `no_route_error`. Opting in adds the `lifi.zapAsync` fallback, which still\n * loses to a synchronous edge when the pair has one; read\n * `outputs[*].delivery.when` to see which representation you got.\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 the planner cannot connect 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\n/**\n * `JSON.stringify` replacer that renders `bigint` amounts as decimal strings.\n *\n * The compose wire carries token amounts as decimal strings, and `bigint` has\n * no JSON representation, so every payload this client sends goes through it.\n * Exported so the example harness prints a request exactly as it is sent.\n */\nexport const 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\n// Non-negative decimal integer string, as the server sends token amounts.\nconst INTEGER_STRING = /^\\d+$/;\n\nconst decodeAmount = (value: unknown, path: string, url: string): bigint => {\n if (typeof value !== 'string' || !INTEGER_STRING.test(value)) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: ${path} is ${\n value === undefined ? 'absent' : 'not an integer string'\n }`,\n { url },\n );\n }\n return BigInt(value);\n};\n\n// The two records this seam reads out of a compile-family response body. Each\n// is `unknown` because the seam is what establishes its shape: it validates\n// every field before writing a decoded `bigint` back over the wire string.\ninterface WireAmountBody {\n outputs?: unknown;\n}\n\n// One `Amount` as it arrives: each leaf is `unknown` until this seam has\n// checked it, and is overwritten in place with the decoded `bigint`.\ninterface WireAmount {\n estimate?: unknown;\n minimum?: unknown;\n}\n\nconst malformed = (path: string, what: string, url: string): ComposeError =>\n new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: ${path} ${what}`,\n { url },\n );\n\n// Decode one `Estimate` / `Minimum` leaf in place: `{ basis, value }` with\n// `value` a decimal string on the wire and a `bigint` on `Amount`.\nconst decodeAmountLeaf = (\n amount: WireAmount,\n field: 'estimate' | 'minimum',\n path: string,\n url: string,\n): void => {\n const leaf = amount[field];\n if (leaf === undefined) return;\n if (!isNonNullObject(leaf)) {\n throw malformed(`${path}.${field}`, 'is not an object', url);\n }\n leaf.value = decodeAmount(leaf.value, `${path}.${field}.value`, url);\n};\n\n// `outputs` is REQUIRED on every compile-family body and is the record a\n// caller reads amounts from, so its structure is checked here — the body is\n// cast, not schema-parsed — before each `value` leaf becomes a `bigint`.\nconst decodeOutputs = (data: WireAmountBody, url: string): void => {\n const outputs = data.outputs;\n if (!isNonNullObject(outputs)) {\n throw malformed(\n 'outputs',\n outputs === undefined ? 'is absent' : 'is not an object',\n url,\n );\n }\n for (const key of Object.keys(outputs)) {\n const entry = outputs[key];\n if (!isNonNullObject(entry)) {\n throw malformed(`outputs.${key}`, 'is not an object', url);\n }\n const amount = entry.amount;\n if (!isNonNullObject(amount)) {\n throw malformed(\n `outputs.${key}.amount`,\n amount === undefined ? 'is absent' : 'is not an object',\n url,\n );\n }\n decodeAmountLeaf(amount, 'estimate', `outputs.${key}.amount`, url);\n decodeAmountLeaf(amount, 'minimum', `outputs.${key}.amount`, url);\n }\n};\n\n// THE decode seam for a compile-family response body: every amount the wire\n// renders as a decimal string becomes the `bigint` its declared type promises.\n// The amount record is handled here so the body is narrowed once — a\n// non-object body is not this function's error to raise, since the enveloping\n// parse already rejected it.\nconst decodeWireAmounts = (data: unknown, url: string): void => {\n if (!isNonNullObject(data)) return;\n decodeOutputs(data, url);\n};\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 decodeWireAmounts(data, 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 ComposeCompilePartialData;\n decodeWireAmounts(data, url);\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\n// A patch bump is additive and a minor bump is breaking, so a 200 from a\n// backend behind this SDK cannot be trusted: the newer flow this SDK authored\n// may have compiled under the server's older semantics without raising an\n// error. This covers the deploy window where the SDK is published to npm ahead\n// of the backend rollout. Only the compile-family POSTs are checked; discovery\n// GETs stay reachable from a mismatched SDK, as on the server. An absent header\n// (a pre-gate backend, a header-stripping proxy) is accepted, mirroring the\n// server's pass-through of a compile that carries no version header.\nconst assertServerVersion = (res: Response, url: string): void => {\n const serverVersion = res.headers.get(COMPOSE_VERSION_HEADER);\n if (serverVersion === null || SDK_VERSION_TRIPLE === undefined) return;\n const serverTriple = parseVersion(serverVersion);\n if (serverTriple === undefined) return;\n\n const compatibility = checkCompatibility(SDK_VERSION_TRIPLE, serverTriple);\n if (compatibility === 'compatible') return;\n\n const versions = { sdkVersion: SDK_VERSION, serverVersion };\n if (compatibility === 'sdk_outdated') {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `composer-sdk ${SDK_VERSION} is older than the compose contract served at ${url} (${serverVersion}); upgrade @lifi/composer-sdk and @lifi/compose-spec`,\n {\n status: res.status,\n url,\n kind: 'sdk_outdated',\n sdkOutdated: {\n ...versions,\n minimumSdkVersion: `${serverTriple.major}.${serverTriple.minor}.0`,\n },\n },\n );\n }\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `composer-sdk ${SDK_VERSION} targets a newer compose contract than the server at ${url} serves (${serverVersion}); pin @lifi/composer-sdk and @lifi/compose-spec to ${serverVersion} or wait for the server rollout`,\n {\n status: res.status,\n url,\n kind: 'server_outdated',\n serverOutdated: versions,\n },\n );\n};\n\ntype SendInit =\n | { readonly method: 'GET' }\n | { readonly method: 'POST'; readonly body: unknown };\n\n// GETs on this API carry no body; POSTs carry a JSON one with `bigint` amounts\n// rendered as decimal strings.\nconst requestInit = (\n baseHeaders: Record<string, string>,\n init: SendInit,\n): RequestInit =>\n init.method === 'GET'\n ? { method: 'GET', headers: { ...baseHeaders } }\n : {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(init.body, bigintReplacer),\n };\n\n// One fetch path for every route: maps transport failures to `NETWORK_ERROR`.\n// The response version check is not here — only the compile-family POSTs gate\n// on it (see `postCompile`); discovery GETs and `/simulate` are ungated.\nconst send = async (\n { fetchFn, baseHeaders }: ComposeTransport,\n url: string,\n init: SendInit,\n): Promise<Response> => {\n try {\n return await fetchFn(url, requestInit(baseHeaders, init));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n};\n\nconst postCompile = async (\n transport: ComposeTransport,\n url: string,\n request: ComposeCompileRequest | ComposeRouteRequest,\n): Promise<ComposeCompileResult> => {\n const res = await send(transport, url, { method: 'POST', body: request });\n assertServerVersion(res, url);\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// Every enveloped GET on this API behaves identically: no body, no query\n// semantics of its own, `{ data }` on success, an error envelope otherwise.\nconst getJson = async <T>(\n transport: ComposeTransport,\n url: string,\n): Promise<T> => {\n const res = await send(transport, url, { method: 'GET' });\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<T>(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 [COMPOSER_SDK_VERSION_HEADER]: SDK_VERSION,\n 'x-lifi-api-key': trimmedApiKey,\n };\n const transport: ComposeTransport = { fetchFn, baseHeaders };\n\n const getManifest = async (): Promise<ComposeManifest> =>\n getJson<ComposeManifest>(transport, `${base}/compose/manifest`);\n\n const getChains = async (): Promise<readonly ChainSummary[]> =>\n getJson<readonly ChainSummary[]>(transport, `${base}/chains`);\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 return getJson<readonly ZapPackOverview[]>(\n transport,\n `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`,\n );\n };\n\n const simulate = async (\n request: SimulateRequest,\n ): Promise<SimulateResult> => {\n const url = `${base}/simulate`;\n const res = await send(transport, url, { method: 'POST', body: request });\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, getChains, compile, route, getZapPacks, simulate };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.cjs","names":["parseVersion","ComposeError","parseCompilePartialEnvelope","parseSimulateResult","COMPOSE_VERSION_HEADER","checkCompatibility","errorFromHttpResponse","COMPOSER_SDK_VERSION_HEADER"],"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';\nimport {\n checkCompatibility,\n COMPOSE_VERSION_HEADER,\n COMPOSER_SDK_VERSION_HEADER,\n parseVersion,\n} from '@lifi/compose-spec';\n\nimport type {\n ChainSummary,\n GetZapPacksOptions,\n ZapPackOverview,\n} 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 tsdown (via `define` in tsdown.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 `COMPOSER_SDK_VERSION_HEADER` request header so the server can identify the caller.\n// Falls back to 'dev' when running via tsx without tsdown 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// `undefined` for the `dev` sentinel, which disables the response version check.\nconst SDK_VERSION_TRIPLE = parseVersion(SDK_VERSION);\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 * Fetches the chains the Compose API supports, ascending by `chainId`.\n *\n * A chain is listed once the server confirms the VM contract is deployed on\n * it — the same condition {@link compile}, {@link route} and\n * {@link simulate} validate against. A chain absent from this list is\n * rejected outright by those endpoints.\n *\n * The converse does not hold: a listed chain can still have no routing edge\n * for a given token pair, which {@link route} reports as `no_route_error`.\n * Use {@link getZapPacks} to enumerate the pairs that do route.\n *\n * Each entry carries a display `name` alongside its `chainId`. The name is a\n * deployment-maintained label — render it, but key on `chainId`.\n *\n * The list tracks the server's chain registry, which refreshes periodically.\n * Results are not cached by the SDK; callers should cache as appropriate.\n *\n * @returns The supported chains, ascending by `chainId`.\n * @throws {@link ComposeError} on network or server errors.\n */\n readonly getChains: () => Promise<readonly ChainSummary[]>;\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 * zap flow with a `directDeposit` input from the `fromToken` / `toToken`\n * pair and runs it through the same pipeline as {@link compile}. The\n * 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 * The server plans a short chain of catalog edges for the pair (a position\n * entry or exit, a wrap or unwrap, a mint or burn), optionally opened by a\n * `lifi.swap`. {@link getZapPacks} lists the single edges; the pairs the\n * planner connects are their closure up to the hop budget, which\n * {@link buildReachability} computes locally. A pair the planner cannot\n * connect is rejected with `kind: 'no_route_error'`.\n *\n * Only synchronous edges are considered unless the request sets\n * `allowAsyncRoutes: true`, so by default every terminal output comes back\n * `delivery.when: 'now'` and a pair reachable only after settlement is a\n * `no_route_error`. Opting in adds the `lifi.zapAsync` fallback, which still\n * loses to a synchronous edge when the pair has one; read\n * `outputs[*].delivery.when` to see which representation you got.\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 the planner cannot connect 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\n/**\n * `JSON.stringify` replacer that renders `bigint` amounts as decimal strings.\n *\n * The compose wire carries token amounts as decimal strings, and `bigint` has\n * no JSON representation, so every payload this client sends goes through it.\n * Exported so the example harness prints a request exactly as it is sent.\n */\nexport const 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\n// Non-negative decimal integer string, as the server sends token amounts.\nconst INTEGER_STRING = /^\\d+$/;\n\nconst decodeAmount = (value: unknown, path: string, url: string): bigint => {\n if (typeof value !== 'string' || !INTEGER_STRING.test(value)) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: ${path} is ${\n value === undefined ? 'absent' : 'not an integer string'\n }`,\n { url },\n );\n }\n return BigInt(value);\n};\n\n// The two records this seam reads out of a compile-family response body. Each\n// is `unknown` because the seam is what establishes its shape: it validates\n// every field before writing a decoded `bigint` back over the wire string.\ninterface WireAmountBody {\n outputs?: unknown;\n epilogue?: unknown;\n}\n\n// One `Amount` as it arrives: each leaf is `unknown` until this seam has\n// checked it, and is overwritten in place with the decoded `bigint`.\ninterface WireAmount {\n estimate?: unknown;\n minimum?: unknown;\n}\n\nconst malformed = (path: string, what: string, url: string): ComposeError =>\n new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: ${path} ${what}`,\n { url },\n );\n\n// Decode one `Estimate` / `Minimum` leaf in place: `{ basis, value }` with\n// `value` a decimal string on the wire and a `bigint` on `Amount`.\nconst decodeAmountLeaf = (\n amount: WireAmount,\n field: 'estimate' | 'minimum',\n path: string,\n url: string,\n): void => {\n const leaf = amount[field];\n if (leaf === undefined) return;\n if (!isNonNullObject(leaf)) {\n throw malformed(`${path}.${field}`, 'is not an object', url);\n }\n leaf.value = decodeAmount(leaf.value, `${path}.${field}.value`, url);\n};\n\n// `outputs` is REQUIRED on every compile-family body and is the record a\n// caller reads amounts from, so its structure is checked here — the body is\n// cast, not schema-parsed — before each `value` leaf becomes a `bigint`.\nconst decodeOutputs = (data: WireAmountBody, url: string): void => {\n const outputs = data.outputs;\n if (!isNonNullObject(outputs)) {\n throw malformed(\n 'outputs',\n outputs === undefined ? 'is absent' : 'is not an object',\n url,\n );\n }\n for (const key of Object.keys(outputs)) {\n const entry = outputs[key];\n if (!isNonNullObject(entry)) {\n throw malformed(`outputs.${key}`, 'is not an object', url);\n }\n const amount = entry.amount;\n if (!isNonNullObject(amount)) {\n throw malformed(\n `outputs.${key}.amount`,\n amount === undefined ? 'is absent' : 'is not an object',\n url,\n );\n }\n decodeAmountLeaf(amount, 'estimate', `outputs.${key}.amount`, url);\n decodeAmountLeaf(amount, 'minimum', `outputs.${key}.amount`, url);\n }\n};\n\n// `epilogue` is optional (absent without an epilogue, and on every partial);\n// when present, each named forward's `amount` is a decimal string on the wire.\nconst decodeEpilogue = (data: WireAmountBody, url: string): void => {\n const epilogue = data.epilogue;\n if (epilogue === undefined) return;\n if (!isNonNullObject(epilogue) || !Array.isArray(epilogue.forwards)) {\n throw malformed('epilogue', 'is not an object with forwards', url);\n }\n const forwards: readonly unknown[] = epilogue.forwards;\n forwards.forEach((forward, index) => {\n if (!isNonNullObject(forward)) {\n throw malformed(`epilogue.forwards[${index}]`, 'is not an object', url);\n }\n forward.amount = decodeAmount(\n forward.amount,\n `epilogue.forwards[${index}].amount`,\n url,\n );\n });\n};\n\n// THE decode seam for a compile-family response body: every amount the wire\n// renders as a decimal string becomes the `bigint` its declared type promises.\n// The amount records are handled here so the body is narrowed once — a\n// non-object body is not this function's error to raise, since the enveloping\n// parse already rejected it.\nconst decodeWireAmounts = (data: unknown, url: string): void => {\n if (!isNonNullObject(data)) return;\n decodeOutputs(data, url);\n decodeEpilogue(data, url);\n};\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 decodeWireAmounts(data, 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 ComposeCompilePartialData;\n decodeWireAmounts(data, url);\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\n// A patch bump is additive and a minor bump is breaking, so a 200 from a\n// backend behind this SDK cannot be trusted: the newer flow this SDK authored\n// may have compiled under the server's older semantics without raising an\n// error. This covers the deploy window where the SDK is published to npm ahead\n// of the backend rollout. Only the compile-family POSTs are checked; discovery\n// GETs stay reachable from a mismatched SDK, as on the server. An absent header\n// (a pre-gate backend, a header-stripping proxy) is accepted, mirroring the\n// server's pass-through of a compile that carries no version header.\nconst assertServerVersion = (res: Response, url: string): void => {\n const serverVersion = res.headers.get(COMPOSE_VERSION_HEADER);\n if (serverVersion === null || SDK_VERSION_TRIPLE === undefined) return;\n const serverTriple = parseVersion(serverVersion);\n if (serverTriple === undefined) return;\n\n const compatibility = checkCompatibility(SDK_VERSION_TRIPLE, serverTriple);\n if (compatibility === 'compatible') return;\n\n const versions = { sdkVersion: SDK_VERSION, serverVersion };\n if (compatibility === 'sdk_outdated') {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `composer-sdk ${SDK_VERSION} is older than the compose contract served at ${url} (${serverVersion}); upgrade @lifi/composer-sdk and @lifi/compose-spec`,\n {\n status: res.status,\n url,\n kind: 'sdk_outdated',\n sdkOutdated: {\n ...versions,\n minimumSdkVersion: `${serverTriple.major}.${serverTriple.minor}.0`,\n },\n },\n );\n }\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `composer-sdk ${SDK_VERSION} targets a newer compose contract than the server at ${url} serves (${serverVersion}); pin @lifi/composer-sdk and @lifi/compose-spec to ${serverVersion} or wait for the server rollout`,\n {\n status: res.status,\n url,\n kind: 'server_outdated',\n serverOutdated: versions,\n },\n );\n};\n\ntype SendInit =\n | { readonly method: 'GET' }\n | { readonly method: 'POST'; readonly body: unknown };\n\n// GETs on this API carry no body; POSTs carry a JSON one with `bigint` amounts\n// rendered as decimal strings.\nconst requestInit = (\n baseHeaders: Record<string, string>,\n init: SendInit,\n): RequestInit =>\n init.method === 'GET'\n ? { method: 'GET', headers: { ...baseHeaders } }\n : {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(init.body, bigintReplacer),\n };\n\n// One fetch path for every route: maps transport failures to `NETWORK_ERROR`.\n// The response version check is not here — only the compile-family POSTs gate\n// on it (see `postCompile`); discovery GETs and `/simulate` are ungated.\nconst send = async (\n { fetchFn, baseHeaders }: ComposeTransport,\n url: string,\n init: SendInit,\n): Promise<Response> => {\n try {\n return await fetchFn(url, requestInit(baseHeaders, init));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n};\n\nconst postCompile = async (\n transport: ComposeTransport,\n url: string,\n request: ComposeCompileRequest | ComposeRouteRequest,\n): Promise<ComposeCompileResult> => {\n const res = await send(transport, url, { method: 'POST', body: request });\n assertServerVersion(res, url);\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// Every enveloped GET on this API behaves identically: no body, no query\n// semantics of its own, `{ data }` on success, an error envelope otherwise.\nconst getJson = async <T>(\n transport: ComposeTransport,\n url: string,\n): Promise<T> => {\n const res = await send(transport, url, { method: 'GET' });\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<T>(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 [COMPOSER_SDK_VERSION_HEADER]: SDK_VERSION,\n 'x-lifi-api-key': trimmedApiKey,\n };\n const transport: ComposeTransport = { fetchFn, baseHeaders };\n\n const getManifest = async (): Promise<ComposeManifest> =>\n getJson<ComposeManifest>(transport, `${base}/compose/manifest`);\n\n const getChains = async (): Promise<readonly ChainSummary[]> =>\n getJson<readonly ChainSummary[]>(transport, `${base}/chains`);\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 return getJson<readonly ZapPackOverview[]>(\n transport,\n `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`,\n );\n };\n\n const simulate = async (\n request: SimulateRequest,\n ): Promise<SimulateResult> => {\n const url = `${base}/simulate`;\n const res = await send(transport, url, { method: 'POST', body: request });\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, getChains, compile, route, getZapPacks, simulate };\n};\n"],"mappings":"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"}
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@ import { parseCompilePartialEnvelope, parseSimulateResult } from "./responseSche
2
2
  import { ComposeError, errorFromHttpResponse } from "./errors.js";
3
3
  import { COMPOSER_SDK_VERSION_HEADER, COMPOSE_VERSION_HEADER, checkCompatibility, parseVersion } from "@lifi/compose-spec";
4
4
  //#region src/client.ts
5
- const SDK_VERSION = "0.9.0";
5
+ const SDK_VERSION = "0.11.3";
6
6
  const SDK_VERSION_TRIPLE = parseVersion(SDK_VERSION);
7
7
  /**
8
8
  * `JSON.stringify` replacer that renders `bigint` amounts as decimal strings.
@@ -37,9 +37,19 @@ const decodeOutputs = (data, url) => {
37
37
  decodeAmountLeaf(amount, "minimum", `outputs.${key}.amount`, url);
38
38
  }
39
39
  };
40
+ const decodeEpilogue = (data, url) => {
41
+ const epilogue = data.epilogue;
42
+ if (epilogue === void 0) return;
43
+ if (!isNonNullObject(epilogue) || !Array.isArray(epilogue.forwards)) throw malformed("epilogue", "is not an object with forwards", url);
44
+ epilogue.forwards.forEach((forward, index) => {
45
+ if (!isNonNullObject(forward)) throw malformed(`epilogue.forwards[${index}]`, "is not an object", url);
46
+ forward.amount = decodeAmount(forward.amount, `epilogue.forwards[${index}].amount`, url);
47
+ });
48
+ };
40
49
  const decodeWireAmounts = (data, url) => {
41
50
  if (!isNonNullObject(data)) return;
42
51
  decodeOutputs(data, url);
52
+ decodeEpilogue(data, url);
43
53
  };
44
54
  const parseBody = async (res, url) => {
45
55
  const body = await res.json().catch((_) => null);
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"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';\nimport {\n checkCompatibility,\n COMPOSE_VERSION_HEADER,\n COMPOSER_SDK_VERSION_HEADER,\n parseVersion,\n} from '@lifi/compose-spec';\n\nimport type {\n ChainSummary,\n GetZapPacksOptions,\n ZapPackOverview,\n} 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 tsdown (via `define` in tsdown.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 `COMPOSER_SDK_VERSION_HEADER` request header so the server can identify the caller.\n// Falls back to 'dev' when running via tsx without tsdown 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// `undefined` for the `dev` sentinel, which disables the response version check.\nconst SDK_VERSION_TRIPLE = parseVersion(SDK_VERSION);\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 * Fetches the chains the Compose API supports, ascending by `chainId`.\n *\n * A chain is listed once the server confirms the VM contract is deployed on\n * it — the same condition {@link compile}, {@link route} and\n * {@link simulate} validate against. A chain absent from this list is\n * rejected outright by those endpoints.\n *\n * The converse does not hold: a listed chain can still have no routing edge\n * for a given token pair, which {@link route} reports as `no_route_error`.\n * Use {@link getZapPacks} to enumerate the pairs that do route.\n *\n * Each entry carries a display `name` alongside its `chainId`. The name is a\n * deployment-maintained label — render it, but key on `chainId`.\n *\n * The list tracks the server's chain registry, which refreshes periodically.\n * Results are not cached by the SDK; callers should cache as appropriate.\n *\n * @returns The supported chains, ascending by `chainId`.\n * @throws {@link ComposeError} on network or server errors.\n */\n readonly getChains: () => Promise<readonly ChainSummary[]>;\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 * zap flow with a `directDeposit` input from the `fromToken` / `toToken`\n * pair and runs it through the same pipeline as {@link compile}. The\n * 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 * The server plans a short chain of catalog edges for the pair (a position\n * entry or exit, a wrap or unwrap, a mint or burn), optionally opened by a\n * `lifi.swap`. {@link getZapPacks} lists the single edges; the pairs the\n * planner connects are their closure up to the hop budget, which\n * {@link buildReachability} computes locally. A pair the planner cannot\n * connect is rejected with `kind: 'no_route_error'`.\n *\n * Only synchronous edges are considered unless the request sets\n * `allowAsyncRoutes: true`, so by default every terminal output comes back\n * `delivery.when: 'now'` and a pair reachable only after settlement is a\n * `no_route_error`. Opting in adds the `lifi.zapAsync` fallback, which still\n * loses to a synchronous edge when the pair has one; read\n * `outputs[*].delivery.when` to see which representation you got.\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 the planner cannot connect 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\n/**\n * `JSON.stringify` replacer that renders `bigint` amounts as decimal strings.\n *\n * The compose wire carries token amounts as decimal strings, and `bigint` has\n * no JSON representation, so every payload this client sends goes through it.\n * Exported so the example harness prints a request exactly as it is sent.\n */\nexport const 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\n// Non-negative decimal integer string, as the server sends token amounts.\nconst INTEGER_STRING = /^\\d+$/;\n\nconst decodeAmount = (value: unknown, path: string, url: string): bigint => {\n if (typeof value !== 'string' || !INTEGER_STRING.test(value)) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: ${path} is ${\n value === undefined ? 'absent' : 'not an integer string'\n }`,\n { url },\n );\n }\n return BigInt(value);\n};\n\n// The two records this seam reads out of a compile-family response body. Each\n// is `unknown` because the seam is what establishes its shape: it validates\n// every field before writing a decoded `bigint` back over the wire string.\ninterface WireAmountBody {\n outputs?: unknown;\n}\n\n// One `Amount` as it arrives: each leaf is `unknown` until this seam has\n// checked it, and is overwritten in place with the decoded `bigint`.\ninterface WireAmount {\n estimate?: unknown;\n minimum?: unknown;\n}\n\nconst malformed = (path: string, what: string, url: string): ComposeError =>\n new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: ${path} ${what}`,\n { url },\n );\n\n// Decode one `Estimate` / `Minimum` leaf in place: `{ basis, value }` with\n// `value` a decimal string on the wire and a `bigint` on `Amount`.\nconst decodeAmountLeaf = (\n amount: WireAmount,\n field: 'estimate' | 'minimum',\n path: string,\n url: string,\n): void => {\n const leaf = amount[field];\n if (leaf === undefined) return;\n if (!isNonNullObject(leaf)) {\n throw malformed(`${path}.${field}`, 'is not an object', url);\n }\n leaf.value = decodeAmount(leaf.value, `${path}.${field}.value`, url);\n};\n\n// `outputs` is REQUIRED on every compile-family body and is the record a\n// caller reads amounts from, so its structure is checked here — the body is\n// cast, not schema-parsed — before each `value` leaf becomes a `bigint`.\nconst decodeOutputs = (data: WireAmountBody, url: string): void => {\n const outputs = data.outputs;\n if (!isNonNullObject(outputs)) {\n throw malformed(\n 'outputs',\n outputs === undefined ? 'is absent' : 'is not an object',\n url,\n );\n }\n for (const key of Object.keys(outputs)) {\n const entry = outputs[key];\n if (!isNonNullObject(entry)) {\n throw malformed(`outputs.${key}`, 'is not an object', url);\n }\n const amount = entry.amount;\n if (!isNonNullObject(amount)) {\n throw malformed(\n `outputs.${key}.amount`,\n amount === undefined ? 'is absent' : 'is not an object',\n url,\n );\n }\n decodeAmountLeaf(amount, 'estimate', `outputs.${key}.amount`, url);\n decodeAmountLeaf(amount, 'minimum', `outputs.${key}.amount`, url);\n }\n};\n\n// THE decode seam for a compile-family response body: every amount the wire\n// renders as a decimal string becomes the `bigint` its declared type promises.\n// The amount record is handled here so the body is narrowed once — a\n// non-object body is not this function's error to raise, since the enveloping\n// parse already rejected it.\nconst decodeWireAmounts = (data: unknown, url: string): void => {\n if (!isNonNullObject(data)) return;\n decodeOutputs(data, url);\n};\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 decodeWireAmounts(data, 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 ComposeCompilePartialData;\n decodeWireAmounts(data, url);\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\n// A patch bump is additive and a minor bump is breaking, so a 200 from a\n// backend behind this SDK cannot be trusted: the newer flow this SDK authored\n// may have compiled under the server's older semantics without raising an\n// error. This covers the deploy window where the SDK is published to npm ahead\n// of the backend rollout. Only the compile-family POSTs are checked; discovery\n// GETs stay reachable from a mismatched SDK, as on the server. An absent header\n// (a pre-gate backend, a header-stripping proxy) is accepted, mirroring the\n// server's pass-through of a compile that carries no version header.\nconst assertServerVersion = (res: Response, url: string): void => {\n const serverVersion = res.headers.get(COMPOSE_VERSION_HEADER);\n if (serverVersion === null || SDK_VERSION_TRIPLE === undefined) return;\n const serverTriple = parseVersion(serverVersion);\n if (serverTriple === undefined) return;\n\n const compatibility = checkCompatibility(SDK_VERSION_TRIPLE, serverTriple);\n if (compatibility === 'compatible') return;\n\n const versions = { sdkVersion: SDK_VERSION, serverVersion };\n if (compatibility === 'sdk_outdated') {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `composer-sdk ${SDK_VERSION} is older than the compose contract served at ${url} (${serverVersion}); upgrade @lifi/composer-sdk and @lifi/compose-spec`,\n {\n status: res.status,\n url,\n kind: 'sdk_outdated',\n sdkOutdated: {\n ...versions,\n minimumSdkVersion: `${serverTriple.major}.${serverTriple.minor}.0`,\n },\n },\n );\n }\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `composer-sdk ${SDK_VERSION} targets a newer compose contract than the server at ${url} serves (${serverVersion}); pin @lifi/composer-sdk and @lifi/compose-spec to ${serverVersion} or wait for the server rollout`,\n {\n status: res.status,\n url,\n kind: 'server_outdated',\n serverOutdated: versions,\n },\n );\n};\n\ntype SendInit =\n | { readonly method: 'GET' }\n | { readonly method: 'POST'; readonly body: unknown };\n\n// GETs on this API carry no body; POSTs carry a JSON one with `bigint` amounts\n// rendered as decimal strings.\nconst requestInit = (\n baseHeaders: Record<string, string>,\n init: SendInit,\n): RequestInit =>\n init.method === 'GET'\n ? { method: 'GET', headers: { ...baseHeaders } }\n : {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(init.body, bigintReplacer),\n };\n\n// One fetch path for every route: maps transport failures to `NETWORK_ERROR`.\n// The response version check is not here — only the compile-family POSTs gate\n// on it (see `postCompile`); discovery GETs and `/simulate` are ungated.\nconst send = async (\n { fetchFn, baseHeaders }: ComposeTransport,\n url: string,\n init: SendInit,\n): Promise<Response> => {\n try {\n return await fetchFn(url, requestInit(baseHeaders, init));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n};\n\nconst postCompile = async (\n transport: ComposeTransport,\n url: string,\n request: ComposeCompileRequest | ComposeRouteRequest,\n): Promise<ComposeCompileResult> => {\n const res = await send(transport, url, { method: 'POST', body: request });\n assertServerVersion(res, url);\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// Every enveloped GET on this API behaves identically: no body, no query\n// semantics of its own, `{ data }` on success, an error envelope otherwise.\nconst getJson = async <T>(\n transport: ComposeTransport,\n url: string,\n): Promise<T> => {\n const res = await send(transport, url, { method: 'GET' });\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<T>(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 [COMPOSER_SDK_VERSION_HEADER]: SDK_VERSION,\n 'x-lifi-api-key': trimmedApiKey,\n };\n const transport: ComposeTransport = { fetchFn, baseHeaders };\n\n const getManifest = async (): Promise<ComposeManifest> =>\n getJson<ComposeManifest>(transport, `${base}/compose/manifest`);\n\n const getChains = async (): Promise<readonly ChainSummary[]> =>\n getJson<readonly ChainSummary[]>(transport, `${base}/chains`);\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 return getJson<readonly ZapPackOverview[]>(\n transport,\n `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`,\n );\n };\n\n const simulate = async (\n request: SimulateRequest,\n ): Promise<SimulateResult> => {\n const url = `${base}/simulate`;\n const res = await send(transport, url, { method: 'POST', body: request });\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, getChains, compile, route, getZapPacks, simulate };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"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';\nimport {\n checkCompatibility,\n COMPOSE_VERSION_HEADER,\n COMPOSER_SDK_VERSION_HEADER,\n parseVersion,\n} from '@lifi/compose-spec';\n\nimport type {\n ChainSummary,\n GetZapPacksOptions,\n ZapPackOverview,\n} 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 tsdown (via `define` in tsdown.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 `COMPOSER_SDK_VERSION_HEADER` request header so the server can identify the caller.\n// Falls back to 'dev' when running via tsx without tsdown 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// `undefined` for the `dev` sentinel, which disables the response version check.\nconst SDK_VERSION_TRIPLE = parseVersion(SDK_VERSION);\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 * Fetches the chains the Compose API supports, ascending by `chainId`.\n *\n * A chain is listed once the server confirms the VM contract is deployed on\n * it — the same condition {@link compile}, {@link route} and\n * {@link simulate} validate against. A chain absent from this list is\n * rejected outright by those endpoints.\n *\n * The converse does not hold: a listed chain can still have no routing edge\n * for a given token pair, which {@link route} reports as `no_route_error`.\n * Use {@link getZapPacks} to enumerate the pairs that do route.\n *\n * Each entry carries a display `name` alongside its `chainId`. The name is a\n * deployment-maintained label — render it, but key on `chainId`.\n *\n * The list tracks the server's chain registry, which refreshes periodically.\n * Results are not cached by the SDK; callers should cache as appropriate.\n *\n * @returns The supported chains, ascending by `chainId`.\n * @throws {@link ComposeError} on network or server errors.\n */\n readonly getChains: () => Promise<readonly ChainSummary[]>;\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 * zap flow with a `directDeposit` input from the `fromToken` / `toToken`\n * pair and runs it through the same pipeline as {@link compile}. The\n * 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 * The server plans a short chain of catalog edges for the pair (a position\n * entry or exit, a wrap or unwrap, a mint or burn), optionally opened by a\n * `lifi.swap`. {@link getZapPacks} lists the single edges; the pairs the\n * planner connects are their closure up to the hop budget, which\n * {@link buildReachability} computes locally. A pair the planner cannot\n * connect is rejected with `kind: 'no_route_error'`.\n *\n * Only synchronous edges are considered unless the request sets\n * `allowAsyncRoutes: true`, so by default every terminal output comes back\n * `delivery.when: 'now'` and a pair reachable only after settlement is a\n * `no_route_error`. Opting in adds the `lifi.zapAsync` fallback, which still\n * loses to a synchronous edge when the pair has one; read\n * `outputs[*].delivery.when` to see which representation you got.\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 the planner cannot connect 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\n/**\n * `JSON.stringify` replacer that renders `bigint` amounts as decimal strings.\n *\n * The compose wire carries token amounts as decimal strings, and `bigint` has\n * no JSON representation, so every payload this client sends goes through it.\n * Exported so the example harness prints a request exactly as it is sent.\n */\nexport const 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\n// Non-negative decimal integer string, as the server sends token amounts.\nconst INTEGER_STRING = /^\\d+$/;\n\nconst decodeAmount = (value: unknown, path: string, url: string): bigint => {\n if (typeof value !== 'string' || !INTEGER_STRING.test(value)) {\n throw new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: ${path} is ${\n value === undefined ? 'absent' : 'not an integer string'\n }`,\n { url },\n );\n }\n return BigInt(value);\n};\n\n// The two records this seam reads out of a compile-family response body. Each\n// is `unknown` because the seam is what establishes its shape: it validates\n// every field before writing a decoded `bigint` back over the wire string.\ninterface WireAmountBody {\n outputs?: unknown;\n epilogue?: unknown;\n}\n\n// One `Amount` as it arrives: each leaf is `unknown` until this seam has\n// checked it, and is overwritten in place with the decoded `bigint`.\ninterface WireAmount {\n estimate?: unknown;\n minimum?: unknown;\n}\n\nconst malformed = (path: string, what: string, url: string): ComposeError =>\n new ComposeError(\n 'UNKNOWN_ERROR',\n `Malformed compose response: ${path} ${what}`,\n { url },\n );\n\n// Decode one `Estimate` / `Minimum` leaf in place: `{ basis, value }` with\n// `value` a decimal string on the wire and a `bigint` on `Amount`.\nconst decodeAmountLeaf = (\n amount: WireAmount,\n field: 'estimate' | 'minimum',\n path: string,\n url: string,\n): void => {\n const leaf = amount[field];\n if (leaf === undefined) return;\n if (!isNonNullObject(leaf)) {\n throw malformed(`${path}.${field}`, 'is not an object', url);\n }\n leaf.value = decodeAmount(leaf.value, `${path}.${field}.value`, url);\n};\n\n// `outputs` is REQUIRED on every compile-family body and is the record a\n// caller reads amounts from, so its structure is checked here — the body is\n// cast, not schema-parsed — before each `value` leaf becomes a `bigint`.\nconst decodeOutputs = (data: WireAmountBody, url: string): void => {\n const outputs = data.outputs;\n if (!isNonNullObject(outputs)) {\n throw malformed(\n 'outputs',\n outputs === undefined ? 'is absent' : 'is not an object',\n url,\n );\n }\n for (const key of Object.keys(outputs)) {\n const entry = outputs[key];\n if (!isNonNullObject(entry)) {\n throw malformed(`outputs.${key}`, 'is not an object', url);\n }\n const amount = entry.amount;\n if (!isNonNullObject(amount)) {\n throw malformed(\n `outputs.${key}.amount`,\n amount === undefined ? 'is absent' : 'is not an object',\n url,\n );\n }\n decodeAmountLeaf(amount, 'estimate', `outputs.${key}.amount`, url);\n decodeAmountLeaf(amount, 'minimum', `outputs.${key}.amount`, url);\n }\n};\n\n// `epilogue` is optional (absent without an epilogue, and on every partial);\n// when present, each named forward's `amount` is a decimal string on the wire.\nconst decodeEpilogue = (data: WireAmountBody, url: string): void => {\n const epilogue = data.epilogue;\n if (epilogue === undefined) return;\n if (!isNonNullObject(epilogue) || !Array.isArray(epilogue.forwards)) {\n throw malformed('epilogue', 'is not an object with forwards', url);\n }\n const forwards: readonly unknown[] = epilogue.forwards;\n forwards.forEach((forward, index) => {\n if (!isNonNullObject(forward)) {\n throw malformed(`epilogue.forwards[${index}]`, 'is not an object', url);\n }\n forward.amount = decodeAmount(\n forward.amount,\n `epilogue.forwards[${index}].amount`,\n url,\n );\n });\n};\n\n// THE decode seam for a compile-family response body: every amount the wire\n// renders as a decimal string becomes the `bigint` its declared type promises.\n// The amount records are handled here so the body is narrowed once — a\n// non-object body is not this function's error to raise, since the enveloping\n// parse already rejected it.\nconst decodeWireAmounts = (data: unknown, url: string): void => {\n if (!isNonNullObject(data)) return;\n decodeOutputs(data, url);\n decodeEpilogue(data, url);\n};\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 decodeWireAmounts(data, 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 ComposeCompilePartialData;\n decodeWireAmounts(data, url);\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\n// A patch bump is additive and a minor bump is breaking, so a 200 from a\n// backend behind this SDK cannot be trusted: the newer flow this SDK authored\n// may have compiled under the server's older semantics without raising an\n// error. This covers the deploy window where the SDK is published to npm ahead\n// of the backend rollout. Only the compile-family POSTs are checked; discovery\n// GETs stay reachable from a mismatched SDK, as on the server. An absent header\n// (a pre-gate backend, a header-stripping proxy) is accepted, mirroring the\n// server's pass-through of a compile that carries no version header.\nconst assertServerVersion = (res: Response, url: string): void => {\n const serverVersion = res.headers.get(COMPOSE_VERSION_HEADER);\n if (serverVersion === null || SDK_VERSION_TRIPLE === undefined) return;\n const serverTriple = parseVersion(serverVersion);\n if (serverTriple === undefined) return;\n\n const compatibility = checkCompatibility(SDK_VERSION_TRIPLE, serverTriple);\n if (compatibility === 'compatible') return;\n\n const versions = { sdkVersion: SDK_VERSION, serverVersion };\n if (compatibility === 'sdk_outdated') {\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `composer-sdk ${SDK_VERSION} is older than the compose contract served at ${url} (${serverVersion}); upgrade @lifi/composer-sdk and @lifi/compose-spec`,\n {\n status: res.status,\n url,\n kind: 'sdk_outdated',\n sdkOutdated: {\n ...versions,\n minimumSdkVersion: `${serverTriple.major}.${serverTriple.minor}.0`,\n },\n },\n );\n }\n throw new ComposeError(\n 'VALIDATION_ERROR',\n `composer-sdk ${SDK_VERSION} targets a newer compose contract than the server at ${url} serves (${serverVersion}); pin @lifi/composer-sdk and @lifi/compose-spec to ${serverVersion} or wait for the server rollout`,\n {\n status: res.status,\n url,\n kind: 'server_outdated',\n serverOutdated: versions,\n },\n );\n};\n\ntype SendInit =\n | { readonly method: 'GET' }\n | { readonly method: 'POST'; readonly body: unknown };\n\n// GETs on this API carry no body; POSTs carry a JSON one with `bigint` amounts\n// rendered as decimal strings.\nconst requestInit = (\n baseHeaders: Record<string, string>,\n init: SendInit,\n): RequestInit =>\n init.method === 'GET'\n ? { method: 'GET', headers: { ...baseHeaders } }\n : {\n method: 'POST',\n headers: { ...baseHeaders, 'Content-Type': 'application/json' },\n body: JSON.stringify(init.body, bigintReplacer),\n };\n\n// One fetch path for every route: maps transport failures to `NETWORK_ERROR`.\n// The response version check is not here — only the compile-family POSTs gate\n// on it (see `postCompile`); discovery GETs and `/simulate` are ungated.\nconst send = async (\n { fetchFn, baseHeaders }: ComposeTransport,\n url: string,\n init: SendInit,\n): Promise<Response> => {\n try {\n return await fetchFn(url, requestInit(baseHeaders, init));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new ComposeError('NETWORK_ERROR', message, { cause: err });\n }\n};\n\nconst postCompile = async (\n transport: ComposeTransport,\n url: string,\n request: ComposeCompileRequest | ComposeRouteRequest,\n): Promise<ComposeCompileResult> => {\n const res = await send(transport, url, { method: 'POST', body: request });\n assertServerVersion(res, url);\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// Every enveloped GET on this API behaves identically: no body, no query\n// semantics of its own, `{ data }` on success, an error envelope otherwise.\nconst getJson = async <T>(\n transport: ComposeTransport,\n url: string,\n): Promise<T> => {\n const res = await send(transport, url, { method: 'GET' });\n if (!res.ok) {\n const body = await res.text();\n throw errorFromHttpResponse(res.status, body, url);\n }\n return await parseBody<T>(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 [COMPOSER_SDK_VERSION_HEADER]: SDK_VERSION,\n 'x-lifi-api-key': trimmedApiKey,\n };\n const transport: ComposeTransport = { fetchFn, baseHeaders };\n\n const getManifest = async (): Promise<ComposeManifest> =>\n getJson<ComposeManifest>(transport, `${base}/compose/manifest`);\n\n const getChains = async (): Promise<readonly ChainSummary[]> =>\n getJson<readonly ChainSummary[]>(transport, `${base}/chains`);\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 return getJson<readonly ZapPackOverview[]>(\n transport,\n `${base}/compose/zap-packs${qs ? `?${qs}` : ''}`,\n );\n };\n\n const simulate = async (\n request: SimulateRequest,\n ): Promise<SimulateResult> => {\n const url = `${base}/simulate`;\n const res = await send(transport, url, { method: 'POST', body: request });\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, getChains, compile, route, getZapPacks, simulate };\n};\n"],"mappings":"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"}