@lifi/composer-sdk 0.4.0-staging.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +95 -5
  2. package/dist/authoring/raw.cjs +3 -1
  3. package/dist/authoring/raw.cjs.map +1 -1
  4. package/dist/authoring/raw.js +3 -1
  5. package/dist/authoring/raw.js.map +1 -1
  6. package/dist/client.cjs +26 -24
  7. package/dist/client.cjs.map +1 -1
  8. package/dist/client.d.cts +31 -1
  9. package/dist/client.d.ts +31 -1
  10. package/dist/client.js +26 -24
  11. package/dist/client.js.map +1 -1
  12. package/dist/discovery.cjs.map +1 -1
  13. package/dist/discovery.d.cts +13 -0
  14. package/dist/discovery.d.ts +13 -0
  15. package/dist/generated/config.generated.cjs.map +1 -1
  16. package/dist/generated/config.generated.d.cts +15 -3
  17. package/dist/generated/config.generated.d.ts +15 -3
  18. package/dist/generated/materialisers.generated.cjs +2 -8
  19. package/dist/generated/materialisers.generated.cjs.map +1 -1
  20. package/dist/generated/materialisers.generated.d.cts +1 -1
  21. package/dist/generated/materialisers.generated.d.ts +1 -1
  22. package/dist/generated/materialisers.generated.js +1 -6
  23. package/dist/generated/materialisers.generated.js.map +1 -1
  24. package/dist/generated/operations.generated.cjs +4 -39
  25. package/dist/generated/operations.generated.cjs.map +1 -1
  26. package/dist/generated/operations.generated.d.cts +17 -111
  27. package/dist/generated/operations.generated.d.ts +17 -111
  28. package/dist/generated/operations.generated.js +4 -39
  29. package/dist/generated/operations.generated.js.map +1 -1
  30. package/dist/index.cjs +5 -1
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +23 -4
  33. package/dist/index.d.ts +23 -4
  34. package/dist/index.js +4 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/{materialisers.generated-BfWJ-g7t.d.ts → materialisers.generated-BMkBQgWZ.d.ts} +3 -8
  37. package/dist/{materialisers.generated-DaJRwiRe.d.cts → materialisers.generated-BWq5oePO.d.cts} +3 -8
  38. package/dist/routeAmount-DdbJYJyM.d.cts +55 -0
  39. package/dist/routeAmount-DdbJYJyM.d.ts +55 -0
  40. package/dist/routeAmount.cjs +38 -0
  41. package/dist/routeAmount.cjs.map +1 -0
  42. package/dist/routeAmount.d.cts +2 -0
  43. package/dist/routeAmount.d.ts +2 -0
  44. package/dist/routeAmount.js +13 -0
  45. package/dist/routeAmount.js.map +1 -0
  46. package/dist/sdk.cjs +1 -0
  47. package/dist/sdk.cjs.map +1 -1
  48. package/dist/sdk.d.cts +14 -1
  49. package/dist/sdk.d.ts +14 -1
  50. package/dist/sdk.js +1 -0
  51. package/dist/sdk.js.map +1 -1
  52. package/package.json +7 -6
  53. package/src/authoring/raw.ts +3 -2
  54. package/src/client.ts +75 -23
  55. package/src/discovery.ts +13 -0
  56. package/src/examples/lifiZap.ts +4 -0
  57. package/src/examples/lifiZapAsync.ts +88 -0
  58. package/src/examples/routeTokenPair.ts +58 -0
  59. package/src/examples/run.ts +30 -5
  60. package/src/generated/config.generated.ts +21 -3
  61. package/src/generated/materialisers.generated.ts +0 -12
  62. package/src/generated/operations.generated.ts +29 -221
  63. package/src/index.ts +23 -0
  64. package/src/routeAmount.ts +61 -0
  65. package/src/sdk.ts +17 -0
  66. package/src/examples/staged/aaveBorrow.ts +0 -128
  67. package/src/examples/staged/aaveDebtRebalanceExactOut.ts +0 -301
  68. package/src/examples/staged/aaveHealthFactorExplode.ts +0 -70
  69. package/src/examples/staged/aaveToMorphoDebtMigration.ts +0 -200
  70. package/src/examples/staged/aaveToMorphoDebtMigrationWithSwap.ts +0 -231
  71. package/src/examples/staged/aaveToMorphoDebtMigrationWithSwapExactOut.ts +0 -307
  72. package/src/examples/staged/flashloanRepay.ts +0 -142
  73. package/src/examples/staged/manualRelayContinuation.ts +0 -161
  74. package/src/examples/staged/morphoBlueBorrow.ts +0 -93
  75. package/src/examples/staged/morphoBlueRepay.ts +0 -132
  76. package/src/examples/staged/registry.ts +0 -89
