@lifi/composer-sdk 0.10.0 → 0.12.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 +36 -4
- package/dist/authoring/raw.cjs +21 -8
- package/dist/authoring/raw.cjs.map +1 -1
- package/dist/authoring/raw.d.cts +21 -8
- package/dist/authoring/raw.d.ts +21 -8
- package/dist/authoring/raw.js +21 -8
- package/dist/authoring/raw.js.map +1 -1
- package/dist/authoring/signatureArgs.cjs +10 -3
- package/dist/authoring/signatureArgs.cjs.map +1 -1
- package/dist/authoring/signatureArgs.d.cts +23 -16
- package/dist/authoring/signatureArgs.d.ts +23 -16
- package/dist/authoring/signatureArgs.js +10 -3
- package/dist/authoring/signatureArgs.js.map +1 -1
- package/dist/client.cjs +11 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +11 -1
- package/dist/client.js.map +1 -1
- package/dist/errors.cjs +35 -13
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +19 -1
- package/dist/errors.d.ts +19 -1
- package/dist/errors.js +35 -14
- package/dist/errors.js.map +1 -1
- package/dist/generated/config.generated.d.cts +98 -2
- package/dist/generated/config.generated.d.ts +98 -2
- package/dist/generated/operations.generated.cjs +10 -0
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +57 -2
- package/dist/generated/operations.generated.d.ts +57 -2
- package/dist/generated/operations.generated.js +10 -0
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/outputs.cjs +3 -2
- package/dist/outputs.cjs.map +1 -1
- package/dist/outputs.d.cts +3 -2
- package/dist/outputs.d.ts +3 -2
- package/dist/outputs.js +3 -2
- package/dist/outputs.js.map +1 -1
- package/dist/responseSchemas.cjs.map +1 -1
- package/dist/responseSchemas.d.cts +2 -2
- package/dist/responseSchemas.d.ts +2 -2
- package/dist/responseSchemas.js.map +1 -1
- package/dist/run/inputs.cjs +0 -1
- package/dist/run/inputs.cjs.map +1 -1
- package/dist/run/inputs.d.cts +3 -11
- package/dist/run/inputs.d.ts +3 -11
- package/dist/run/inputs.js +0 -1
- package/dist/run/inputs.js.map +1 -1
- package/dist/sdk.cjs +0 -1
- package/dist/sdk.cjs.map +1 -1
- package/dist/sdk.js +0 -1
- package/dist/sdk.js.map +1 -1
- package/package.json +2 -2
- package/src/authoring/raw.ts +21 -8
- package/src/authoring/signatureArgs.ts +53 -23
- package/src/client.ts +24 -1
- package/src/errors.ts +48 -17
- package/src/examples/continuationBridgeSettle.ts +99 -0
- package/src/examples/continuationSettle.ts +113 -0
- package/src/examples/run.ts +8 -0
- package/src/generated/config.generated.ts +111 -2
- package/src/generated/operations.generated.ts +99 -1
- package/src/index.ts +6 -2
- package/src/outputs.ts +3 -2
- package/src/responseSchemas.ts +9 -5
- package/src/run/inputs.ts +3 -12
- package/src/sdk.ts +0 -1
- package/src/examples/swapWithFee.ts +0 -59
package/dist/sdk.js
CHANGED
|
@@ -24,7 +24,6 @@ const createComposeSdk = (options) => {
|
|
|
24
24
|
signer: run.signer,
|
|
25
25
|
assumptions: run.assumptions,
|
|
26
26
|
referrer: run.referrer,
|
|
27
|
-
integratorFeeBps: run.integratorFeeBps,
|
|
28
27
|
maxPriceImpactBps: run.maxPriceImpactBps,
|
|
29
28
|
sweepTo: run.sweepTo,
|
|
30
29
|
simulationPolicy: run.simulationPolicy,
|
package/dist/sdk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.js","names":[],"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 type { ChainSummary } from './discovery.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 * Fetches the chains this Compose deployment supports, ascending by\n * `chainId`. Thin pass-through to {@link ComposeClient.getChains}; see it\n * for what \"supported\" means and for the caching caveat.\n *\n * Each entry pairs the `chainId` with a display `name`, so one call both\n * gates a chain selector and labels it. Key on `chainId`; `name` is a\n * deployment-maintained label that can change.\n *\n * Use it to fail fast before building a flow for a chain the server would\n * reject.\n *\n * @returns The supported chains, ascending by `chainId`.\n * @throws {@link ComposeError} on network or server errors.\n *\n * @example\n * ```ts\n * const chains = await sdk.getSupportedChains();\n * if (!chains.some((c) => c.chainId === 8453)) {\n * throw new Error('Base is not supported');\n * }\n * for (const { chainId, name } of chains) {\n * console.log(`${name} (${chainId})`);\n * }\n * ```\n */\n readonly getSupportedChains: () => Promise<readonly ChainSummary[]>;\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 into a submit-ready transaction. Thin\n * pass-through to {@link ComposeClient.route}; see it for the short chain of\n * catalog edges the server plans, when to prefer `flow(...).compile(...)`,\n * the `success` / `partial` discrimination, and error behaviour. Build the\n * `amount` with `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
|
|
1
|
+
{"version":3,"file":"sdk.js","names":[],"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 type { ChainSummary } from './discovery.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 * Fetches the chains this Compose deployment supports, ascending by\n * `chainId`. Thin pass-through to {@link ComposeClient.getChains}; see it\n * for what \"supported\" means and for the caching caveat.\n *\n * Each entry pairs the `chainId` with a display `name`, so one call both\n * gates a chain selector and labels it. Key on `chainId`; `name` is a\n * deployment-maintained label that can change.\n *\n * Use it to fail fast before building a flow for a chain the server would\n * reject.\n *\n * @returns The supported chains, ascending by `chainId`.\n * @throws {@link ComposeError} on network or server errors.\n *\n * @example\n * ```ts\n * const chains = await sdk.getSupportedChains();\n * if (!chains.some((c) => c.chainId === 8453)) {\n * throw new Error('Base is not supported');\n * }\n * for (const { chainId, name } of chains) {\n * console.log(`${name} (${chainId})`);\n * }\n * ```\n */\n readonly getSupportedChains: () => Promise<readonly ChainSummary[]>;\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 into a submit-ready transaction. Thin\n * pass-through to {@link ComposeClient.route}; see it for the short chain of\n * catalog edges the server plans, when to prefer `flow(...).compile(...)`,\n * the `success` / `partial` discrimination, and error behaviour. Build the\n * `amount` with `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 maxPriceImpactBps: run.maxPriceImpactBps,\n sweepTo: run.sweepTo,\n simulationPolicy: run.simulationPolicy,\n simulateUserProgram: run.simulateUserProgram,\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 getSupportedChains: composeClient.getChains,\n request,\n route: composeClient.route,\n simulate: composeClient.simulate,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;AA6LA,MAAa,oBAAoB,YAA2C;CAC1E,MAAM,gBAAgB,oBAAoB,OAAO;CAEjD,MAAM,WACJ,SACA,SAC2B;EAC3B,MAAM;EACN,KAAK,SAAS;GACZ,QAAQ,IAAI;GACZ,eAAe,IAAI;GACnB,QAAQ,IAAI;GACZ,aAAa,IAAI;GACjB,UAAU,IAAI;GACd,mBAAmB,IAAI;GACvB,SAAS,IAAI;GACb,kBAAkB,IAAI;GACtB,qBAAqB,IAAI;GACzB,wBAAwB,IAAI;EAC9B,CAAC;CACH;CAEA,MAAM,QACJ,SACA,SACmB;EACnB,MAAM,OAAO,sBAAsB,SAAS,IAAI;EAChD,MAAM,UAAU,OAAO,OAAO,MAAM,iBAAiB,IAAI,CAAC;EAE1D,MAAM,UAAU,OACd,QACkC;GAClC,MAAM,UAAU,QAAQ,MAAM;GAC9B,MAAM,MAAM,QAAQ,SAAS,GAAG;GAChC,OAAO,cAAc,QAAQ,GAAG;EAClC;EAEA,OAAO,OAAO,OAAO,SAAS,EAAE,QAAQ,CAAC;CAC3C;CAEA,OAAO;EACL,QAAQ;EACR;EACA,oBAAoB,cAAc;EAClC;EACA,OAAO,cAAc;EACrB,UAAU,cAAc;CAC1B;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifi/composer-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Public Composer SDK for building and submitting flows",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"example:staged": "tsx src/examples/run.ts --staged"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@lifi/compose-spec": "0.
|
|
50
|
+
"@lifi/compose-spec": "0.12.0"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"abitype": "^1.2.3",
|
package/src/authoring/raw.ts
CHANGED
|
@@ -45,24 +45,37 @@ export const ref = <T extends OutputKind>(path: string): TypedRef<T> =>
|
|
|
45
45
|
* flow document rather than supplied at run time.
|
|
46
46
|
*
|
|
47
47
|
* Prefer a declared flow input and `builder.inputs.<name>` — a value the
|
|
48
|
-
* caller supplies per run stays out of the document. Use a literal
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
48
|
+
* caller supplies per run stays out of the document. Use a literal for a
|
|
49
|
+
* constant that belongs to the document itself, such as a fixed treasury
|
|
50
|
+
* address.
|
|
51
|
+
*
|
|
52
|
+
* Never use a literal for the payee of a `continuation.settle` leg
|
|
53
|
+
* (`continuationFlow`). The leg produces its outcome into its own proxy
|
|
54
|
+
* (`leg.context.executionAddress`) and names no recipient; the composer
|
|
55
|
+
* appends a settlement epilogue at commit that forwards the outcome to the
|
|
56
|
+
* provider's recipient and the residual to `refund`. A call argument inside
|
|
57
|
+
* the leg (the payee of an invoice the leg pays through `core.call`) is not
|
|
58
|
+
* that recipient and may be a literal.
|
|
59
|
+
*
|
|
60
|
+
* In a `core.call` / `core.staticCall` bind, `kind` must equal the parameter's
|
|
61
|
+
* Solidity type exactly, and `value` must encode as that type: a decimal or
|
|
62
|
+
* `0x` integer, `'true'` / `'false'`, a `0x` address (mixed case only with a
|
|
63
|
+
* valid checksum), or a `0x` hex byte string of the right length.
|
|
53
64
|
*
|
|
54
65
|
* @typeParam T - The Solidity type of the literal (e.g. `'address'`).
|
|
55
66
|
* @param kind - The literal's Solidity type.
|
|
56
67
|
* @param value - The literal value, as the wire format carries it: a string.
|
|
57
|
-
* @returns A {@link TypedLiteral} accepted by `Bindable<T>` slots matching `T
|
|
68
|
+
* @returns A {@link TypedLiteral} accepted by `Bindable<T>` slots matching `T`
|
|
69
|
+
* and by `core.call` / `core.staticCall` parameters of type `T`.
|
|
58
70
|
*
|
|
59
71
|
* @example
|
|
60
72
|
* ```ts
|
|
61
73
|
* import { raw } from '@lifi/composer-sdk';
|
|
62
74
|
*
|
|
63
|
-
*
|
|
75
|
+
* // A fixed fee to a treasury baked into the flow document.
|
|
76
|
+
* builder.core.transfer('fee', {
|
|
64
77
|
* bind: {
|
|
65
|
-
* amount:
|
|
78
|
+
* amount: builder.inputs.fee,
|
|
66
79
|
* recipient: raw.literal('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
|
|
67
80
|
* },
|
|
68
81
|
* config: {},
|
|
@@ -1,38 +1,59 @@
|
|
|
1
|
-
import type { Ref } from '@lifi/compose-spec';
|
|
1
|
+
import type { Ref, SolType } from '@lifi/compose-spec';
|
|
2
2
|
import { type ParseAbiItem, parseAbiItem } from 'abitype';
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
AbiTypeToOutputKind,
|
|
6
|
+
TypedGuard,
|
|
7
|
+
TypedLiteral,
|
|
8
|
+
} from '../types.js';
|
|
5
9
|
|
|
6
10
|
import type { CallArgs } from './FlowBuilderCore.js';
|
|
7
11
|
import type { AnyRefBindable, OutputHandle, RefBindable } from './handles.js';
|
|
8
12
|
import { handleToRef } from './handles.js';
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Any value a signature-derived bind slot accepts, with no per-slot type
|
|
16
|
+
* constraint: a ref-resolving bindable or a typed literal.
|
|
17
|
+
*/
|
|
18
|
+
type AnySignatureBindable = AnyRefBindable | TypedLiteral;
|
|
19
|
+
|
|
20
|
+
// The literal arm of a signature-derived slot. A positional arg is encoded by
|
|
21
|
+
// its parameter's ABI type, not by the literal's `kind`, so the kind must equal
|
|
22
|
+
// that type exactly: the `bytes32` widening `Bindable<T>` applies would admit an
|
|
23
|
+
// `address` literal the encoder then rejects. A parameter whose ABI type is no
|
|
24
|
+
// `SolType` (a tuple, an array, `uint24`) has no literal kind to name, so its
|
|
25
|
+
// slot stays ref-only.
|
|
26
|
+
type SignatureLiteral<TAbiType extends string> = TAbiType extends SolType
|
|
27
|
+
? TypedLiteral<TAbiType>
|
|
28
|
+
: never;
|
|
29
|
+
|
|
10
30
|
/**
|
|
11
31
|
* Type-level extraction of named bind keys from a function signature string.
|
|
12
32
|
* When `TSig` is a string literal matching a Solidity function signature,
|
|
13
|
-
* resolves to a record whose keys are the parameter names and values are
|
|
14
|
-
* `RefBindable<kind
|
|
15
|
-
* type via `AbiTypeToOutputKind
|
|
16
|
-
* (e.g. `uint256`,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
33
|
+
* resolves to a record whose keys are the parameter names and whose values are
|
|
34
|
+
* `RefBindable<kind>`, where the kind is derived from each parameter's Solidity
|
|
35
|
+
* type via `AbiTypeToOutputKind`, or a `TypedLiteral` (`raw.literal`) of that
|
|
36
|
+
* exact Solidity type. For recognised `SolType` parameters (e.g. `uint256`,
|
|
37
|
+
* `address`, `bytes`) the slot is precisely typed; other Solidity types (e.g.
|
|
38
|
+
* tuples) accept any scalar handle and no literal.
|
|
39
|
+
* Falls back to `Record<string, AnySignatureBindable>` for non-literal strings.
|
|
19
40
|
*
|
|
20
|
-
* These slots are lowered into `config.args`,
|
|
21
|
-
*
|
|
22
|
-
*
|
|
41
|
+
* These slots are lowered into `config.args`, positional calldata: a ref
|
|
42
|
+
* becomes `{ $ref }` and a literal becomes its `value` string, which the
|
|
43
|
+
* backend encodes against the parameter's type.
|
|
23
44
|
*/
|
|
24
45
|
export type SignatureBind<TSig extends string> = string extends TSig
|
|
25
|
-
? Record<string,
|
|
46
|
+
? Record<string, AnySignatureBindable>
|
|
26
47
|
: ParseAbiItem<TSig> extends {
|
|
27
48
|
type: 'function';
|
|
28
49
|
inputs: infer I extends readonly { name: string; type: string }[];
|
|
29
50
|
}
|
|
30
51
|
? {
|
|
31
|
-
readonly [P in I[number] as P['name']]:
|
|
32
|
-
AbiTypeToOutputKind<P['type']
|
|
33
|
-
|
|
52
|
+
readonly [P in I[number] as P['name']]:
|
|
53
|
+
| RefBindable<AbiTypeToOutputKind<P['type']>>
|
|
54
|
+
| SignatureLiteral<P['type']>;
|
|
34
55
|
}
|
|
35
|
-
: Record<string,
|
|
56
|
+
: Record<string, AnySignatureBindable>;
|
|
36
57
|
|
|
37
58
|
// ---------------------------------------------------------------------------
|
|
38
59
|
// Return-type inference from function signatures
|
|
@@ -139,9 +160,18 @@ export const toBindRef = (bindable: AnyRefBindable): Ref => {
|
|
|
139
160
|
return bindable;
|
|
140
161
|
};
|
|
141
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Converts one signature-derived bind to its positional `config.args` entry: a
|
|
165
|
+
* ref-resolving bindable becomes `{ $ref }`; a literal becomes its `value`
|
|
166
|
+
* verbatim, which the backend encodes against the parameter's ABI type (a
|
|
167
|
+
* decimal or hex integer, a hex address or byte string, `'true'`/`'false'`).
|
|
168
|
+
*/
|
|
169
|
+
const toPositionalArg = (bindable: AnySignatureBindable): Ref | string =>
|
|
170
|
+
'kind' in bindable ? bindable.value : toBindRef(bindable);
|
|
171
|
+
|
|
142
172
|
export interface BuildCallWireFormatInput {
|
|
143
173
|
readonly resource?: AnyRefBindable;
|
|
144
|
-
readonly bind: Record<string,
|
|
174
|
+
readonly bind: Record<string, AnySignatureBindable>;
|
|
145
175
|
readonly config: Record<string, unknown>;
|
|
146
176
|
readonly guards?: readonly TypedGuard[];
|
|
147
177
|
}
|
|
@@ -155,13 +185,13 @@ export interface CallWireResult {
|
|
|
155
185
|
|
|
156
186
|
/**
|
|
157
187
|
* Parses a function signature from `config`, validates bind keys against it,
|
|
158
|
-
* and converts named binds to positional
|
|
188
|
+
* and converts named binds to positional args.
|
|
159
189
|
*/
|
|
160
190
|
const resolvePositionalArgs = (
|
|
161
191
|
label: string,
|
|
162
|
-
bind: Record<string,
|
|
192
|
+
bind: Record<string, AnySignatureBindable>,
|
|
163
193
|
config: Record<string, unknown>,
|
|
164
|
-
): Ref[] => {
|
|
194
|
+
): (Ref | string)[] => {
|
|
165
195
|
const functionSignature = config.functionSignature;
|
|
166
196
|
if (typeof functionSignature !== 'string') {
|
|
167
197
|
throw new Error(
|
|
@@ -196,7 +226,7 @@ const resolvePositionalArgs = (
|
|
|
196
226
|
`${label}: bind value for parameter "${name}" is undefined`,
|
|
197
227
|
);
|
|
198
228
|
}
|
|
199
|
-
return
|
|
229
|
+
return toPositionalArg(bindable);
|
|
200
230
|
});
|
|
201
231
|
};
|
|
202
232
|
|
|
@@ -205,7 +235,7 @@ const resolvePositionalArgs = (
|
|
|
205
235
|
* format that the backend expects:
|
|
206
236
|
* - With resource: `core.call` with `bind: { input: <resourceRef> }`
|
|
207
237
|
* - Without resource: `core.invoke` with `bind: {}`
|
|
208
|
-
* - `config: { ...userConfig, args: [positional refs...] }`
|
|
238
|
+
* - `config: { ...userConfig, args: [positional refs and literal values...] }`
|
|
209
239
|
*/
|
|
210
240
|
export const buildCallWireFormat = (
|
|
211
241
|
args: BuildCallWireFormatInput,
|
|
@@ -226,7 +256,7 @@ export const buildCallWireFormat = (
|
|
|
226
256
|
};
|
|
227
257
|
|
|
228
258
|
export interface BuildStaticCallWireFormatInput {
|
|
229
|
-
readonly bind: Record<string,
|
|
259
|
+
readonly bind: Record<string, AnySignatureBindable>;
|
|
230
260
|
readonly config: Record<string, unknown>;
|
|
231
261
|
readonly guards?: readonly TypedGuard[];
|
|
232
262
|
}
|
package/src/client.ts
CHANGED
|
@@ -218,6 +218,7 @@ const decodeAmount = (value: unknown, path: string, url: string): bigint => {
|
|
|
218
218
|
// every field before writing a decoded `bigint` back over the wire string.
|
|
219
219
|
interface WireAmountBody {
|
|
220
220
|
outputs?: unknown;
|
|
221
|
+
epilogue?: unknown;
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
// One `Amount` as it arrives: each leaf is `unknown` until this seam has
|
|
@@ -280,14 +281,36 @@ const decodeOutputs = (data: WireAmountBody, url: string): void => {
|
|
|
280
281
|
}
|
|
281
282
|
};
|
|
282
283
|
|
|
284
|
+
// `epilogue` is optional (absent without an epilogue, and on every partial);
|
|
285
|
+
// when present, each named forward's `amount` is a decimal string on the wire.
|
|
286
|
+
const decodeEpilogue = (data: WireAmountBody, url: string): void => {
|
|
287
|
+
const epilogue = data.epilogue;
|
|
288
|
+
if (epilogue === undefined) return;
|
|
289
|
+
if (!isNonNullObject(epilogue) || !Array.isArray(epilogue.forwards)) {
|
|
290
|
+
throw malformed('epilogue', 'is not an object with forwards', url);
|
|
291
|
+
}
|
|
292
|
+
const forwards: readonly unknown[] = epilogue.forwards;
|
|
293
|
+
forwards.forEach((forward, index) => {
|
|
294
|
+
if (!isNonNullObject(forward)) {
|
|
295
|
+
throw malformed(`epilogue.forwards[${index}]`, 'is not an object', url);
|
|
296
|
+
}
|
|
297
|
+
forward.amount = decodeAmount(
|
|
298
|
+
forward.amount,
|
|
299
|
+
`epilogue.forwards[${index}].amount`,
|
|
300
|
+
url,
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
283
305
|
// THE decode seam for a compile-family response body: every amount the wire
|
|
284
306
|
// renders as a decimal string becomes the `bigint` its declared type promises.
|
|
285
|
-
// The amount
|
|
307
|
+
// The amount records are handled here so the body is narrowed once — a
|
|
286
308
|
// non-object body is not this function's error to raise, since the enveloping
|
|
287
309
|
// parse already rejected it.
|
|
288
310
|
const decodeWireAmounts = (data: unknown, url: string): void => {
|
|
289
311
|
if (!isNonNullObject(data)) return;
|
|
290
312
|
decodeOutputs(data, url);
|
|
313
|
+
decodeEpilogue(data, url);
|
|
291
314
|
};
|
|
292
315
|
|
|
293
316
|
const parseBody = async <T>(res: Response, url: string): Promise<T> => {
|
package/src/errors.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ComposeErrorKind,
|
|
3
3
|
ComposeRouteErrorKind,
|
|
4
|
+
DeliveryErrorDetails,
|
|
4
5
|
FailedPreparedOp,
|
|
5
6
|
GenericComposeErrorKind,
|
|
6
7
|
SimulationRevert,
|
|
@@ -102,6 +103,12 @@ export class ComposeError extends Error {
|
|
|
102
103
|
* backend can parse the revert reason.
|
|
103
104
|
*/
|
|
104
105
|
readonly details?: SimulationRevert;
|
|
106
|
+
/**
|
|
107
|
+
* The delivery tranche's reason, first quote, and abandoned escrow,
|
|
108
|
+
* attached to `delivery_error` errors. Narrow with
|
|
109
|
+
* {@link isComposeDeliveryError}.
|
|
110
|
+
*/
|
|
111
|
+
readonly delivery?: DeliveryErrorDetails;
|
|
105
112
|
/**
|
|
106
113
|
* The prepared ops that failed, attached to `preparation_error` errors.
|
|
107
114
|
* Each entry carries the `callId` of the failing node so callers can drop
|
|
@@ -130,6 +137,7 @@ export class ComposeError extends Error {
|
|
|
130
137
|
port?: string;
|
|
131
138
|
fix?: string;
|
|
132
139
|
details?: SimulationRevert;
|
|
140
|
+
delivery?: DeliveryErrorDetails;
|
|
133
141
|
failedOps?: readonly FailedPreparedOp[];
|
|
134
142
|
succeededOps?: readonly string[];
|
|
135
143
|
sdkOutdated?: ComposeSdkOutdated;
|
|
@@ -145,6 +153,7 @@ export class ComposeError extends Error {
|
|
|
145
153
|
this.port = options?.port;
|
|
146
154
|
this.fix = options?.fix;
|
|
147
155
|
this.details = options?.details;
|
|
156
|
+
this.delivery = options?.delivery;
|
|
148
157
|
this.failedOps = options?.failedOps;
|
|
149
158
|
this.succeededOps = options?.succeededOps;
|
|
150
159
|
this.sdkOutdated = options?.sdkOutdated;
|
|
@@ -200,6 +209,19 @@ export const isComposePreparationError = (
|
|
|
200
209
|
Array.isArray(e.failedOps) &&
|
|
201
210
|
Array.isArray(e.succeededOps);
|
|
202
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Type guard for `delivery_error` failures (HTTP 422): a bridge delivering into
|
|
214
|
+
* a settle escrow no longer fits it. Narrows {@link ComposeError.delivery} to
|
|
215
|
+
* present. Recompile: each compile quotes and registers afresh.
|
|
216
|
+
*
|
|
217
|
+
* @param e - The value to check.
|
|
218
|
+
* @returns `true` if `e` is a `ComposeError` carrying delivery details.
|
|
219
|
+
*/
|
|
220
|
+
export const isComposeDeliveryError = (
|
|
221
|
+
e: unknown,
|
|
222
|
+
): e is ComposeError & { readonly delivery: DeliveryErrorDetails } =>
|
|
223
|
+
isComposeError(e) && e.kind === 'delivery_error' && e.delivery !== undefined;
|
|
224
|
+
|
|
203
225
|
// `isComposeError` accepts a structurally-similar error by name, so an error
|
|
204
226
|
// raised by a *second* copy of this package in the dependency tree reaches the
|
|
205
227
|
// guard below without its `sdkOutdated` field being type-guaranteed. Parse it.
|
|
@@ -287,21 +309,32 @@ const tryParseErrorBody = (body: string): ServerErrorBody | null => {
|
|
|
287
309
|
|
|
288
310
|
/**
|
|
289
311
|
* Extracts the version triple an `sdk_outdated` or `server_outdated` envelope
|
|
290
|
-
* carries
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* a half-populated object.
|
|
312
|
+
* carries, and routes the wire `details` by kind. The version slots reuse the
|
|
313
|
+
* same schemas the type guards narrow with. Zod strips keys the schema does
|
|
314
|
+
* not declare, so the parsed value is exactly the triple; a partial payload
|
|
315
|
+
* fails the parse and yields `undefined` rather than a half-populated object.
|
|
316
|
+
* A `delivery_error`'s `details` land in `delivery`; every other kind's are
|
|
317
|
+
* forwarded verbatim as `details`.
|
|
294
318
|
*/
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
319
|
+
const readKindSlots = (serverError: ServerErrorBody['error']) => {
|
|
320
|
+
const delivery = serverError?.kind === 'delivery_error';
|
|
321
|
+
return {
|
|
322
|
+
sdkOutdated:
|
|
323
|
+
serverError?.kind === 'sdk_outdated'
|
|
324
|
+
? sdkOutdatedSchema.safeParse(serverError).data
|
|
325
|
+
: undefined,
|
|
326
|
+
serverOutdated:
|
|
327
|
+
serverError?.kind === 'server_outdated'
|
|
328
|
+
? serverOutdatedSchema.safeParse(serverError).data
|
|
329
|
+
: undefined,
|
|
330
|
+
details: delivery
|
|
331
|
+
? undefined
|
|
332
|
+
: (serverError?.details as SimulationRevert | undefined),
|
|
333
|
+
delivery: delivery
|
|
334
|
+
? (serverError.details as DeliveryErrorDetails | undefined)
|
|
303
335
|
: undefined,
|
|
304
|
-
}
|
|
336
|
+
};
|
|
337
|
+
};
|
|
305
338
|
|
|
306
339
|
/**
|
|
307
340
|
* Constructs a {@link ComposeError} from an HTTP error response, extracting
|
|
@@ -319,7 +352,7 @@ export const errorFromHttpResponse = (
|
|
|
319
352
|
): ComposeError => {
|
|
320
353
|
const parsed = tryParseErrorBody(body);
|
|
321
354
|
const serverError = parsed?.error;
|
|
322
|
-
const
|
|
355
|
+
const slots = readKindSlots(serverError);
|
|
323
356
|
|
|
324
357
|
return new ComposeError(
|
|
325
358
|
STATUS_TO_CODE.get(status) ??
|
|
@@ -333,7 +366,7 @@ export const errorFromHttpResponse = (
|
|
|
333
366
|
path: serverError?.path,
|
|
334
367
|
port: serverError?.port,
|
|
335
368
|
fix: serverError?.fix,
|
|
336
|
-
|
|
369
|
+
...slots,
|
|
337
370
|
// The wire schema types `kind` as a bare string; the server only ever
|
|
338
371
|
// reports generic kinds for individual ops.
|
|
339
372
|
failedOps: serverError?.failedOps?.map((op) => ({
|
|
@@ -341,8 +374,6 @@ export const errorFromHttpResponse = (
|
|
|
341
374
|
kind: op.kind as GenericComposeErrorKind,
|
|
342
375
|
})),
|
|
343
376
|
succeededOps: serverError?.succeededOps,
|
|
344
|
-
sdkOutdated,
|
|
345
|
-
serverOutdated,
|
|
346
377
|
},
|
|
347
378
|
);
|
|
348
379
|
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createComposeSdk,
|
|
5
|
+
guards,
|
|
6
|
+
materialisers,
|
|
7
|
+
resources,
|
|
8
|
+
} from '../index.js';
|
|
9
|
+
import type { Address } from '../types.js';
|
|
10
|
+
|
|
11
|
+
import { API_KEY, BASE_URL } from './config.js';
|
|
12
|
+
|
|
13
|
+
const ARBITRUM = 42161;
|
|
14
|
+
const BASE = 8453;
|
|
15
|
+
const ARB_USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
|
|
16
|
+
const BASE_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
17
|
+
// Aave V3 aToken for USDC on Base.
|
|
18
|
+
const A_BAS_USDC = '0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB';
|
|
19
|
+
const VALIDITY_WINDOW_MS = 3_600_000;
|
|
20
|
+
|
|
21
|
+
export interface ContinuationBridgeSettleInput {
|
|
22
|
+
readonly owner: Address;
|
|
23
|
+
// Receives the aBasUSDC position on Base; defaults to `owner`.
|
|
24
|
+
readonly recipient?: Address;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Bridge USDC from Arbitrum to Base, and escrow the bridged USDC on Base for a
|
|
29
|
+
* deferred settlement that supplies it to Aave and delivers the aBasUSDC
|
|
30
|
+
* position to the recipient.
|
|
31
|
+
*
|
|
32
|
+
* Demonstrates:
|
|
33
|
+
* - `lifi.bridge` with no `destination`: its `amountOut` funds the settle's
|
|
34
|
+
* escrow on Base instead of landing at an address
|
|
35
|
+
* - `continuation.settle` bound to `bridge.amountOut` (delivered funding): the
|
|
36
|
+
* bridge delivers straight into the escrow, so the user signs one
|
|
37
|
+
* transaction on Arbitrum, and the settle must omit `funding`
|
|
38
|
+
* - `validity.expiresAtMs` at least `max(3 × the bridge ETA, 30 minutes)` away
|
|
39
|
+
* - A leg on the escrow chain (Base) that does the work the user could not do
|
|
40
|
+
* from Arbitrum: `lifi.zap` supplies the landed USDC to Aave. The aBasUSDC
|
|
41
|
+
* stays in the leg's proxy; the settlement epilogue the composer appends at
|
|
42
|
+
* commit forwards it to the recipient
|
|
43
|
+
* - An outcome in a token the leg produces (aBasUSDC), not the funding token:
|
|
44
|
+
* the zap's slippage guard proves the floor, so `minAmount` is omitted
|
|
45
|
+
*/
|
|
46
|
+
export const buildContinuationBridgeSettleExample = ({
|
|
47
|
+
owner,
|
|
48
|
+
recipient = owner,
|
|
49
|
+
}: ContinuationBridgeSettleInput): {
|
|
50
|
+
flow: Flow;
|
|
51
|
+
request: ComposeCompileRequest;
|
|
52
|
+
} => {
|
|
53
|
+
const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
|
|
54
|
+
|
|
55
|
+
// The leg: runs from the escrow on Base once the bridged USDC has landed.
|
|
56
|
+
// The zap supplies the USDC to Aave; the aBasUSDC it mints lands in the
|
|
57
|
+
// leg's proxy, where the settlement epilogue picks it up. The slippage guard
|
|
58
|
+
// on `amountOut` is the enforced floor the compiler commits.
|
|
59
|
+
const leg = sdk.flow(BASE, {
|
|
60
|
+
name: 'bridge-settle-leg',
|
|
61
|
+
inputs: { funding: resources.erc20(BASE_USDC, BASE) },
|
|
62
|
+
});
|
|
63
|
+
leg.lifi.zap('supply', {
|
|
64
|
+
bind: { amountIn: leg.inputs.funding },
|
|
65
|
+
config: { resourceOut: resources.erc20(A_BAS_USDC, BASE) },
|
|
66
|
+
guards: [guards.slippage({ port: 'amountOut', bps: 50 })],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// The main flow on Arbitrum: bridge to Base, then escrow the delivery.
|
|
70
|
+
const builder = sdk.flow(ARBITRUM, {
|
|
71
|
+
name: 'bridge-and-settle',
|
|
72
|
+
inputs: { amountIn: resources.erc20(ARB_USDC, ARBITRUM) },
|
|
73
|
+
});
|
|
74
|
+
const bridge = builder.lifi.bridge('bridge', {
|
|
75
|
+
bind: { amountIn: builder.inputs.amountIn },
|
|
76
|
+
config: { resourceOut: resources.erc20(BASE_USDC, BASE) },
|
|
77
|
+
});
|
|
78
|
+
builder.continuation.settle('settle', {
|
|
79
|
+
bind: { input: bridge.amountOut },
|
|
80
|
+
config: {
|
|
81
|
+
outcomes: [{ token: A_BAS_USDC, destination: recipient, chainId: BASE }],
|
|
82
|
+
validity: { expiresAtMs: Date.now() + VALIDITY_WINDOW_MS },
|
|
83
|
+
provider: 'intent-factory',
|
|
84
|
+
refund: owner,
|
|
85
|
+
continuationFlow: leg.build(),
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const flow = builder.build();
|
|
90
|
+
|
|
91
|
+
const request = sdk.request(flow, {
|
|
92
|
+
signer: owner,
|
|
93
|
+
inputs: {
|
|
94
|
+
amountIn: materialisers.directDeposit({ amount: '1000000000' }),
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return { flow, request };
|
|
99
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createComposeSdk,
|
|
5
|
+
guards,
|
|
6
|
+
materialisers,
|
|
7
|
+
resources,
|
|
8
|
+
} from '../index.js';
|
|
9
|
+
import type { Address } from '../types.js';
|
|
10
|
+
|
|
11
|
+
import { API_KEY, BASE_URL } from './config.js';
|
|
12
|
+
|
|
13
|
+
const BASE = 8453;
|
|
14
|
+
const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
15
|
+
const WETH = '0x4200000000000000000000000000000000000006';
|
|
16
|
+
// Aave V3 aToken for WETH on Base.
|
|
17
|
+
const A_BAS_WETH = '0xD4a0e0b9149BCee3C920d2E00b5dE09138fd8bb7';
|
|
18
|
+
const VALIDITY_WINDOW_MS = 3_600_000;
|
|
19
|
+
|
|
20
|
+
export interface ContinuationSettleInput {
|
|
21
|
+
readonly owner: Address;
|
|
22
|
+
// Receives the aBasWETH position; defaults to `owner`.
|
|
23
|
+
readonly recipient?: Address;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Swap USDC to WETH on Base, then escrow the WETH for a deferred settlement
|
|
28
|
+
* whose leg supplies it to Aave and delivers the aBasWETH position to the
|
|
29
|
+
* recipient.
|
|
30
|
+
*
|
|
31
|
+
* Demonstrates:
|
|
32
|
+
* - `continuation.settle` funded by an upstream output (`swap.amountOut`)
|
|
33
|
+
* - The leg (`continuationFlow`) as an ordinary flow built with `sdk.flow`
|
|
34
|
+
* and assigned with `leg.build()`: here one `lifi.zap` that supplies the
|
|
35
|
+
* escrowed WETH to Aave
|
|
36
|
+
* - An outcome in the token the leg produces (aBasWETH): the zap's slippage
|
|
37
|
+
* guard proves the floor, so `minAmount` is omitted
|
|
38
|
+
* - `refund` naming the address that receives the escrow balance on expiry
|
|
39
|
+
* and the leg's residual (optional; defaults to the signer)
|
|
40
|
+
*
|
|
41
|
+
* The leg contract:
|
|
42
|
+
* - Exactly one input: a resource input in the funding token on the escrow
|
|
43
|
+
* chain. The solver binds the escrow balance to it.
|
|
44
|
+
* - Produce each outcome into the leg's own proxy and end there. A zap or a
|
|
45
|
+
* deposit mints into the proxy by default; a node that needs a receiver
|
|
46
|
+
* binds `leg.context.executionAddress`. A funding input no node consumes
|
|
47
|
+
* stays in the proxy as the terminal `input.<name>`. Never
|
|
48
|
+
* pay a recipient: not `destination`, not the provider's settlement
|
|
49
|
+
* recipient, and never `context.sender` (inside the leg that is the
|
|
50
|
+
* settlement account, not the user). Never set the leg's own epilogue. At
|
|
51
|
+
* commit the composer appends the settlement epilogue, which forwards each
|
|
52
|
+
* outcome to the provider's recipient and the residual to `refund`.
|
|
53
|
+
* - Omit `funding` when the settle input is a bridge's
|
|
54
|
+
* `amountOut` (see `continuationBridgeSettle.ts`).
|
|
55
|
+
* - `minAmount` is optional: a guard or an op floor on the outcome token
|
|
56
|
+
* proves it, and an unconsumed funding input proves the full funding floor.
|
|
57
|
+
*/
|
|
58
|
+
export const buildContinuationSettleExample = ({
|
|
59
|
+
owner,
|
|
60
|
+
recipient = owner,
|
|
61
|
+
}: ContinuationSettleInput): {
|
|
62
|
+
flow: Flow;
|
|
63
|
+
request: ComposeCompileRequest;
|
|
64
|
+
} => {
|
|
65
|
+
const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
|
|
66
|
+
|
|
67
|
+
// The leg: runs from the escrow on Base once the WETH has landed there. The
|
|
68
|
+
// zap supplies the WETH to Aave; the aBasWETH it mints lands in the leg's
|
|
69
|
+
// proxy, where the settlement epilogue picks it up. The slippage guard on
|
|
70
|
+
// `amountOut` is the enforced floor the compiler commits.
|
|
71
|
+
const leg = sdk.flow(BASE, {
|
|
72
|
+
name: 'settle-leg',
|
|
73
|
+
inputs: { funding: resources.erc20(WETH, BASE) },
|
|
74
|
+
});
|
|
75
|
+
leg.lifi.zap('supply', {
|
|
76
|
+
bind: { amountIn: leg.inputs.funding },
|
|
77
|
+
config: { resourceOut: resources.erc20(A_BAS_WETH, BASE) },
|
|
78
|
+
guards: [guards.slippage({ port: 'amountOut', bps: 50 })],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// The main flow: swap USDC to WETH, then escrow the WETH for the leg.
|
|
82
|
+
const builder = sdk.flow(BASE, {
|
|
83
|
+
name: 'swap-and-settle',
|
|
84
|
+
inputs: { amountIn: resources.erc20(USDC, BASE) },
|
|
85
|
+
});
|
|
86
|
+
const swap = builder.lifi.swap('swap', {
|
|
87
|
+
bind: { amountIn: builder.inputs.amountIn },
|
|
88
|
+
config: { resourceOut: resources.erc20(WETH, BASE) },
|
|
89
|
+
});
|
|
90
|
+
builder.continuation.settle('settle', {
|
|
91
|
+
bind: { input: swap.amountOut },
|
|
92
|
+
config: {
|
|
93
|
+
outcomes: [{ token: A_BAS_WETH, destination: recipient, chainId: BASE }],
|
|
94
|
+
validity: { expiresAtMs: Date.now() + VALIDITY_WINDOW_MS },
|
|
95
|
+
provider: 'intent-factory',
|
|
96
|
+
refund: owner,
|
|
97
|
+
continuationFlow: leg.build(),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const flow = builder.build();
|
|
102
|
+
|
|
103
|
+
// No `sweepTo`: the swap output funds the escrow and the settle output is
|
|
104
|
+
// delivered by the settlement, never landing in the VM.
|
|
105
|
+
const request = sdk.request(flow, {
|
|
106
|
+
signer: owner,
|
|
107
|
+
inputs: {
|
|
108
|
+
amountIn: materialisers.directDeposit({ amount: '1000000000' }),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return { flow, request };
|
|
113
|
+
};
|