@lifi/composer-sdk 0.2.0-staging.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/dist/client.cjs +38 -6
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +25 -1
- package/dist/client.d.ts +25 -1
- package/dist/client.js +41 -6
- package/dist/client.js.map +1 -1
- package/dist/errors.cjs +4 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +6 -1
- package/dist/errors.js.map +1 -1
- package/dist/generated/config.generated.cjs.map +1 -1
- package/dist/generated/config.generated.d.cts +44 -7
- package/dist/generated/config.generated.d.ts +44 -7
- package/dist/generated/materialisers.generated.cjs +2 -8
- package/dist/generated/materialisers.generated.cjs.map +1 -1
- package/dist/generated/materialisers.generated.d.cts +1 -1
- package/dist/generated/materialisers.generated.d.ts +1 -1
- package/dist/generated/materialisers.generated.js +1 -6
- package/dist/generated/materialisers.generated.js.map +1 -1
- package/dist/generated/operations.generated.cjs +1 -30
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +2 -94
- package/dist/generated/operations.generated.d.ts +2 -94
- package/dist/generated/operations.generated.js +1 -30
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/{materialisers.generated-BfWJ-g7t.d.ts → materialisers.generated-BMkBQgWZ.d.ts} +3 -8
- package/dist/{materialisers.generated-DaJRwiRe.d.cts → materialisers.generated-BWq5oePO.d.cts} +3 -8
- package/dist/responseSchemas.cjs +110 -0
- package/dist/responseSchemas.cjs.map +1 -0
- package/dist/responseSchemas.d.cts +36 -0
- package/dist/responseSchemas.d.ts +36 -0
- package/dist/responseSchemas.js +74 -0
- package/dist/responseSchemas.js.map +1 -0
- package/dist/run/simulate.cjs +41 -0
- package/dist/run/simulate.cjs.map +1 -0
- package/dist/run/simulate.d.cts +59 -0
- package/dist/run/simulate.d.ts +59 -0
- package/dist/run/simulate.js +17 -0
- package/dist/run/simulate.js.map +1 -0
- package/dist/sdk.cjs +6 -1
- package/dist/sdk.cjs.map +1 -1
- package/dist/sdk.d.cts +13 -1
- package/dist/sdk.d.ts +13 -1
- package/dist/sdk.js +6 -1
- package/dist/sdk.js.map +1 -1
- package/package.json +7 -5
- package/src/client.ts +86 -11
- package/src/errors.ts +8 -10
- package/src/examples/simulateCompiledSwap.ts +76 -0
- package/src/examples/simulateRawTransaction.ts +39 -0
- package/src/generated/config.generated.ts +179 -6
- package/src/generated/materialisers.generated.ts +0 -12
- package/src/generated/operations.generated.ts +0 -177
- package/src/index.ts +19 -0
- package/src/responseSchemas.ts +143 -0
- package/src/run/simulate.ts +78 -0
- package/src/sdk.ts +20 -1
- package/src/examples/staged/aaveBorrow.ts +0 -128
- package/src/examples/staged/aaveDebtRebalanceExactOut.ts +0 -301
- package/src/examples/staged/aaveHealthFactorExplode.ts +0 -70
- package/src/examples/staged/aaveToMorphoDebtMigration.ts +0 -200
- package/src/examples/staged/aaveToMorphoDebtMigrationWithSwap.ts +0 -231
- package/src/examples/staged/aaveToMorphoDebtMigrationWithSwapExactOut.ts +0 -307
- package/src/examples/staged/flashloanRepay.ts +0 -142
- package/src/examples/staged/morphoBlueBorrow.ts +0 -93
- package/src/examples/staged/morphoBlueRepay.ts +0 -132
- package/src/examples/staged/registry.ts +0 -80
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} 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 /** Optional LI.FI API key for authenticated access. Sent as the `x-lifi-api-key` header on every request. */\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' });\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\n/**\n * Creates a new Compose SDK instance.\n *\n * @param options - SDK configuration including the API base URL and optional fetch implementation.\n * @returns A {@link ComposeSdk} instance.\n *\n * @example\n * ```ts\n * const sdk = createComposeSdk({ baseUrl: 'https://li.quest' });\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 {
|
|
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 /** Optional LI.FI API key for authenticated access. Sent as the `x-lifi-api-key` header on every request. */\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' });\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 and optional fetch implementation.\n * @returns A {@link ComposeSdk} instance.\n *\n * @example\n * ```ts\n * const sdk = createComposeSdk({ baseUrl: 'https://li.quest' });\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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifi/composer-sdk",
|
|
3
|
-
"version": "0.2.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Public Composer SDK for building and submitting flows",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"./package.json": "./package.json"
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
26
|
+
"node": ">=24"
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
@@ -51,11 +51,12 @@
|
|
|
51
51
|
"example:staged": "tsx src/examples/run.ts --staged"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@lifi/compose-spec": "0.2.0
|
|
54
|
+
"@lifi/compose-spec": "0.2.0"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"abitype": "^1.2.3",
|
|
58
|
-
"effect": "^3.21.0"
|
|
58
|
+
"effect": "^3.21.0",
|
|
59
|
+
"zod": "^4.1.11"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@eslint/compat": "^1.4.0",
|
|
@@ -84,6 +85,7 @@
|
|
|
84
85
|
"!src/**/*.test.ts",
|
|
85
86
|
"!src/**/*.mock.ts",
|
|
86
87
|
"!src/**/*.typecheck.ts",
|
|
87
|
-
"!dist/**/*.typecheck.*"
|
|
88
|
+
"!dist/**/*.typecheck.*",
|
|
89
|
+
"!src/examples/staged/**"
|
|
88
90
|
]
|
|
89
91
|
}
|
package/src/client.ts
CHANGED
|
@@ -4,10 +4,16 @@ import type {
|
|
|
4
4
|
ComposeCompileResult,
|
|
5
5
|
ComposeCompileSuccessData,
|
|
6
6
|
ComposeManifest,
|
|
7
|
+
SimulateRequest,
|
|
8
|
+
SimulateResult,
|
|
7
9
|
} from '@lifi/compose-spec';
|
|
8
10
|
|
|
9
11
|
import type { GetZapPacksOptions, ZapPackOverview } from './discovery.js';
|
|
10
12
|
import { ComposeError, errorFromHttpResponse } from './errors.js';
|
|
13
|
+
import {
|
|
14
|
+
parseCompilePartialEnvelope,
|
|
15
|
+
parseSimulateResult,
|
|
16
|
+
} from './responseSchemas.js';
|
|
11
17
|
|
|
12
18
|
// __SDK_VERSION__ is a compile-time constant injected by tsup (via `define` in tsup.config.ts)
|
|
13
19
|
// and by vitest (via `define` in vitest.config.ts). Both read the version from package.json
|
|
@@ -78,6 +84,30 @@ export interface ComposeClient {
|
|
|
78
84
|
readonly getZapPacks: (
|
|
79
85
|
options?: GetZapPacksOptions,
|
|
80
86
|
) => Promise<readonly ZapPackOverview[]>;
|
|
87
|
+
/**
|
|
88
|
+
* Simulates a raw, pre-encoded transaction against `POST /simulate` and
|
|
89
|
+
* reports how the watched balances change and how much inner-call gas it
|
|
90
|
+
* burns.
|
|
91
|
+
*
|
|
92
|
+
* The result is a discriminated union on `status`:
|
|
93
|
+
* - `'ok'` — the simulation ran successfully; `deltas`/`gasUsed` are populated.
|
|
94
|
+
* - `'revert'` — the simulation ran but the transaction reverted on-chain. A
|
|
95
|
+
* revert is a *successful simulation*, not a transport error, so it is
|
|
96
|
+
* returned (HTTP 200) rather than thrown — mirroring how {@link compile}
|
|
97
|
+
* returns `status: 'partial'` on a simulated revert.
|
|
98
|
+
* - `'error'` — the request was well-formed but the simulation could not be
|
|
99
|
+
* set up or run (HTTP 422); `message` is intentionally generic.
|
|
100
|
+
*
|
|
101
|
+
* `bigint` amounts in the request (`value`, requirement `balance`/`allowance`)
|
|
102
|
+
* are serialised to decimal strings automatically.
|
|
103
|
+
*
|
|
104
|
+
* @param request - The raw transaction plus funding `requirements` and the
|
|
105
|
+
* `trackedBalances` to watch.
|
|
106
|
+
* @returns A {@link SimulateResult} (`ok` / `revert` / `error`).
|
|
107
|
+
* @throws {@link ComposeError} on network failures, HTTP 400 (malformed
|
|
108
|
+
* input), 401/403 (auth), 404, 429, and 5xx.
|
|
109
|
+
*/
|
|
110
|
+
readonly simulate: (request: SimulateRequest) => Promise<SimulateResult>;
|
|
81
111
|
}
|
|
82
112
|
|
|
83
113
|
const bigintReplacer = (_key: string, value: unknown): unknown =>
|
|
@@ -109,22 +139,39 @@ const parsePartialBody = async (
|
|
|
109
139
|
url: string,
|
|
110
140
|
): Promise<ComposeCompileResult> => {
|
|
111
141
|
const body = await res.json().catch((_) => null);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
!('data' in body) ||
|
|
115
|
-
!isNonNullObject(body.data) ||
|
|
116
|
-
!('error' in body) ||
|
|
117
|
-
!isNonNullObject(body.error)
|
|
118
|
-
) {
|
|
142
|
+
const envelope = parseCompilePartialEnvelope(body);
|
|
143
|
+
if (envelope === null) {
|
|
119
144
|
throw new ComposeError(
|
|
120
145
|
'UNKNOWN_ERROR',
|
|
121
146
|
'Unexpected partial response format',
|
|
122
147
|
{ url },
|
|
123
148
|
);
|
|
124
149
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
150
|
+
// `data` is validated as an object by the schema; compose-spec owns its full
|
|
151
|
+
// shape as a hand-authored type, so we narrow it here rather than re-declaring
|
|
152
|
+
// that type as a schema. `error` is fully validated — no cast needed.
|
|
153
|
+
const data = envelope.data as unknown as ComposeCompilePartialData;
|
|
154
|
+
return { ...data, status: 'partial' as const, error: envelope.error };
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// `/simulate` is un-enveloped: the discriminated body (`{ status, ... }`) is at
|
|
158
|
+
// the top level, NOT wrapped in `{ data }` like `/compose`. So this reads the
|
|
159
|
+
// body directly and validates it against the simulate union rather than reusing
|
|
160
|
+
// `parseBody`.
|
|
161
|
+
const parseSimulateBody = async (
|
|
162
|
+
res: Response,
|
|
163
|
+
url: string,
|
|
164
|
+
): Promise<SimulateResult> => {
|
|
165
|
+
const body = await res.json().catch((_) => null);
|
|
166
|
+
const result = parseSimulateResult(body);
|
|
167
|
+
if (result === null) {
|
|
168
|
+
throw new ComposeError(
|
|
169
|
+
'UNKNOWN_ERROR',
|
|
170
|
+
'Unexpected simulate response format',
|
|
171
|
+
{ url },
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
128
175
|
};
|
|
129
176
|
|
|
130
177
|
/**
|
|
@@ -226,5 +273,33 @@ export const createComposeClient = (
|
|
|
226
273
|
return await parseBody<readonly ZapPackOverview[]>(res, url);
|
|
227
274
|
};
|
|
228
275
|
|
|
229
|
-
|
|
276
|
+
const simulate = async (
|
|
277
|
+
request: SimulateRequest,
|
|
278
|
+
): Promise<SimulateResult> => {
|
|
279
|
+
const url = `${base}/simulate`;
|
|
280
|
+
let res: Response;
|
|
281
|
+
try {
|
|
282
|
+
res = await fetchFn(url, {
|
|
283
|
+
method: 'POST',
|
|
284
|
+
headers: { ...baseHeaders, 'Content-Type': 'application/json' },
|
|
285
|
+
body: JSON.stringify(request, bigintReplacer),
|
|
286
|
+
});
|
|
287
|
+
} catch (err) {
|
|
288
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
289
|
+
throw new ComposeError('NETWORK_ERROR', message, { cause: err });
|
|
290
|
+
}
|
|
291
|
+
// Only 200 (carries `ok`/`revert`) and 422 (carries the `error` member of
|
|
292
|
+
// the union) have a discriminated body. 422 is deliberately intercepted
|
|
293
|
+
// here (not thrown as VALIDATION_ERROR) so callers get one exhaustive
|
|
294
|
+
// `switch (result.status)`. Every other status is a transport error and is
|
|
295
|
+
// thrown — including HTTP 400 (malformed input, no `status` body) and any
|
|
296
|
+
// unexpected 2xx.
|
|
297
|
+
if (res.status === 200 || res.status === 422) {
|
|
298
|
+
return await parseSimulateBody(res, url);
|
|
299
|
+
}
|
|
300
|
+
const body = await res.text();
|
|
301
|
+
throw errorFromHttpResponse(res.status, body, url);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
return { getManifest, compile, getZapPacks, simulate };
|
|
230
305
|
};
|
package/src/errors.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { ComposeErrorKind, SimulationRevert } from '@lifi/compose-spec';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
parseServerErrorBody,
|
|
5
|
+
type ServerErrorBody,
|
|
6
|
+
} from './responseSchemas.js';
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* Machine-readable error codes returned by the SDK.
|
|
5
10
|
*
|
|
@@ -101,21 +106,14 @@ const STATUS_TO_CODE: ReadonlyMap<number, ComposeErrorCode> = new Map<
|
|
|
101
106
|
[429, 'RATE_LIMITED'],
|
|
102
107
|
]);
|
|
103
108
|
|
|
104
|
-
interface ServerErrorBody {
|
|
105
|
-
readonly error?: {
|
|
106
|
-
readonly kind?: string;
|
|
107
|
-
readonly message?: string;
|
|
108
|
-
readonly path?: string;
|
|
109
|
-
readonly details?: SimulationRevert;
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
109
|
const tryParseErrorBody = (body: string): ServerErrorBody | null => {
|
|
110
|
+
let json: unknown;
|
|
114
111
|
try {
|
|
115
|
-
|
|
112
|
+
json = JSON.parse(body);
|
|
116
113
|
} catch {
|
|
117
114
|
return null;
|
|
118
115
|
}
|
|
116
|
+
return parseServerErrorBody(json);
|
|
119
117
|
};
|
|
120
118
|
|
|
121
119
|
/**
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { SimulateResult } from '@lifi/compose-spec';
|
|
2
|
+
|
|
3
|
+
import { buildSimulateRequest, materialisers, resources } from '../index.js';
|
|
4
|
+
import type { ComposeSdk } from '../index.js';
|
|
5
|
+
|
|
6
|
+
import { OWNER } from './config.js';
|
|
7
|
+
|
|
8
|
+
const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
|
|
9
|
+
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
|
|
10
|
+
|
|
11
|
+
// 1 WETH (18 decimals).
|
|
12
|
+
const ONE_WETH = 1_000_000_000_000_000_000n;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* End-to-end `/simulate` usage: compile a WETH→USDC swap, then simulate the
|
|
16
|
+
* compiled transaction to see how the signer's balances move and how much
|
|
17
|
+
* inner-call gas it burns — without broadcasting anything on-chain.
|
|
18
|
+
*
|
|
19
|
+
* Demonstrates the compile → simulate arc:
|
|
20
|
+
* - `buildSimulateRequest` lifts a `ComposeCompileResult` into a
|
|
21
|
+
* `SimulateRequest`, supplying the fields a compiled flow cannot carry: the
|
|
22
|
+
* target `chainId`, the `signer` (`from`), the funding `requirements`, and the
|
|
23
|
+
* `trackedBalances` to watch.
|
|
24
|
+
* - `sdk.simulate` runs it and returns a discriminated `ok | revert | error`
|
|
25
|
+
* result. A `revert` is a *successful simulation* whose execution reverted, so
|
|
26
|
+
* it is returned (not thrown); only transport/auth/5xx failures throw.
|
|
27
|
+
*
|
|
28
|
+
* Unlike the pure-builder examples, this performs network I/O through the
|
|
29
|
+
* injected `sdk` (one `/compose` call, one `/simulate` call). Pass a live
|
|
30
|
+
* `createComposeSdk({ baseUrl })` to run it for real, or a mock-backed SDK in
|
|
31
|
+
* tests.
|
|
32
|
+
*/
|
|
33
|
+
export const simulateCompiledSwap = async (
|
|
34
|
+
sdk: ComposeSdk,
|
|
35
|
+
): Promise<SimulateResult> => {
|
|
36
|
+
// Author and compile a single WETH → USDC swap on Ethereum mainnet.
|
|
37
|
+
const builder = sdk.flow(1, {
|
|
38
|
+
name: 'swap-weth-to-usdc',
|
|
39
|
+
inputs: { amountIn: resources.erc20(WETH, 1) },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
builder.lifi.swap('swap', {
|
|
43
|
+
bind: { amountIn: builder.inputs.amountIn },
|
|
44
|
+
config: { resourceOut: resources.erc20(USDC, 1), slippage: 0.03 },
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const compiled = await builder.compile({
|
|
48
|
+
signer: OWNER,
|
|
49
|
+
inputs: {
|
|
50
|
+
amountIn: materialisers.directDeposit({ amount: ONE_WETH }),
|
|
51
|
+
},
|
|
52
|
+
sweepTo: builder.context.sender,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Lift the compiled transaction into a simulation request. The chain, signer,
|
|
56
|
+
// funding, and watched balances are NOT inferable from a compiled flow, so
|
|
57
|
+
// they are supplied here. `to`/`data`/`value` come from the compile result.
|
|
58
|
+
const request = buildSimulateRequest({
|
|
59
|
+
result: compiled,
|
|
60
|
+
chainId: 1,
|
|
61
|
+
signer: OWNER,
|
|
62
|
+
// Seed the signer with the 1 WETH the swap deposits, so the inner call has
|
|
63
|
+
// funds to pull. `bigint` amounts are serialised to decimal strings.
|
|
64
|
+
requirements: [
|
|
65
|
+
{ type: 'Erc20Balance', wallet: OWNER, token: WETH, balance: ONE_WETH },
|
|
66
|
+
],
|
|
67
|
+
// Watch the signer's WETH (spent) and USDC (received) balances; the response
|
|
68
|
+
// `deltas` come back ordered to match.
|
|
69
|
+
trackedBalances: [
|
|
70
|
+
{ token: WETH, owner: OWNER },
|
|
71
|
+
{ token: USDC, owner: OWNER },
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return sdk.simulate(request);
|
|
76
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SimulateRequest } from '@lifi/compose-spec';
|
|
2
|
+
|
|
3
|
+
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
|
|
4
|
+
const SENDER = '0x1111111111111111111111111111111111111111';
|
|
5
|
+
const RECIPIENT = '0x2222222222222222222222222222222222222222';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build a representative `POST /simulate` request: a USDC transfer of 1 USDC
|
|
9
|
+
* (1_000_000 base units at 6 decimals) from `SENDER` to `RECIPIENT` on chain 1.
|
|
10
|
+
*
|
|
11
|
+
* Demonstrates:
|
|
12
|
+
* - A pre-encoded `transfer(address,uint256)` calldata literal (`data`)
|
|
13
|
+
* - Funding the sender with an `Erc20Balance` requirement so the transfer can
|
|
14
|
+
* succeed in simulation (`bigint` amounts are accepted and serialised)
|
|
15
|
+
* - Watching both the sender's and recipient's USDC balance via `trackedBalances`
|
|
16
|
+
*
|
|
17
|
+
* Pure: returns a plain {@link SimulateRequest}, performs no network I/O. Pass
|
|
18
|
+
* it to `sdk.client.simulate(...)` / `sdk.simulate(...)` to run it.
|
|
19
|
+
*/
|
|
20
|
+
export const buildSimulateRawTransactionExample = (): SimulateRequest => ({
|
|
21
|
+
chainId: 1,
|
|
22
|
+
from: SENDER,
|
|
23
|
+
to: USDC,
|
|
24
|
+
// transfer(RECIPIENT, 1_000_000)
|
|
25
|
+
data: '0xa9059cbb0000000000000000000000002222222222222222222222222222222222222222000000000000000000000000000000000000000000000000000000000f4240',
|
|
26
|
+
value: 0n,
|
|
27
|
+
requirements: [
|
|
28
|
+
{
|
|
29
|
+
type: 'Erc20Balance',
|
|
30
|
+
wallet: SENDER,
|
|
31
|
+
token: USDC,
|
|
32
|
+
balance: 1_000_000n,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
trackedBalances: [
|
|
36
|
+
{ token: USDC, owner: SENDER },
|
|
37
|
+
{ token: USDC, owner: RECIPIENT },
|
|
38
|
+
],
|
|
39
|
+
});
|
|
@@ -126,23 +126,139 @@ export interface CoreEmitEvent4Config {
|
|
|
126
126
|
signature: string;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* a string at most 65536 character(s) long
|
|
131
|
+
*/
|
|
132
|
+
export type MaxLength65536 = string;
|
|
133
|
+
|
|
129
134
|
export interface CoreCallConfig {
|
|
130
135
|
target: Address;
|
|
131
136
|
functionSignature: string;
|
|
132
137
|
callType?: 'Call' | 'ValueCall';
|
|
133
|
-
args?:
|
|
138
|
+
args?: (
|
|
139
|
+
| (
|
|
140
|
+
| MaxLength65536
|
|
141
|
+
| number
|
|
142
|
+
| boolean
|
|
143
|
+
| {
|
|
144
|
+
$ref: string;
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
| (
|
|
148
|
+
| (
|
|
149
|
+
| MaxLength65536
|
|
150
|
+
| number
|
|
151
|
+
| boolean
|
|
152
|
+
| {
|
|
153
|
+
$ref: string;
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
| (
|
|
157
|
+
| (
|
|
158
|
+
| MaxLength65536
|
|
159
|
+
| number
|
|
160
|
+
| boolean
|
|
161
|
+
| {
|
|
162
|
+
$ref: string;
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
| (
|
|
166
|
+
| (
|
|
167
|
+
| MaxLength65536
|
|
168
|
+
| number
|
|
169
|
+
| boolean
|
|
170
|
+
| {
|
|
171
|
+
$ref: string;
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
| (
|
|
175
|
+
| (
|
|
176
|
+
| MaxLength65536
|
|
177
|
+
| number
|
|
178
|
+
| boolean
|
|
179
|
+
| {
|
|
180
|
+
$ref: string;
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
| (
|
|
184
|
+
| MaxLength65536
|
|
185
|
+
| number
|
|
186
|
+
| boolean
|
|
187
|
+
| {
|
|
188
|
+
$ref: string;
|
|
189
|
+
}
|
|
190
|
+
)[]
|
|
191
|
+
)[]
|
|
192
|
+
)[]
|
|
193
|
+
)[]
|
|
194
|
+
)[]
|
|
195
|
+
)[];
|
|
134
196
|
approvals?: {
|
|
135
197
|
spender: Address;
|
|
136
198
|
}[];
|
|
137
199
|
}
|
|
138
|
-
export interface Unknown {
|
|
139
|
-
[k: string]: unknown;
|
|
140
|
-
}
|
|
141
200
|
|
|
142
201
|
export interface CoreStaticCallConfig {
|
|
143
202
|
target: Address;
|
|
144
203
|
functionSignature: string;
|
|
145
|
-
args?:
|
|
204
|
+
args?: (
|
|
205
|
+
| (
|
|
206
|
+
| MaxLength65536
|
|
207
|
+
| number
|
|
208
|
+
| boolean
|
|
209
|
+
| {
|
|
210
|
+
$ref: string;
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
| (
|
|
214
|
+
| (
|
|
215
|
+
| MaxLength65536
|
|
216
|
+
| number
|
|
217
|
+
| boolean
|
|
218
|
+
| {
|
|
219
|
+
$ref: string;
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
| (
|
|
223
|
+
| (
|
|
224
|
+
| MaxLength65536
|
|
225
|
+
| number
|
|
226
|
+
| boolean
|
|
227
|
+
| {
|
|
228
|
+
$ref: string;
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
| (
|
|
232
|
+
| (
|
|
233
|
+
| MaxLength65536
|
|
234
|
+
| number
|
|
235
|
+
| boolean
|
|
236
|
+
| {
|
|
237
|
+
$ref: string;
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
| (
|
|
241
|
+
| (
|
|
242
|
+
| MaxLength65536
|
|
243
|
+
| number
|
|
244
|
+
| boolean
|
|
245
|
+
| {
|
|
246
|
+
$ref: string;
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
| (
|
|
250
|
+
| MaxLength65536
|
|
251
|
+
| number
|
|
252
|
+
| boolean
|
|
253
|
+
| {
|
|
254
|
+
$ref: string;
|
|
255
|
+
}
|
|
256
|
+
)[]
|
|
257
|
+
)[]
|
|
258
|
+
)[]
|
|
259
|
+
)[]
|
|
260
|
+
)[]
|
|
261
|
+
)[];
|
|
146
262
|
}
|
|
147
263
|
|
|
148
264
|
export interface CoreRawCallConfig {
|
|
@@ -156,7 +272,64 @@ export interface CoreInvokeConfig {
|
|
|
156
272
|
target: Address;
|
|
157
273
|
functionSignature: string;
|
|
158
274
|
callType?: 'Call' | 'ValueCall';
|
|
159
|
-
args?:
|
|
275
|
+
args?: (
|
|
276
|
+
| (
|
|
277
|
+
| MaxLength65536
|
|
278
|
+
| number
|
|
279
|
+
| boolean
|
|
280
|
+
| {
|
|
281
|
+
$ref: string;
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
| (
|
|
285
|
+
| (
|
|
286
|
+
| MaxLength65536
|
|
287
|
+
| number
|
|
288
|
+
| boolean
|
|
289
|
+
| {
|
|
290
|
+
$ref: string;
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
| (
|
|
294
|
+
| (
|
|
295
|
+
| MaxLength65536
|
|
296
|
+
| number
|
|
297
|
+
| boolean
|
|
298
|
+
| {
|
|
299
|
+
$ref: string;
|
|
300
|
+
}
|
|
301
|
+
)
|
|
302
|
+
| (
|
|
303
|
+
| (
|
|
304
|
+
| MaxLength65536
|
|
305
|
+
| number
|
|
306
|
+
| boolean
|
|
307
|
+
| {
|
|
308
|
+
$ref: string;
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
| (
|
|
312
|
+
| (
|
|
313
|
+
| MaxLength65536
|
|
314
|
+
| number
|
|
315
|
+
| boolean
|
|
316
|
+
| {
|
|
317
|
+
$ref: string;
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
| (
|
|
321
|
+
| MaxLength65536
|
|
322
|
+
| number
|
|
323
|
+
| boolean
|
|
324
|
+
| {
|
|
325
|
+
$ref: string;
|
|
326
|
+
}
|
|
327
|
+
)[]
|
|
328
|
+
)[]
|
|
329
|
+
)[]
|
|
330
|
+
)[]
|
|
331
|
+
)[]
|
|
332
|
+
)[];
|
|
160
333
|
}
|
|
161
334
|
|
|
162
335
|
export interface CoreAsResourceConfig {
|
|
@@ -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
|
-
});
|