package/dist/sdk.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sdk.ts"],"sourcesContent":["import type {\n ComposeCompileRequest,\n ComposeCompileResult,\n InputSpec,\n SimulateRequest,\n SimulateResult,\n} from '@lifi/compose-spec';\n\nimport {\n createFlowBuilderCore,\n type FlowBuilderCore,\n type FlowOptions,\n type TypedFlow,\n} from './authoring/FlowBuilderCore.js';\nimport { type ComposeClient, createComposeClient } from './client.js';\nimport {\n bindGeneratedOps,\n type GeneratedOps,\n} from './generated/operations.generated.js';\nimport { buildRun, type ComposeRunInput } from './run/inputs.js';\nimport type { InputSchema } from './types.js';\n\nexport type { ComposeRunInput };\n\n/**\n * A flow builder augmented with generated operation methods and a `compile` method\n * that submits the flow to the Compose backend for compilation.\n *\n * Created via {@link ComposeSdk.flow}.\n *\n * @typeParam T - The input schema describing the flow's required inputs.\n */\nexport type FlowBuilder<T extends InputSchema = InputSchema> =\n FlowBuilderCore<T> &\n GeneratedOps & {\n /**\n * Builds the flow document from the current builder state and submits it\n * to the Compose API for compilation.\n *\n * @param run - Runtime inputs, preconditions, and signer address.\n * @returns The compiled transaction calldata and metadata.\n * @throws {@link ComposeError} on network, validation, or server errors.\n *\n * @example\n * ```ts\n * const result = await builder.compile({\n * inputs: { token: materialisers.balanceOf({}) },\n * signer: '0xYourAddress...',\n * });\n * console.log(result.calldata);\n * ```\n */\n readonly compile: (\n run: ComposeRunInput<T>,\n ) => Promise<ComposeCompileResult>;\n };\n\n/**\n * Configuration for creating a Compose SDK instance.\n */\nexport interface ComposeSdkOptions {\n /** Base URL of the Compose API (e.g. `\"https://li.quest\"`). */\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 * The top-level Compose SDK interface.\n *\n * Provides methods to build flows, compile them into executable calldata, and\n * interact with the Compose API directly.\n *\n * @example\n * ```ts\n * import { createComposeSdk, resources, materialisers } from '@lifi/composer-sdk';\n *\n * const sdk = createComposeSdk({ baseUrl: 'https://li.quest', apiKey: 'YOUR_API_KEY' });\n *\n * const builder = sdk.flow(1, {\n * inputs: { usdc: resources.erc20('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 1) },\n * });\n *\n * builder.lifi.swap('swap1', {\n * bind: { amountIn: builder.inputs.usdc },\n * config: { resourceOut: resources.native(1) },\n * });\n *\n * const result = await builder.compile({\n * inputs: { usdc: materialisers.balanceOf({}) },\n * signer: '0xYourAddress...',\n * });\n * ```\n */\nexport interface ComposeSdk {\n /** Low-level HTTP client for the Compose API. */\n readonly client: ComposeClient;\n /**\n * Creates a new flow builder targeting the given chain.\n * @param chainId - The EVM chain ID (e.g. `1` for Ethereum mainnet).\n * @param options - Flow configuration including input declarations.\n * @returns A {@link FlowBuilder} with generated operation methods and a `compile` method.\n * @throws Error if any resource input's `chainId` doesn't match the flow's `chainId`.\n */\n readonly flow: <T extends InputSchema>(\n chainId: number,\n options: FlowOptions<T>,\n ) => FlowBuilder<T>;\n /**\n * Builds a raw compile request without sending it. Useful for inspecting the\n * request payload or submitting it through a custom transport.\n * @param flow - A built flow document (from `builder.build()`).\n * @param run - Runtime inputs, preconditions, and signer address.\n * @returns A {@link ComposeCompileRequest} ready to send to the Compose API.\n */\n readonly request: <T extends InputSchema>(\n flow: TypedFlow<T>,\n run: ComposeRunInput<T>,\n ) => ComposeCompileRequest;\n /**\n * Simulates a raw, pre-encoded transaction. Thin pass-through to\n * {@link ComposeClient.simulate}; see it for the `ok` / `revert` / `error`\n * result discrimination and error behaviour. Pair with `buildSimulateRequest`\n * to assemble the request from a compile result.\n *\n * @param request - The raw transaction plus funding `requirements` and\n * `trackedBalances`.\n * @returns A {@link SimulateResult} (`ok` / `revert` / `error`).\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly simulate: (request: SimulateRequest) => Promise<SimulateResult>;\n}\n\n/**\n * Creates a new Compose SDK instance.\n *\n * @param options - SDK configuration including the API base URL, required API key, and optional fetch implementation.\n * @returns A {@link ComposeSdk} instance.\n *\n * @example\n * ```ts\n * const sdk = createComposeSdk({ baseUrl: 'https://li.quest', apiKey: 'YOUR_API_KEY' });\n * ```\n */\nexport const createComposeSdk = (options: ComposeSdkOptions): ComposeSdk => {\n const composeClient = createComposeClient(options);\n\n const request = <T extends InputSchema>(\n flowDoc: TypedFlow<T>,\n run: ComposeRunInput<T>,\n ): ComposeCompileRequest => ({\n flow: flowDoc,\n run: buildRun({\n inputs: run.inputs as Record<string, InputSpec>,\n preconditions: run.preconditions,\n signer: run.signer,\n assumptions: run.assumptions as Record<string, bigint> | undefined,\n referrer: run.referrer,\n integratorFeeBps: run.integratorFeeBps,\n maxPriceImpactBps: run.maxPriceImpactBps,\n sweepTo: run.sweepTo,\n simulationPolicy: run.simulationPolicy,\n checkOnChainAllowances: run.checkOnChainAllowances,\n }),\n });\n\n const flow = <T extends InputSchema>(\n chainId: number,\n opts: FlowOptions<T>,\n ): FlowBuilder<T> => {\n const core = createFlowBuilderCore(chainId, opts);\n const builder = Object.assign(core, bindGeneratedOps(core));\n\n const compile = async (\n run: ComposeRunInput<T>,\n ): Promise<ComposeCompileResult> => {\n const flowDoc = builder.build();\n const req = request(flowDoc, run);\n return composeClient.compile(req);\n };\n\n return Object.assign(builder, { compile });\n };\n\n return {\n client: composeClient,\n flow,\n request,\n simulate: composeClient.simulate,\n };\n};\n"],"mappings":"AAQA;AAAA,EACE;AAAA,OAIK;AACP,SAA6B,2BAA2B;AACxD;AAAA,EACE;AAAA,OAEK;AACP,SAAS,gBAAsC;AA+HxC,MAAM,mBAAmB,CAAC,YAA2C;AAC1E,QAAM,gBAAgB,oBAAoB,OAAO;AAEjD,QAAM,UAAU,CACd,SACA,SAC2B;AAAA,IAC3B,MAAM;AAAA,IACN,KAAK,SAAS;AAAA,MACZ,QAAQ,IAAI;AAAA,MACZ,eAAe,IAAI;AAAA,MACnB,QAAQ,IAAI;AAAA,MACZ,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,kBAAkB,IAAI;AAAA,MACtB,mBAAmB,IAAI;AAAA,MACvB,SAAS,IAAI;AAAA,MACb,kBAAkB,IAAI;AAAA,MACtB,wBAAwB,IAAI;AAAA,IAC9B,CAAC;AAAA,EACH;AAEA,QAAM,OAAO,CACX,SACA,SACmB;AACnB,UAAM,OAAO,sBAAsB,SAAS,IAAI;AAChD,UAAM,UAAU,OAAO,OAAO,MAAM,iBAAiB,IAAI,CAAC;AAE1D,UAAM,UAAU,OACd,QACkC;AAClC,YAAM,UAAU,QAAQ,MAAM;AAC9B,YAAM,MAAM,QAAQ,SAAS,GAAG;AAChC,aAAO,cAAc,QAAQ,GAAG;AAAA,IAClC;AAEA,WAAO,OAAO,OAAO,SAAS,EAAE,QAAQ,CAAC;AAAA,EAC3C;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,UAAU,cAAc;AAAA,EAC1B;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/sdk.ts"],"sourcesContent":["import type {\n ComposeCompileRequest,\n ComposeCompileResult,\n ComposeRouteRequest,\n InputSpec,\n SimulateRequest,\n SimulateResult,\n} from '@lifi/compose-spec';\n\nimport {\n createFlowBuilderCore,\n type FlowBuilderCore,\n type FlowOptions,\n type TypedFlow,\n} from './authoring/FlowBuilderCore.js';\nimport { type ComposeClient, createComposeClient } from './client.js';\nimport {\n bindGeneratedOps,\n type GeneratedOps,\n} from './generated/operations.generated.js';\nimport { buildRun, type ComposeRunInput } from './run/inputs.js';\nimport type { InputSchema } from './types.js';\n\nexport type { ComposeRunInput };\n\n/**\n * A flow builder augmented with generated operation methods and a `compile` method\n * that submits the flow to the Compose backend for compilation.\n *\n * Created via {@link ComposeSdk.flow}.\n *\n * @typeParam T - The input schema describing the flow's required inputs.\n */\nexport type FlowBuilder<T extends InputSchema = InputSchema> =\n FlowBuilderCore<T> &\n GeneratedOps & {\n /**\n * Builds the flow document from the current builder state and submits it\n * to the Compose API for compilation.\n *\n * @param run - Runtime inputs, preconditions, and signer address.\n * @returns The compiled transaction calldata and metadata.\n * @throws {@link ComposeError} on network, validation, or server errors.\n *\n * @example\n * ```ts\n * const result = await builder.compile({\n * inputs: { token: materialisers.balanceOf({}) },\n * signer: '0xYourAddress...',\n * });\n * console.log(result.calldata);\n * ```\n */\n readonly compile: (\n run: ComposeRunInput<T>,\n ) => Promise<ComposeCompileResult>;\n };\n\n/**\n * Configuration for creating a Compose SDK instance.\n */\nexport interface ComposeSdkOptions {\n /** Base URL of the Compose API (e.g. `\"https://li.quest\"`). */\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 * The top-level Compose SDK interface.\n *\n * Provides methods to build flows, compile them into executable calldata, and\n * interact with the Compose API directly.\n *\n * @example\n * ```ts\n * import { createComposeSdk, resources, materialisers } from '@lifi/composer-sdk';\n *\n * const sdk = createComposeSdk({ baseUrl: 'https://li.quest', apiKey: 'YOUR_API_KEY' });\n *\n * const builder = sdk.flow(1, {\n * inputs: { usdc: resources.erc20('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 1) },\n * });\n *\n * builder.lifi.swap('swap1', {\n * bind: { amountIn: builder.inputs.usdc },\n * config: { resourceOut: resources.native(1) },\n * });\n *\n * const result = await builder.compile({\n * inputs: { usdc: materialisers.balanceOf({}) },\n * signer: '0xYourAddress...',\n * });\n * ```\n */\nexport interface ComposeSdk {\n /** Low-level HTTP client for the Compose API. */\n readonly client: ComposeClient;\n /**\n * Creates a new flow builder targeting the given chain.\n * @param chainId - The EVM chain ID (e.g. `1` for Ethereum mainnet).\n * @param options - Flow configuration including input declarations.\n * @returns A {@link FlowBuilder} with generated operation methods and a `compile` method.\n * @throws Error if any resource input's `chainId` doesn't match the flow's `chainId`.\n */\n readonly flow: <T extends InputSchema>(\n chainId: number,\n options: FlowOptions<T>,\n ) => FlowBuilder<T>;\n /**\n * Builds a raw compile request without sending it. Useful for inspecting the\n * request payload or submitting it through a custom transport.\n * @param flow - A built flow document (from `builder.build()`).\n * @param run - Runtime inputs, preconditions, and signer address.\n * @returns A {@link ComposeCompileRequest} ready to send to the Compose API.\n */\n readonly request: <T extends InputSchema>(\n flow: TypedFlow<T>,\n run: ComposeRunInput<T>,\n ) => ComposeCompileRequest;\n /**\n * Compiles a token pair on one chain — as of today a single zap step over\n * one routing-catalog edge. Thin pass-through to\n * {@link ComposeClient.route}; see it for the current single-step limit,\n * when to prefer `flow(...).compile(...)`, the `success` / `partial`\n * discrimination, and error behaviour. Build the `amount` with\n * `routeAmount.exact(...)` / `routeAmount.all(...)`.\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 */\n readonly route: (\n request: ComposeRouteRequest,\n ) => Promise<ComposeCompileResult>;\n /**\n * Simulates a raw, pre-encoded transaction. Thin pass-through to\n * {@link ComposeClient.simulate}; see it for the `ok` / `revert` / `error`\n * result discrimination and error behaviour. Pair with `buildSimulateRequest`\n * to assemble the request from a compile result.\n *\n * @param request - The raw transaction plus funding `requirements` and\n * `trackedBalances`.\n * @returns A {@link SimulateResult} (`ok` / `revert` / `error`).\n * @throws {@link ComposeError} on network, validation, or server errors.\n */\n readonly simulate: (request: SimulateRequest) => Promise<SimulateResult>;\n}\n\n/**\n * Creates a new Compose SDK instance.\n *\n * @param options - SDK configuration including the API base URL, required API key, and optional fetch implementation.\n * @returns A {@link ComposeSdk} instance.\n *\n * @example\n * ```ts\n * const sdk = createComposeSdk({ baseUrl: 'https://li.quest', apiKey: 'YOUR_API_KEY' });\n * ```\n */\nexport const createComposeSdk = (options: ComposeSdkOptions): ComposeSdk => {\n const composeClient = createComposeClient(options);\n\n const request = <T extends InputSchema>(\n flowDoc: TypedFlow<T>,\n run: ComposeRunInput<T>,\n ): ComposeCompileRequest => ({\n flow: flowDoc,\n run: buildRun({\n inputs: run.inputs as Record<string, InputSpec>,\n preconditions: run.preconditions,\n signer: run.signer,\n assumptions: run.assumptions as Record<string, bigint> | undefined,\n referrer: run.referrer,\n integratorFeeBps: run.integratorFeeBps,\n maxPriceImpactBps: run.maxPriceImpactBps,\n sweepTo: run.sweepTo,\n simulationPolicy: run.simulationPolicy,\n checkOnChainAllowances: run.checkOnChainAllowances,\n }),\n });\n\n const flow = <T extends InputSchema>(\n chainId: number,\n opts: FlowOptions<T>,\n ): FlowBuilder<T> => {\n const core = createFlowBuilderCore(chainId, opts);\n const builder = Object.assign(core, bindGeneratedOps(core));\n\n const compile = async (\n run: ComposeRunInput<T>,\n ): Promise<ComposeCompileResult> => {\n const flowDoc = builder.build();\n const req = request(flowDoc, run);\n return composeClient.compile(req);\n };\n\n return Object.assign(builder, { compile });\n };\n\n return {\n client: composeClient,\n flow,\n request,\n route: composeClient.route,\n simulate: composeClient.simulate,\n };\n};\n"],"mappings":"AASA;AAAA,EACE;AAAA,OAIK;AACP,SAA6B,2BAA2B;AACxD;AAAA,EACE;AAAA,OAEK;AACP,SAAS,gBAAsC;AA8IxC,MAAM,mBAAmB,CAAC,YAA2C;AAC1E,QAAM,gBAAgB,oBAAoB,OAAO;AAEjD,QAAM,UAAU,CACd,SACA,SAC2B;AAAA,IAC3B,MAAM;AAAA,IACN,KAAK,SAAS;AAAA,MACZ,QAAQ,IAAI;AAAA,MACZ,eAAe,IAAI;AAAA,MACnB,QAAQ,IAAI;AAAA,MACZ,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,kBAAkB,IAAI;AAAA,MACtB,mBAAmB,IAAI;AAAA,MACvB,SAAS,IAAI;AAAA,MACb,kBAAkB,IAAI;AAAA,MACtB,wBAAwB,IAAI;AAAA,IAC9B,CAAC;AAAA,EACH;AAEA,QAAM,OAAO,CACX,SACA,SACmB;AACnB,UAAM,OAAO,sBAAsB,SAAS,IAAI;AAChD,UAAM,UAAU,OAAO,OAAO,MAAM,iBAAiB,IAAI,CAAC;AAE1D,UAAM,UAAU,OACd,QACkC;AAClC,YAAM,UAAU,QAAQ,MAAM;AAC9B,YAAM,MAAM,QAAQ,SAAS,GAAG;AAChC,aAAO,cAAc,QAAQ,GAAG;AAAA,IAClC;AAEA,WAAO,OAAO,OAAO,SAAS,EAAE,QAAQ,CAAC;AAAA,EAC3C;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,OAAO,cAAc;AAAA,IACrB,UAAU,cAAc;AAAA,EAC1B;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/composer-sdk",
3
- "version": "0.4.0-staging.1",
3
+ "version": "0.4.1",
4
4
  "description": "Public Composer SDK for building and submitting flows",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "example:staged": "tsx src/examples/run.ts --staged"
52
52
  },
53
53
  "peerDependencies": {
54
- "@lifi/compose-spec": "0.4.0-staging.1"
54
+ "@lifi/compose-spec": "0.4.1"
55
55
  },
56
56
  "dependencies": {
57
57
  "abitype": "^1.2.3",
@@ -62,8 +62,8 @@
62
62
  "@eslint/compat": "^1.4.0",
63
63
  "@lifi/compose-spec": "workspace:^",
64
64
  "@types/node": "^25.6.0",
65
- "@typescript-eslint/eslint-plugin": "^8.44.1",
66
- "@typescript-eslint/parser": "^8.44.1",
65
+ "@typescript-eslint/eslint-plugin": "^8.68.0",
66
+ "@typescript-eslint/parser": "^8.68.0",
67
67
  "eslint": "^9.36.0",
68
68
  "eslint-config-prettier": "^9.0.0",
69
69
  "eslint-plugin-eslint-comments": "^3.2.0",
@@ -74,7 +74,7 @@
74
74
  "prettier": "^3.9.4",
75
75
  "tsup": "^8.5.1",
76
76
  "tsx": "^4.20.4",
77
- "typescript": "^5.9.2",
77
+ "typescript": "^6.0.3",
78
78
  "vitest": "^3.2.4"
79
79
  },
80
80
  "files": [
@@ -85,6 +85,7 @@
85
85
  "!src/**/*.test.ts",
86
86
  "!src/**/*.mock.ts",
87
87
  "!src/**/*.typecheck.ts",
88
- "!dist/**/*.typecheck.*"
88
+ "!dist/**/*.typecheck.*",
89
+ "!src/examples/staged/**"
89
90
  ]
90
91
  }
@@ -35,8 +35,9 @@ import type { OutputKind, TypedRef } from '../types.js';
35
35
  * });
36
36
  * ```
37
37
  */
38
- export const ref = <T extends OutputKind>(path: string): TypedRef<T> =>
39
- ({ $ref: path }) as TypedRef<T>;
38
+ export const ref = <T extends OutputKind>(path: string): TypedRef<T> => ({
39
+ $ref: path,
40
+ });
40
41
 
41
42
  /**
42
43
  * Creates a raw guard object for use in the `guards` array of an operation call.
package/src/client.ts CHANGED
@@ -4,6 +4,7 @@ import type {
4
4
  ComposeCompileResult,
5
5
  ComposeCompileSuccessData,
6
6
  ComposeManifest,
7
+ ComposeRouteRequest,
7
8
  SimulateRequest,
8
9
  SimulateResult,
9
10
  } from '@lifi/compose-spec';
@@ -69,6 +70,38 @@ export interface ComposeClient {
69
70
  readonly compile: (
70
71
  request: ComposeCompileRequest,
71
72
  ) => Promise<ComposeCompileResult>;
73
+ /**
74
+ * Compiles a token pair into a submit-ready transaction via
75
+ * `POST /compose/route`.
76
+ *
77
+ * A convenience path that skips flow authoring: the server builds a one-node
78
+ * `lifi.zap` flow with a `directDeposit` input from the `fromToken` /
79
+ * `toToken` pair and runs it through the same pipeline as {@link compile}.
80
+ * The response is therefore the same discriminated result — HTTP 206 under
81
+ * `simulationPolicy: 'allow-revert'` yields `status: 'partial'` rather than
82
+ * throwing, exactly as it does for {@link compile}.
83
+ *
84
+ * As of today that flow is a single zap step: one edge of the backend's
85
+ * routing catalog (a position entry or exit, a wrap or unwrap, a mint or
86
+ * burn). Enumerate the pairs it covers with {@link getZapPacks}; a pair with
87
+ * no edge is rejected with `kind: 'no_route_error'`. The endpoint is meant to
88
+ * author richer flows later, so the single-step limit is the current state of
89
+ * the server, not the shape of this contract.
90
+ *
91
+ * Use {@link compile} for what the server does not author for you: several
92
+ * chained operations, splits, explicit preconditions, or per-op guards.
93
+ *
94
+ * `bigint` amounts in `amount` are serialised to decimal strings
95
+ * automatically.
96
+ *
97
+ * @param request - The from/to token pair, amount, signer, and route options.
98
+ * @returns A discriminated result: `status: 'success'` or `status: 'partial'`.
99
+ * @throws {@link ComposeError} on network, validation, or server errors —
100
+ * including `NOT_FOUND` (HTTP 404) when no catalog edge covers the pair.
101
+ */
102
+ readonly route: (
103
+ request: ComposeRouteRequest,
104
+ ) => Promise<ComposeCompileResult>;
72
105
  /**
73
106
  * Fetches the available routing edges grouped by protocol.
74
107
  *
@@ -173,6 +206,39 @@ const parseSimulateBody = async (
173
206
  }
174
207
  return result;
175
208
  };
209
+ // `POST /compose` and `POST /compose/route` differ only in path and request
210
+ // shape: both return the enveloped success data on 200 and the partial
211
+ // envelope on 206, so they share one transport.
212
+ type ComposeTransport = {
213
+ readonly fetchFn: typeof fetch;
214
+ readonly baseHeaders: Record<string, string>;
215
+ };
216
+
217
+ const postCompile = async (
218
+ { fetchFn, baseHeaders }: ComposeTransport,
219
+ url: string,
220
+ request: ComposeCompileRequest | ComposeRouteRequest,
221
+ ): Promise<ComposeCompileResult> => {
222
+ let res: Response;
223
+ try {
224
+ res = await fetchFn(url, {
225
+ method: 'POST',
226
+ headers: { ...baseHeaders, 'Content-Type': 'application/json' },
227
+ body: JSON.stringify(request, bigintReplacer),
228
+ });
229
+ } catch (err) {
230
+ const message = err instanceof Error ? err.message : String(err);
231
+ throw new ComposeError('NETWORK_ERROR', message, { cause: err });
232
+ }
233
+ if (res.status === 206) {
234
+ return await parsePartialBody(res, url);
235
+ }
236
+ if (!res.ok) {
237
+ const body = await res.text();
238
+ throw errorFromHttpResponse(res.status, body, url);
239
+ }
240
+ return await parseCompileSuccessBody(res, url);
241
+ };
176
242
 
177
243
  /**
178
244
  * Creates a low-level Compose API client.
@@ -204,6 +270,7 @@ export const createComposeClient = (
204
270
  'x-lifi-composer-sdk': SDK_VERSION,
205
271
  'x-lifi-api-key': trimmedApiKey,
206
272
  };
273
+ const transport: ComposeTransport = { fetchFn, baseHeaders };
207
274
 
208
275
  const getManifest = async (): Promise<ComposeManifest> => {
209
276
  const url = `${base}/compose/manifest`;
@@ -226,28 +293,13 @@ export const createComposeClient = (
226
293
 
227
294
  const compile = async (
228
295
  request: ComposeCompileRequest,
229
- ): Promise<ComposeCompileResult> => {
230
- const url = `${base}/compose`;
231
- let res: Response;
232
- try {
233
- res = await fetchFn(url, {
234
- method: 'POST',
235
- headers: { ...baseHeaders, 'Content-Type': 'application/json' },
236
- body: JSON.stringify(request, bigintReplacer),
237
- });
238
- } catch (err) {
239
- const message = err instanceof Error ? err.message : String(err);
240
- throw new ComposeError('NETWORK_ERROR', message, { cause: err });
241
- }
242
- if (res.status === 206) {
243
- return await parsePartialBody(res, url);
244
- }
245
- if (!res.ok) {
246
- const body = await res.text();
247
- throw errorFromHttpResponse(res.status, body, url);
248
- }
249
- return await parseCompileSuccessBody(res, url);
250
- };
296
+ ): Promise<ComposeCompileResult> =>
297
+ postCompile(transport, `${base}/compose`, request);
298
+
299
+ const route = async (
300
+ request: ComposeRouteRequest,
301
+ ): Promise<ComposeCompileResult> =>
302
+ postCompile(transport, `${base}/compose/route`, request);
251
303
 
252
304
  const getZapPacks = async (
253
305
  options?: GetZapPacksOptions,
@@ -306,5 +358,5 @@ export const createComposeClient = (
306
358
  throw errorFromHttpResponse(res.status, body, url);
307
359
  };
308
360
 
309
- return { getManifest, compile, getZapPacks, simulate };
361
+ return { getManifest, compile, route, getZapPacks, simulate };
310
362
  };
package/src/discovery.ts CHANGED
@@ -26,6 +26,19 @@ export interface ZapPackEdge {
26
26
  readonly inAmountMin?: string;
27
27
  /** Maximum input amount accepted (stringified integer), if constrained. */
28
28
  readonly inAmountMax?: string;
29
+ /**
30
+ * Recipient binding obligation. `"required"` means the zap must bind its
31
+ * `recipient` input. The output is externally owned, terminal, and unswept.
32
+ * The loose string type preserves forward compatibility with new values.
33
+ */
34
+ readonly recipient?: string;
35
+ /**
36
+ * Output availability. `"future"` means the output becomes available only
37
+ * after the initiating transaction settles; route via `lifi.zapAsync` with
38
+ * a bound recipient. Absent means the output is available synchronously.
39
+ * The loose string type preserves forward compatibility with new values.
40
+ */
41
+ readonly availability?: string;
29
42
  }
30
43
 
31
44
  /**
@@ -53,6 +53,10 @@ export const buildLifiZapExample = (): {
53
53
  inputs: {
54
54
  amountIn: materialisers.directDeposit({ amount: '1000000000' }),
55
55
  },
56
+ // This zap's edge keeps `amountOut` inside the execution VM, so sweeping
57
+ // leftovers back to the sender is safe. Omit `sweepTo` for an edge that
58
+ // delivers externally (`recipient: 'required'` in discovery): the output
59
+ // never enters the VM, so a sweep would find nothing to move.
56
60
  sweepTo: builder.context.sender,
57
61
  });
58
62
 
@@ -0,0 +1,88 @@
1
+ import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
2
+
3
+ import { createComposeSdk, materialisers, resources } from '../index.js';
4
+ import type { Address } from '../types.js';
5
+
6
+ import { API_KEY, BASE_URL } from './config.js';
7
+
8
+ const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
9
+ // Aave aEthUSDC receipt token on Ethereum mainnet
10
+ const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c';
11
+
12
+ export interface LifiZapAsyncInput {
13
+ readonly owner: Address;
14
+ readonly recipient: Address;
15
+ }
16
+
17
+ /**
18
+ * Zap USDC into a DeFi position whose output settles asynchronously via LI.FI.
19
+ *
20
+ * Demonstrates:
21
+ * - `lifi.zapAsync`, the future-availability sibling of `lifi.zap`
22
+ * - Binding the required `recipient` input (the async op has no optional
23
+ * recipient — the decl is non-optional, so `bind.recipient` is mandatory)
24
+ * - Omitting `sweepTo`: `availability: 'future'` governs custody, so the
25
+ * output is delivered externally and never enters the execution VM — a
26
+ * sweep would find nothing to move (custody, not amount reporting)
27
+ * - Amount reporting is unaffected by availability: when the async lowering
28
+ * binds a terminal handle the output still reports `simulated.amountOut`,
29
+ * the protocol's in-transaction expected value (an estimate; the delivered
30
+ * amount arrives after settlement). `simulated.amountOutMin` appears only
31
+ * when a minimum source exists — for a future output that means a floor the
32
+ * edge lowering itself reported, never a user guard
33
+ * - No output guards: the reported value is the initiating-transaction
34
+ * estimate, not the asynchronously delivered amount a guard could enforce,
35
+ * so `amountOut` is excluded from the generated `TypedGuard` union
36
+ * (`guards` typechecks as `TypedGuard<never>[]`) and the compiler rejects
37
+ * any guard on it
38
+ */
39
+ export const buildLifiZapAsyncExample = ({
40
+ owner,
41
+ recipient,
42
+ }: LifiZapAsyncInput): {
43
+ flow: Flow;
44
+ request: ComposeCompileRequest;
45
+ } => {
46
+ const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
47
+
48
+ // Declare the flow with a single USDC input and the recipient address input.
49
+ const builder = sdk.flow(1, {
50
+ name: 'zap-async-usdc-to-aave',
51
+ inputs: {
52
+ amountIn: resources.erc20(USDC, 1),
53
+ recipient: 'address',
54
+ },
55
+ });
56
+
57
+ // Zap USDC into an async Aave position via LI.FI.
58
+ // The future output is delivered to `recipient` once the initiating
59
+ // transaction settles; the recipient binding is required by the op.
60
+ builder.lifi.zapAsync('zap', {
61
+ bind: {
62
+ amountIn: builder.inputs.amountIn,
63
+ recipient: builder.inputs.recipient,
64
+ },
65
+ config: {
66
+ resourceOut: resources.erc20(A_ETH_USDC, 1),
67
+ },
68
+ });
69
+
70
+ const flow = builder.build();
71
+
72
+ // Build the compile request.
73
+ // directDeposit transfers a fixed amount of USDC into the VM; the recipient
74
+ // handle passes the delivery address as a plain string.
75
+ // No `sweepTo`: the async output is delivered externally to the recipient,
76
+ // never landing in the VM for a sweep to reach.
77
+ // NOTE: no production edge sets `availability: 'future'` yet, so this request
78
+ // resolves to a typed no_route today; it documents the intended shape.
79
+ const request = sdk.request(flow, {
80
+ signer: owner,
81
+ inputs: {
82
+ amountIn: materialisers.directDeposit({ amount: '1000000000' }),
83
+ recipient,
84
+ },
85
+ });
86
+
87
+ return { flow, request };
88
+ };
@@ -0,0 +1,58 @@
1
+ import type { ComposeRouteRequest } from '@lifi/compose-spec';
2
+
3
+ import { routeAmount } from '../index.js';
4
+
5
+ import { OWNER } from './config.js';
6
+
7
+ const NATIVE_ETH = '0x0000000000000000000000000000000000000000';
8
+ const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
9
+
10
+ /**
11
+ * Wrap 0.1 native ETH into WETH on Ethereum mainnet through
12
+ * `POST /compose/route`.
13
+ *
14
+ * Demonstrates:
15
+ * - The convenience path: a from/to token pair instead of an authored flow.
16
+ * The pair resolves to the catalog's `wrap` edge, and the server authors the
17
+ * zap step for it
18
+ * - `routeAmount.exact` for a fixed input amount (`bigint`, serialised on send)
19
+ * - The native sentinel address as `fromToken`
20
+ *
21
+ * Pure: returns a plain {@link ComposeRouteRequest}, performs no network I/O.
22
+ * Pass it to `sdk.route(...)` / `sdk.client.route(...)` to compile it.
23
+ */
24
+ export const buildRouteExactExample = (): ComposeRouteRequest => ({
25
+ chainId: 1,
26
+ fromToken: NATIVE_ETH,
27
+ toToken: WETH,
28
+ amount: routeAmount.exact(100_000_000_000_000_000n), // 0.1 ETH
29
+ signer: OWNER,
30
+ slippageBps: 100,
31
+ });
32
+
33
+ /**
34
+ * Unwrap the signer's entire WETH balance back into native ETH on Ethereum
35
+ * mainnet — the catalog's `unwrap` edge.
36
+ *
37
+ * Demonstrates:
38
+ * - `routeAmount.all` for a balance resolved on-chain at execution time, where
39
+ * the passed value is only the quoting stand-in
40
+ * - `sweepTo` naming the address that receives the output
41
+ * - `checkOnChainAllowances` so already-sufficient approvals are omitted from
42
+ * the response
43
+ *
44
+ * Pure: returns a plain {@link ComposeRouteRequest}, performs no network I/O.
45
+ */
46
+ export const buildRouteAllExample = (): ComposeRouteRequest => ({
47
+ chainId: 1,
48
+ fromToken: WETH,
49
+ toToken: NATIVE_ETH,
50
+ // Estimate of the balance on hand — the quote and the derived preconditions
51
+ // are computed against this, but the amount actually moved is the real
52
+ // balance at execution time.
53
+ amount: routeAmount.all(10n ** 18n), // ~1 WETH
54
+ signer: OWNER,
55
+ sweepTo: OWNER,
56
+ slippageBps: 50,
57
+ checkOnChainAllowances: true,
58
+ });
@@ -20,6 +20,7 @@
20
20
  * split-zap — split USDC 60/40 and zap into Aave + Morpho vaults
21
21
  * split-arithmetic — split USDC 70/30 with arithmetic assertions
22
22
  * zap — zap USDC into Aave lending position
23
+ * zap-async — zap USDC into an async Aave position (future output)
23
24
  * swap-zap — swap WETH to USDC then zap into Aave
24
25
  * swap-check — swap WETH to USDC with balance check
25
26
  * swap-recipient — swap WETH + DAI to USDC and send to recipient
@@ -32,8 +33,13 @@
32
33
  * transfer — transfer full token balance to a recipient
33
34
  * partial-transfer — transfer a specific amount, keeping the remainder
34
35
  * untyped-ref — mix untypedOp with typed handles via raw.ref
36
+ * route-exact — wrap 0.1 native ETH into WETH via /compose/route
37
+ * route-all — unwrap the whole WETH balance to ETH via /compose/route
35
38
  */
36
- import type { ComposeCompileRequest } from '@lifi/compose-spec';
39
+ import type {
40
+ ComposeCompileRequest,
41
+ ComposeRouteRequest,
42
+ } from '@lifi/compose-spec';
37
43
 
38
44
  import { createComposeSdk } from '../sdk.js';
39
45
 
@@ -49,8 +55,13 @@ import { buildDepositFromProxy } from './depositFromProxy.js';
49
55
  import { buildDustSweepExample } from './dustSweep.js';
50
56
  import { buildLifiSwapExample } from './lifiSwap.js';
51
57
  import { buildLifiZapExample } from './lifiZap.js';
58
+ import { buildLifiZapAsyncExample } from './lifiZapAsync.js';
52
59
  import { buildRawCallWithArithmetic } from './rawCallWithArithmetic.js';
53
60
  import { buildReadContractState } from './readContractState.js';
61
+ import {
62
+ buildRouteAllExample,
63
+ buildRouteExactExample,
64
+ } from './routeTokenPair.js';
54
65
  import { buildSplitAndZapExample } from './splitAndZap.js';
55
66
  import { buildSplitWithArithmetic } from './splitWithArithmetic.js';
56
67
  import { buildSwapAndZapExample } from './swapAndZap.js';
@@ -106,6 +117,8 @@ const EXAMPLES: Record<string, () => ComposeCompileRequest> = {
106
117
  'split-arithmetic': () =>
107
118
  buildSplitWithArithmetic({ owner: OWNER, amount: '1000000000' }).request,
108
119
  zap: () => buildLifiZapExample().request,
120
+ 'zap-async': () =>
121
+ buildLifiZapAsyncExample({ owner: OWNER, recipient: RECIPIENT }).request,
109
122
  'swap-zap': () => buildSwapAndZapExample().request,
110
123
  'swap-check': () =>
111
124
  buildSwapWithBalanceCheck({ owner: OWNER, amount: '1000000000000000000' })
@@ -146,6 +159,13 @@ const EXAMPLES: Record<string, () => ComposeCompileRequest> = {
146
159
  'untyped-ref': () => buildUntypedOpWithTypedRef({ owner: OWNER }).request,
147
160
  };
148
161
 
162
+ // `/compose/route` examples build a `ComposeRouteRequest` and go to
163
+ // `sdk.route`, not `sdk.client.compile`, so they live in their own registry.
164
+ const ROUTE_EXAMPLES: Record<string, () => ComposeRouteRequest> = {
165
+ 'route-exact': buildRouteExactExample,
166
+ 'route-all': buildRouteAllExample,
167
+ };
168
+
149
169
  const run = async () => {
150
170
  const args = process.argv.slice(2);
151
171
  const useStaged = args.includes('--staged');
@@ -166,10 +186,13 @@ const run = async () => {
166
186
  examples = { ...EXAMPLES, ...stagedModule.STAGED_EXAMPLES };
167
187
  }
168
188
 
189
+ const routeBuilder = name ? ROUTE_EXAMPLES[name] : undefined;
169
190
  const builder = name ? examples[name] : undefined;
170
191
 
171
- if (!builder) {
172
- const valid = Object.keys(examples).join(', ');
192
+ if (!builder && !routeBuilder) {
193
+ const valid = [...Object.keys(examples), ...Object.keys(ROUTE_EXAMPLES)]
194
+ .sort()
195
+ .join(', ');
173
196
  console.error(
174
197
  `Error: ${
175
198
  name ? `unknown example "${name}"` : 'no example specified'
@@ -181,7 +204,7 @@ const run = async () => {
181
204
  }
182
205
  console.log(`Running example: ${name}`);
183
206
 
184
- const request = builder();
207
+ const request = routeBuilder ? routeBuilder() : builder!();
185
208
  const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
186
209
 
187
210
  console.log('--- Request ---');
@@ -195,7 +218,9 @@ const run = async () => {
195
218
  );
196
219
 
197
220
  console.log(`\n--- Compiling against ${BASE_URL} ---`);
198
- const result = await sdk.client.compile(request);
221
+ const result = routeBuilder
222
+ ? await sdk.route(request as ComposeRouteRequest)
223
+ : await sdk.client.compile(request as ComposeCompileRequest);
199
224
 
200
225
  console.log('\n--- Result ---');
201
226
  console.log(JSON.stringify(result, null, 2));
@@ -203,9 +203,14 @@ export interface CoreCallConfig {
203
203
  )[]
204
204
  )[]
205
205
  )[];
206
- approvals?: {
207
- spender: Address;
208
- }[];
206
+ approvals?: (
207
+ | {
208
+ spender: Address;
209
+ }
210
+ | {
211
+ from: 'sender';
212
+ }
213
+ )[];
209
214
  }
210
215
 
211
216
  export interface CoreStaticCallConfig {
@@ -684,6 +689,19 @@ export interface LifiZapConfig {
684
689
  };
685
690
  }
686
691
 
692
+ export interface LifiZapAsyncConfig {
693
+ resourceOut:
694
+ | {
695
+ kind: 'native';
696
+ chainId: Positive;
697
+ }
698
+ | {
699
+ kind: 'erc20';
700
+ token: Address;
701
+ chainId: Positive;
702
+ };
703
+ }
704
+
687
705
  export interface ParaswapBuyConfig {
688
706
  resourceOut:
689
707
  | {
@@ -10,7 +10,6 @@ import type {
10
10
  BalanceOfMaterialiserConfig as BalanceOfMaterialiserConfigSchema,
11
11
  CallMaterialiserConfig as CallMaterialiserConfigSchema,
12
12
  DirectDepositMaterialiserConfig as DirectDepositMaterialiserConfigSchema,
13
- FlashloanMaterialiserConfig as FlashloanMaterialiserConfigSchema,
14
13
  } from './config.generated.js';
15
14
 
16
15
  export type BalanceOfMaterialiserConfig =
@@ -43,14 +42,3 @@ export const call = (config: CallMaterialiserConfig): MaterialiserInput => ({
43
42
  kind: 'call',
44
43
  ...config,
45
44
  });
46
-
47
- export type FlashloanMaterialiserConfig =
48
- MaterialiserConfigOf<FlashloanMaterialiserConfigSchema>;
49
-
50
- /** Borrow tokens from a flashloan provider; amount becomes a register populated by the proxy at callback time — accepts: resource */
51
- export const flashloan = (
52
- config: FlashloanMaterialiserConfig,
53
- ): MaterialiserInput => ({
54
- kind: 'flashloan',
55
- ...config,
56
- });