@lifi/composer-sdk 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/authoring/FlowBuilderCore.cjs +1 -5
- package/dist/authoring/FlowBuilderCore.cjs.map +1 -1
- package/dist/authoring/FlowBuilderCore.d.cts +9 -7
- package/dist/authoring/FlowBuilderCore.d.ts +9 -7
- package/dist/authoring/FlowBuilderCore.js +2 -6
- package/dist/authoring/FlowBuilderCore.js.map +1 -1
- package/dist/authoring/handles.cjs +5 -2
- package/dist/authoring/handles.cjs.map +1 -1
- package/dist/authoring/handles.d.cts +25 -6
- package/dist/authoring/handles.d.ts +25 -6
- package/dist/authoring/handles.js +3 -1
- package/dist/authoring/handles.js.map +1 -1
- package/dist/authoring/raw.cjs +3 -0
- package/dist/authoring/raw.cjs.map +1 -1
- package/dist/authoring/raw.d.cts +33 -3
- package/dist/authoring/raw.d.ts +33 -3
- package/dist/authoring/raw.js +2 -0
- package/dist/authoring/raw.js.map +1 -1
- package/dist/authoring/signatureArgs.cjs.map +1 -1
- package/dist/authoring/signatureArgs.d.cts +16 -12
- package/dist/authoring/signatureArgs.d.ts +16 -12
- package/dist/authoring/signatureArgs.js.map +1 -1
- package/dist/client.cjs +112 -55
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +42 -5
- package/dist/client.d.ts +42 -5
- package/dist/client.js +116 -55
- package/dist/client.js.map +1 -1
- package/dist/discovery.cjs.map +1 -1
- package/dist/discovery.d.cts +18 -2
- package/dist/discovery.d.ts +18 -2
- package/dist/errors.cjs +42 -3
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +67 -4
- package/dist/errors.d.ts +67 -4
- package/dist/errors.js +29 -2
- package/dist/errors.js.map +1 -1
- package/dist/generated/config.generated.cjs.map +1 -1
- package/dist/generated/config.generated.d.cts +21 -1
- package/dist/generated/config.generated.d.ts +21 -1
- package/dist/generated/operations.generated.cjs +6 -0
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +18 -2
- package/dist/generated/operations.generated.d.ts +18 -2
- package/dist/generated/operations.generated.js +6 -0
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/{raw-IxCF_bSS.d.ts → raw-BvRghN3r.d.ts} +3 -2
- package/dist/{raw-Dj_2rBSQ.d.cts → raw-s1rer8X3.d.cts} +3 -2
- package/dist/raw.cjs +2 -0
- package/dist/raw.cjs.map +1 -1
- package/dist/raw.d.cts +1 -1
- package/dist/raw.d.ts +1 -1
- package/dist/raw.js +2 -1
- package/dist/raw.js.map +1 -1
- package/dist/responseSchemas.cjs +3 -0
- package/dist/responseSchemas.cjs.map +1 -1
- package/dist/responseSchemas.d.cts +3 -0
- package/dist/responseSchemas.d.ts +3 -0
- package/dist/responseSchemas.js +3 -0
- package/dist/responseSchemas.js.map +1 -1
- package/dist/run/inputs.cjs +14 -15
- package/dist/run/inputs.cjs.map +1 -1
- package/dist/run/inputs.d.cts +8 -0
- package/dist/run/inputs.d.ts +8 -0
- package/dist/run/inputs.js +14 -15
- package/dist/run/inputs.js.map +1 -1
- package/dist/run/simulate.cjs +5 -0
- package/dist/run/simulate.cjs.map +1 -1
- package/dist/run/simulate.d.cts +5 -2
- package/dist/run/simulate.d.ts +5 -2
- package/dist/run/simulate.js +5 -0
- package/dist/run/simulate.js.map +1 -1
- package/dist/sdk.cjs +2 -0
- package/dist/sdk.cjs.map +1 -1
- package/dist/sdk.d.cts +28 -1
- package/dist/sdk.d.ts +28 -1
- package/dist/sdk.js +2 -0
- package/dist/sdk.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +18 -2
- package/dist/types.d.ts +18 -2
- package/package.json +3 -10
- package/src/authoring/FlowBuilderCore.ts +13 -15
- package/src/authoring/handles.ts +41 -5
- package/src/authoring/raw.ts +36 -2
- package/src/authoring/signatureArgs.ts +17 -13
- package/src/client.ts +222 -62
- package/src/discovery.ts +18 -1
- package/src/errors.ts +116 -3
- package/src/examples/flySwap.ts +167 -0
- package/src/examples/run.ts +35 -13
- package/src/examples/supportedChains.ts +122 -0
- package/src/generated/config.generated.ts +24 -0
- package/src/generated/operations.generated.ts +26 -0
- package/src/index.ts +18 -2
- package/src/raw.ts +1 -1
- package/src/responseSchemas.ts +6 -0
- package/src/run/inputs.ts +30 -11
- package/src/run/simulate.ts +14 -2
- package/src/sdk.ts +30 -0
- package/src/types.ts +20 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
|
|
2
|
+
|
|
3
|
+
import { createComposeSdk, materialisers, resources } from '../index.js';
|
|
4
|
+
|
|
5
|
+
import { API_KEY, BASE_URL, OWNER } from './config.js';
|
|
6
|
+
|
|
7
|
+
// Base mainnet. Pendle PT-USDC, its underlying USDC, and WETH.
|
|
8
|
+
const BASE = 8453;
|
|
9
|
+
const PT_USDC = '0x0d18b7bab00988a442e31065e76286844809dc9a';
|
|
10
|
+
const USDC = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
|
|
11
|
+
const WETH = '0x4200000000000000000000000000000000000006';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Exit a Pendle PT position and swap **all** of the proceeds to WETH on Base.
|
|
15
|
+
*
|
|
16
|
+
* ## The problem this solves
|
|
17
|
+
*
|
|
18
|
+
* Every other swap op bakes a fixed input amount into the router calldata at
|
|
19
|
+
* quote time. That is fine when you know the amount up front, and useless when
|
|
20
|
+
* you do not: a PT exit routes through an on-chain AMM quote, so it can deliver
|
|
21
|
+
* a little more or a little less than the estimate. With `lifi.swap` you must
|
|
22
|
+
* name an amount, and whatever the exit over-delivers is stranded in the proxy
|
|
23
|
+
* (or, if it under-delivers, the flow reverts).
|
|
24
|
+
*
|
|
25
|
+
* `fly.swap` is the only swap that can sit downstream of a producer whose output
|
|
26
|
+
* is not known until execution. Fly's `DexAggregator` supports a variable-input
|
|
27
|
+
* mode — the `amountIn` word is excluded from the signed quote, so leaving it
|
|
28
|
+
* unset makes the contract resolve the amount on-chain: for an erc20 input,
|
|
29
|
+
* `min(our balance, the allowance we granted)`; for a native input, whatever
|
|
30
|
+
* `msg.value` the op forwards. The op therefore swaps *all* of whatever
|
|
31
|
+
* actually arrives. Either leg may be the chain's gas coin.
|
|
32
|
+
*
|
|
33
|
+
* ## The two numbers, which are independent
|
|
34
|
+
*
|
|
35
|
+
* This is the thing to get right, because they sound alike and are not:
|
|
36
|
+
*
|
|
37
|
+
* - **`inputVarianceBps`** describes the **upstream**: how far its real output
|
|
38
|
+
* may sit either side of the estimate, symmetrically. It is a quantity, not a
|
|
39
|
+
* price.
|
|
40
|
+
* - **`maxSlippageBps`** is ordinary **price** protection: expected rate versus
|
|
41
|
+
* achieved rate, sized on the pair, and completely unaffected by how much
|
|
42
|
+
* input turns up. It is enforced on-chain as a *rate* against the input
|
|
43
|
+
* actually consumed, so the percentage protection is identical at any size.
|
|
44
|
+
*
|
|
45
|
+
* Neither is derived from the other and neither constrains the other. A
|
|
46
|
+
* `maxSlippageBps` below `inputVarianceBps` is perfectly legitimate.
|
|
47
|
+
*
|
|
48
|
+
* ## Choosing `inputVarianceBps` (default 50 = 0.5%)
|
|
49
|
+
*
|
|
50
|
+
* The compile-time input amount is **simulated** at the current block, not
|
|
51
|
+
* guessed — the compiler runs the upstream and reads what it produced. So this
|
|
52
|
+
* field covers only simulate-to-execute drift: block delay, yield accrued in
|
|
53
|
+
* that window, and third parties moving the upstream's rate in between. Tens of
|
|
54
|
+
* basis points, not percent.
|
|
55
|
+
*
|
|
56
|
+
* Widening it is safe but not free. The bottom of the band is what the compose
|
|
57
|
+
* response publishes as `amountOutMin`, so a wider band advertises a weaker
|
|
58
|
+
* minimum to whoever consumes your quote.
|
|
59
|
+
*
|
|
60
|
+
* ## Ignore the 50% slippage in the Fly calldata
|
|
61
|
+
*
|
|
62
|
+
* If you inspect the compiled transaction you will see a 50% slippage figure
|
|
63
|
+
* where you configured 1%. That is deliberate and it is not your setting. All
|
|
64
|
+
* Fly's own `slippage` parameter does is price its `amountOutMin`, which is an
|
|
65
|
+
* absolute amount fixed at quote time and therefore cannot scale down when a
|
|
66
|
+
* legitimately smaller input arrives. The op sends a high value so that floor
|
|
67
|
+
* can never be the binding constraint, and enforces its own rate-based bound
|
|
68
|
+
* instead — which is strictly tighter for every in-band delivery. The number
|
|
69
|
+
* that protects you is `maxSlippageBps`.
|
|
70
|
+
*
|
|
71
|
+
* ## What happens outside the band
|
|
72
|
+
*
|
|
73
|
+
* With `E` the simulated exit output, `v = inputVarianceBps`:
|
|
74
|
+
*
|
|
75
|
+
* E = 1000, v = 200 (2%), p = 100 (1%) -> band [980, 1020]
|
|
76
|
+
*
|
|
77
|
+
* delivered 1020 -> consumes 1020, nothing left over (band top)
|
|
78
|
+
* delivered 1000 -> consumes 1000, nothing left over
|
|
79
|
+
* delivered 980 -> consumes 980, nothing left over (band bottom)
|
|
80
|
+
* delivered 950 -> reverts on the op's own floor invariant
|
|
81
|
+
* delivered 1200 -> consumes 1020; the 180 excess is swept back to you
|
|
82
|
+
*
|
|
83
|
+
* Both out-of-band outcomes are safe. The upward cap exists because Fly
|
|
84
|
+
* confiscates output above the quoted expectation rather than rejecting it, so
|
|
85
|
+
* the op never approves more than it quoted for. Note the excess only comes back
|
|
86
|
+
* if the run supplies `sweepTo` — without it the residue sits in the proxy until
|
|
87
|
+
* a later flow or an explicit `POST /compose/sweep` collects it.
|
|
88
|
+
*
|
|
89
|
+
* ## Two limitations worth knowing before you author
|
|
90
|
+
*
|
|
91
|
+
* 1. **`fly.swap` cannot sit downstream of another *prepared* op.** `lifi.zap`
|
|
92
|
+
* (used here) and other plain ops are fine. `lifi.swap` and `paraswap.buy`
|
|
93
|
+
* are prepared, and the compiler rejects those chains with a message about
|
|
94
|
+
* splitting the flow across a continuation.
|
|
95
|
+
* 2. **The upstream must deliver to the proxy.** `lifi.zap` does so by
|
|
96
|
+
* construction. An upstream that exposes its own delivery address — such as
|
|
97
|
+
* `morphoBlue.withdraw`'s `receiver` — must have it bound to
|
|
98
|
+
* `context.executionAddress`, or the amount it reports will diverge from what
|
|
99
|
+
* the proxy actually holds and the op's custody assertion reverts.
|
|
100
|
+
*
|
|
101
|
+
* Demonstrates:
|
|
102
|
+
* - A variable-input swap consuming an upstream's entire, runtime-determined output
|
|
103
|
+
* - `lifi.zap` as the nondeterministic producer (a PT exit through an AMM quote)
|
|
104
|
+
* - `sweepTo`, which is what returns any out-of-band residue to the sender
|
|
105
|
+
*
|
|
106
|
+
* `fly.swap` is always registered on the backend; without `FLY_API_KEY` it uses
|
|
107
|
+
* Fly's public host.
|
|
108
|
+
*
|
|
109
|
+
* See `docs/references/fly-api.md` for the router behaviour all of this rests on.
|
|
110
|
+
*/
|
|
111
|
+
export const buildFlySwapExample = (): {
|
|
112
|
+
flow: Flow;
|
|
113
|
+
request: ComposeCompileRequest;
|
|
114
|
+
} => {
|
|
115
|
+
const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
|
|
116
|
+
|
|
117
|
+
const builder = sdk.flow(BASE, {
|
|
118
|
+
name: 'pt-usdc-exit-swap-all-to-weth',
|
|
119
|
+
inputs: {
|
|
120
|
+
amountIn: resources.erc20(PT_USDC, BASE),
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Exit the PT position to its underlying USDC. `lifi.zap` declares no
|
|
125
|
+
// output-amount estimator, so the compiler cannot resolve the next node's
|
|
126
|
+
// input statically and instead simulates this node to read what it really
|
|
127
|
+
// produced. That simulated value is what `fly.swap` quotes against, and it is
|
|
128
|
+
// why a 50 bps band is enough.
|
|
129
|
+
const exitPt = builder.lifi.zap('exitPt', {
|
|
130
|
+
bind: { amountIn: builder.inputs.amountIn },
|
|
131
|
+
config: {
|
|
132
|
+
resourceOut: resources.erc20(USDC, BASE),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Swap the entire exit output to WETH.
|
|
137
|
+
//
|
|
138
|
+
// No slippage guard here, and none is accepted: `amountOut` declares
|
|
139
|
+
// `providesMinimum`, so the op owns its own bound. Attaching one is a
|
|
140
|
+
// `guard_error`. The published `amountOutMin` is the op's, derived from the
|
|
141
|
+
// bottom of the input band.
|
|
142
|
+
builder.fly.swap('swapAllToWeth', {
|
|
143
|
+
bind: { amountIn: exitPt.amountOut },
|
|
144
|
+
config: {
|
|
145
|
+
resourceOut: resources.erc20(WETH, BASE),
|
|
146
|
+
// The exit is a same-block simulation, so only drift needs covering.
|
|
147
|
+
inputVarianceBps: 50,
|
|
148
|
+
// Price protection on the USDC/WETH pair, independent of the above.
|
|
149
|
+
maxSlippageBps: 100,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const flow = builder.build();
|
|
154
|
+
|
|
155
|
+
const request = sdk.request(flow, {
|
|
156
|
+
signer: OWNER,
|
|
157
|
+
inputs: {
|
|
158
|
+
// 1 PT-USDC (6 decimals).
|
|
159
|
+
amountIn: materialisers.directDeposit({ amount: '1000000' }),
|
|
160
|
+
},
|
|
161
|
+
// Load-bearing, not boilerplate: this is what returns the unconsumed input
|
|
162
|
+
// to the sender if the exit over-delivers past the top of the band.
|
|
163
|
+
sweepTo: builder.context.sender,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return { flow, request };
|
|
167
|
+
};
|
package/src/examples/run.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* split-arithmetic — split USDC 70/30 with arithmetic assertions
|
|
22
22
|
* zap — zap USDC into Aave lending position
|
|
23
23
|
* zap-async — zap USDC into an async Aave position (future output)
|
|
24
|
+
* fly-swap — exit a Pendle PT position and swap ALL proceeds to WETH
|
|
24
25
|
* swap-zap — swap WETH to USDC then zap into Aave
|
|
25
26
|
* swap-check — swap WETH to USDC with balance check
|
|
26
27
|
* swap-recipient — swap WETH + DAI to USDC and send to recipient
|
|
@@ -35,13 +36,15 @@
|
|
|
35
36
|
* untyped-ref — mix untypedOp with typed handles via raw.ref
|
|
36
37
|
* route-exact — wrap 0.1 native ETH into WETH via /compose/route
|
|
37
38
|
* route-all — unwrap the whole WETH balance to ETH via /compose/route
|
|
39
|
+
* supported-chains — list the chains this deployment supports, and guard on them
|
|
38
40
|
*/
|
|
39
41
|
import type {
|
|
40
42
|
ComposeCompileRequest,
|
|
41
43
|
ComposeRouteRequest,
|
|
42
44
|
} from '@lifi/compose-spec';
|
|
43
45
|
|
|
44
|
-
import {
|
|
46
|
+
import { bigintReplacer } from '../client.js';
|
|
47
|
+
import { type ComposeSdk, createComposeSdk } from '../sdk.js';
|
|
45
48
|
|
|
46
49
|
import { buildAaveRepay } from './aaveRepay.js';
|
|
47
50
|
import { buildAaveRepayWithATokens } from './aaveRepayWithATokens.js';
|
|
@@ -53,6 +56,7 @@ import { buildConsolidateToEth } from './consolidateToEth.js';
|
|
|
53
56
|
import { buildConsolidateStablesToUsdc } from './consolidateToUsdc.js';
|
|
54
57
|
import { buildDepositFromProxy } from './depositFromProxy.js';
|
|
55
58
|
import { buildDustSweepExample } from './dustSweep.js';
|
|
59
|
+
import { buildFlySwapExample } from './flySwap.js';
|
|
56
60
|
import { buildLifiSwapExample } from './lifiSwap.js';
|
|
57
61
|
import { buildLifiZapExample } from './lifiZap.js';
|
|
58
62
|
import { buildLifiZapAsyncExample } from './lifiZapAsync.js';
|
|
@@ -64,6 +68,7 @@ import {
|
|
|
64
68
|
} from './routeTokenPair.js';
|
|
65
69
|
import { buildSplitAndZapExample } from './splitAndZap.js';
|
|
66
70
|
import { buildSplitWithArithmetic } from './splitWithArithmetic.js';
|
|
71
|
+
import { runSupportedChainsExample } from './supportedChains.js';
|
|
67
72
|
import { buildSwapAndZapExample } from './swapAndZap.js';
|
|
68
73
|
import { buildSwapToRecipient } from './swapToRecipient.js';
|
|
69
74
|
import { buildSwapWithAllowRevertExample } from './swapWithAllowRevert.js';
|
|
@@ -72,6 +77,9 @@ import { buildSwapWithOutputValidation } from './swapWithOutputValidation.js';
|
|
|
72
77
|
import { buildPartialTransfer, buildTransferTokens } from './transferTokens.js';
|
|
73
78
|
import { buildUntypedOpWithTypedRef } from './untypedOpWithTypedRef.js';
|
|
74
79
|
|
|
80
|
+
const stringifyJson = (value: unknown) =>
|
|
81
|
+
JSON.stringify(value, bigintReplacer, 2);
|
|
82
|
+
|
|
75
83
|
const EXAMPLES: Record<string, () => ComposeCompileRequest> = {
|
|
76
84
|
'aave-repay': () =>
|
|
77
85
|
buildAaveRepay({
|
|
@@ -119,6 +127,7 @@ const EXAMPLES: Record<string, () => ComposeCompileRequest> = {
|
|
|
119
127
|
zap: () => buildLifiZapExample().request,
|
|
120
128
|
'zap-async': () =>
|
|
121
129
|
buildLifiZapAsyncExample({ owner: OWNER, recipient: RECIPIENT }).request,
|
|
130
|
+
'fly-swap': () => buildFlySwapExample().request,
|
|
122
131
|
'swap-zap': () => buildSwapAndZapExample().request,
|
|
123
132
|
'swap-check': () =>
|
|
124
133
|
buildSwapWithBalanceCheck({ owner: OWNER, amount: '1000000000000000000' })
|
|
@@ -166,6 +175,12 @@ const ROUTE_EXAMPLES: Record<string, () => ComposeRouteRequest> = {
|
|
|
166
175
|
'route-all': buildRouteAllExample,
|
|
167
176
|
};
|
|
168
177
|
|
|
178
|
+
// Query examples read from the API instead of building a request to compile,
|
|
179
|
+
// so they take the sdk and print their own output.
|
|
180
|
+
const QUERY_EXAMPLES: Record<string, (sdk: ComposeSdk) => Promise<void>> = {
|
|
181
|
+
'supported-chains': runSupportedChainsExample,
|
|
182
|
+
};
|
|
183
|
+
|
|
169
184
|
const run = async () => {
|
|
170
185
|
const args = process.argv.slice(2);
|
|
171
186
|
const useStaged = args.includes('--staged');
|
|
@@ -186,11 +201,16 @@ const run = async () => {
|
|
|
186
201
|
examples = { ...EXAMPLES, ...stagedModule.STAGED_EXAMPLES };
|
|
187
202
|
}
|
|
188
203
|
|
|
204
|
+
const queryExample = name ? QUERY_EXAMPLES[name] : undefined;
|
|
189
205
|
const routeBuilder = name ? ROUTE_EXAMPLES[name] : undefined;
|
|
190
206
|
const builder = name ? examples[name] : undefined;
|
|
191
207
|
|
|
192
|
-
if (!builder && !routeBuilder) {
|
|
193
|
-
const valid = [
|
|
208
|
+
if (!builder && !routeBuilder && !queryExample) {
|
|
209
|
+
const valid = [
|
|
210
|
+
...Object.keys(examples),
|
|
211
|
+
...Object.keys(ROUTE_EXAMPLES),
|
|
212
|
+
...Object.keys(QUERY_EXAMPLES),
|
|
213
|
+
]
|
|
194
214
|
.sort()
|
|
195
215
|
.join(', ');
|
|
196
216
|
console.error(
|
|
@@ -204,18 +224,20 @@ const run = async () => {
|
|
|
204
224
|
}
|
|
205
225
|
console.log(`Running example: ${name}`);
|
|
206
226
|
|
|
207
|
-
const request = routeBuilder ? routeBuilder() : builder!();
|
|
208
227
|
const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
|
|
209
228
|
|
|
229
|
+
// Query examples do their own I/O and printing - there is no request to
|
|
230
|
+
// show and nothing to compile.
|
|
231
|
+
if (queryExample) {
|
|
232
|
+
console.log(`\n--- Querying ${BASE_URL} ---`);
|
|
233
|
+
await queryExample(sdk);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const request = routeBuilder ? routeBuilder() : builder!();
|
|
238
|
+
|
|
210
239
|
console.log('--- Request ---');
|
|
211
|
-
console.log(
|
|
212
|
-
JSON.stringify(
|
|
213
|
-
request,
|
|
214
|
-
(_key, value: unknown) =>
|
|
215
|
-
typeof value === 'bigint' ? value.toString() : value,
|
|
216
|
-
2,
|
|
217
|
-
),
|
|
218
|
-
);
|
|
240
|
+
console.log(stringifyJson(request));
|
|
219
241
|
|
|
220
242
|
console.log(`\n--- Compiling against ${BASE_URL} ---`);
|
|
221
243
|
const result = routeBuilder
|
|
@@ -223,7 +245,7 @@ const run = async () => {
|
|
|
223
245
|
: await sdk.client.compile(request as ComposeCompileRequest);
|
|
224
246
|
|
|
225
247
|
console.log('\n--- Result ---');
|
|
226
|
-
console.log(
|
|
248
|
+
console.log(stringifyJson(result));
|
|
227
249
|
};
|
|
228
250
|
|
|
229
251
|
run().catch((err: unknown) => {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { ChainSummary } from '../discovery.js';
|
|
2
|
+
import type { ComposeSdk } from '../sdk.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The slice of {@link ComposeSdk} these helpers need. Depending on the one
|
|
6
|
+
* capability rather than the whole SDK keeps them testable with a two-line
|
|
7
|
+
* fake, and documents that nothing here touches the compile path.
|
|
8
|
+
*/
|
|
9
|
+
export type SupportedChainsSource = Pick<ComposeSdk, 'getSupportedChains'>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A memoised view of the supported-chain list.
|
|
13
|
+
*/
|
|
14
|
+
export interface SupportedChainsCache {
|
|
15
|
+
/** Supported chains, ascending by `chainId`. Fetched once, then served from memory. */
|
|
16
|
+
readonly get: () => Promise<readonly ChainSummary[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Resolves when `chainId` is supported, rejects with an actionable message
|
|
19
|
+
* naming the alternatives when it is not.
|
|
20
|
+
*/
|
|
21
|
+
readonly assertSupported: (chainId: number) => Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Display label for `chainId`, or `undefined` when the deployment does not
|
|
24
|
+
* support it. Served from the same cached fetch as {@link get}.
|
|
25
|
+
*/
|
|
26
|
+
readonly nameOf: (chainId: number) => Promise<string | undefined>;
|
|
27
|
+
/** Drops the memoised list so the next {@link get} refetches. */
|
|
28
|
+
readonly refresh: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Wraps `sdk.getSupportedChains()` in a cache so a long-lived process pays for
|
|
33
|
+
* one round trip instead of one per check.
|
|
34
|
+
*
|
|
35
|
+
* The SDK deliberately does not cache the list — the backend's chain registry
|
|
36
|
+
* refreshes periodically, so the right lifetime is the caller's to choose.
|
|
37
|
+
* Chains are added rarely, so caching for the life of a request handler (or
|
|
38
|
+
* refreshing on a timer via {@link SupportedChainsCache.refresh}) is usually
|
|
39
|
+
* right.
|
|
40
|
+
*
|
|
41
|
+
* Demonstrates:
|
|
42
|
+
* - `sdk.getSupportedChains()`, the convenience accessor over `GET /chains`
|
|
43
|
+
* - Preflighting a chain so an unsupported one fails locally with a message
|
|
44
|
+
* naming the alternatives, instead of costing a server round trip
|
|
45
|
+
* - Turning the per-chain `name` into display labels without a second lookup
|
|
46
|
+
* or a hardcoded chain-name table
|
|
47
|
+
* - Memoising the in-flight promise, not just the settled value, so concurrent
|
|
48
|
+
* first callers share one request
|
|
49
|
+
*/
|
|
50
|
+
export const createSupportedChainsCache = (
|
|
51
|
+
sdk: SupportedChainsSource,
|
|
52
|
+
): SupportedChainsCache => {
|
|
53
|
+
let inFlight: Promise<readonly ChainSummary[]> | undefined;
|
|
54
|
+
|
|
55
|
+
const get = (): Promise<readonly ChainSummary[]> => {
|
|
56
|
+
// A rejected fetch must not be memoised, or one transient network blip
|
|
57
|
+
// would poison every later lookup.
|
|
58
|
+
inFlight ??= sdk.getSupportedChains().catch((err: unknown) => {
|
|
59
|
+
inFlight = undefined;
|
|
60
|
+
throw err;
|
|
61
|
+
});
|
|
62
|
+
return inFlight;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const find = async (chainId: number): Promise<ChainSummary | undefined> =>
|
|
66
|
+
(await get()).find((chain) => chain.chainId === chainId);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
get,
|
|
70
|
+
assertSupported: async (chainId: number): Promise<void> => {
|
|
71
|
+
if (await find(chainId)) return;
|
|
72
|
+
const chains = await get();
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Chain ${chainId} is not supported by this Compose deployment. ` +
|
|
75
|
+
`Supported chains: ${chains
|
|
76
|
+
.map((chain) => `${chain.name} (${chain.chainId})`)
|
|
77
|
+
.join(', ')}`,
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
nameOf: async (chainId: number): Promise<string | undefined> =>
|
|
81
|
+
(await find(chainId))?.name,
|
|
82
|
+
refresh: (): void => {
|
|
83
|
+
inFlight = undefined;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Ethereum mainnet — supported by every deployment. */
|
|
89
|
+
const MAINNET = 1;
|
|
90
|
+
/** Not an EVM chain ID any deployment serves; stands in for a bad input. */
|
|
91
|
+
const UNSUPPORTED = 999_999;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Harness entry point: prints the live chain list with names, then shows the
|
|
95
|
+
* guard accepting a supported chain and rejecting an unsupported one.
|
|
96
|
+
*/
|
|
97
|
+
export const runSupportedChainsExample = async (
|
|
98
|
+
sdk: SupportedChainsSource,
|
|
99
|
+
): Promise<void> => {
|
|
100
|
+
const chains = createSupportedChainsCache(sdk);
|
|
101
|
+
|
|
102
|
+
const supported = await chains.get();
|
|
103
|
+
console.log(`Supported chains (${supported.length}):`);
|
|
104
|
+
for (const { chainId, name } of supported) {
|
|
105
|
+
console.log(` ${chainId}\t${name}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await chains.assertSupported(MAINNET);
|
|
109
|
+
// Served from cache: no second round trip.
|
|
110
|
+
console.log(
|
|
111
|
+
`\n${await chains.nameOf(MAINNET)} (${MAINNET}) is supported — safe to build a request.`,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
await chains
|
|
115
|
+
.assertSupported(UNSUPPORTED)
|
|
116
|
+
.then(() => {
|
|
117
|
+
throw new Error(`expected chain ${UNSUPPORTED} to be rejected`);
|
|
118
|
+
})
|
|
119
|
+
.catch((err: unknown) => {
|
|
120
|
+
console.log(`\nGuard rejected as expected: ${(err as Error).message}`);
|
|
121
|
+
});
|
|
122
|
+
};
|
|
@@ -731,6 +731,30 @@ export interface CorePeekConfig {
|
|
|
731
731
|
args?: string[];
|
|
732
732
|
}
|
|
733
733
|
|
|
734
|
+
/**
|
|
735
|
+
* a number between 0 and 3318
|
|
736
|
+
*/
|
|
737
|
+
export type Between03318 = number;
|
|
738
|
+
/**
|
|
739
|
+
* a number between 0 and 5000
|
|
740
|
+
*/
|
|
741
|
+
export type Between05000 = number;
|
|
742
|
+
|
|
743
|
+
export interface FlySwapConfig {
|
|
744
|
+
resourceOut:
|
|
745
|
+
| {
|
|
746
|
+
kind: 'native';
|
|
747
|
+
chainId: Positive;
|
|
748
|
+
}
|
|
749
|
+
| {
|
|
750
|
+
kind: 'erc20';
|
|
751
|
+
token: Address;
|
|
752
|
+
chainId: Positive;
|
|
753
|
+
};
|
|
754
|
+
inputVarianceBps?: Between03318;
|
|
755
|
+
maxSlippageBps?: Between05000;
|
|
756
|
+
}
|
|
757
|
+
|
|
734
758
|
export interface SlippageGuardConfig {
|
|
735
759
|
port: string;
|
|
736
760
|
bps: Between010000;
|
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
CoreSplitConfig,
|
|
36
36
|
CoreStaticCallConfig,
|
|
37
37
|
CoreTransferConfig,
|
|
38
|
+
FlySwapConfig,
|
|
38
39
|
InvariantEqConfig,
|
|
39
40
|
InvariantGtConfig,
|
|
40
41
|
InvariantGteConfig,
|
|
@@ -550,6 +551,15 @@ export interface CorePeekArgs {
|
|
|
550
551
|
readonly guards?: readonly TypedGuard<'result'>[];
|
|
551
552
|
}
|
|
552
553
|
|
|
554
|
+
/** Swap via Fly's DexAggregator, sized at runtime: the router calldata leaves `amountIn` unset, so an upstream op's output can feed the swap without being known at plan time. Either leg may be the chain-native coin. */
|
|
555
|
+
export interface FlySwapArgs {
|
|
556
|
+
readonly bind: {
|
|
557
|
+
readonly amountIn: Bindable<'resource'>;
|
|
558
|
+
};
|
|
559
|
+
readonly config: FlySwapConfig;
|
|
560
|
+
readonly guards?: readonly TypedGuard<'unspentIn'>[];
|
|
561
|
+
}
|
|
562
|
+
|
|
553
563
|
export interface CoreSplitOutputs {
|
|
554
564
|
readonly a: OutputHandle<'resource'>;
|
|
555
565
|
readonly b: OutputHandle<'resource'>;
|
|
@@ -651,6 +661,11 @@ export interface CorePeekOutputs {
|
|
|
651
661
|
readonly result: OutputHandle<'uint256'>;
|
|
652
662
|
}
|
|
653
663
|
|
|
664
|
+
export interface FlySwapOutputs {
|
|
665
|
+
readonly amountOut: OutputHandle<'resource'>;
|
|
666
|
+
readonly unspentIn: OutputHandle<'resource'>;
|
|
667
|
+
}
|
|
668
|
+
|
|
654
669
|
export type GeneratedOps = {
|
|
655
670
|
readonly core: {
|
|
656
671
|
/** Split a resource into two outputs by basis-point ratio — original id: "core.split" */
|
|
@@ -858,6 +873,10 @@ export type GeneratedOps = {
|
|
|
858
873
|
/** Exact-output (BUY) swap via the Paraswap/Velora Market API: produce exactly `exactAmountOut` of the destination token, pulling only the source needed and exposing the genuinely-unspent source as `unspentIn`. — original id: "paraswap.buy" */
|
|
859
874
|
readonly buy: (nodeId: string, args: ParaswapBuyArgs) => ParaswapBuyOutputs;
|
|
860
875
|
};
|
|
876
|
+
readonly fly: {
|
|
877
|
+
/** Swap via Fly's DexAggregator, sized at runtime: the router calldata leaves `amountIn` unset, so an upstream op's output can feed the swap without being known at plan time. Either leg may be the chain-native coin. — original id: "fly.swap" */
|
|
878
|
+
readonly swap: (nodeId: string, args: FlySwapArgs) => FlySwapOutputs;
|
|
879
|
+
};
|
|
861
880
|
};
|
|
862
881
|
|
|
863
882
|
export const bindGeneratedOps = (
|
|
@@ -1100,4 +1119,11 @@ export const bindGeneratedOps = (
|
|
|
1100
1119
|
unspentIn: 'resource',
|
|
1101
1120
|
} as const),
|
|
1102
1121
|
},
|
|
1122
|
+
fly: {
|
|
1123
|
+
swap: (nodeId: string, args: FlySwapArgs) =>
|
|
1124
|
+
builder.call(nodeId, 'fly.swap', args, {
|
|
1125
|
+
amountOut: 'resource',
|
|
1126
|
+
unspentIn: 'resource',
|
|
1127
|
+
} as const),
|
|
1128
|
+
},
|
|
1103
1129
|
});
|
package/src/index.ts
CHANGED
|
@@ -19,9 +19,11 @@ export type {
|
|
|
19
19
|
OutputKind,
|
|
20
20
|
TypedGuard,
|
|
21
21
|
TypedRef,
|
|
22
|
+
TypedLiteral,
|
|
22
23
|
} from './types.js';
|
|
23
24
|
|
|
24
25
|
export type {
|
|
26
|
+
ChainSummary,
|
|
25
27
|
GetZapPacksOptions,
|
|
26
28
|
ZapPackEdge,
|
|
27
29
|
ZapPackOverview,
|
|
@@ -29,6 +31,8 @@ export type {
|
|
|
29
31
|
export type {
|
|
30
32
|
ComposeError,
|
|
31
33
|
ComposeErrorCode,
|
|
34
|
+
ComposeSdkOutdated,
|
|
35
|
+
ComposeServerOutdated,
|
|
32
36
|
ComposePreparationOps,
|
|
33
37
|
} from './errors.js';
|
|
34
38
|
export type {
|
|
@@ -36,10 +40,13 @@ export type {
|
|
|
36
40
|
ApprovalEntry,
|
|
37
41
|
Call,
|
|
38
42
|
ComposeCompilePartialData,
|
|
43
|
+
ComposeAction,
|
|
44
|
+
ComposeFill,
|
|
45
|
+
ComposeFee,
|
|
46
|
+
ComposeUserProgramSimulation,
|
|
39
47
|
ComposeCompileRequest,
|
|
40
48
|
ComposeCompileResult,
|
|
41
49
|
ComposeCompileSuccessData,
|
|
42
|
-
ComposeContinuation,
|
|
43
50
|
ComposeErrorKind,
|
|
44
51
|
ComposeRouteRequest,
|
|
45
52
|
ComposeTransactionRequest,
|
|
@@ -51,11 +58,13 @@ export type {
|
|
|
51
58
|
Precondition,
|
|
52
59
|
PreconditionConfigOf,
|
|
53
60
|
PriceImpact,
|
|
61
|
+
ProducedResource,
|
|
54
62
|
Ref,
|
|
55
63
|
Resource,
|
|
56
64
|
RouteAmount,
|
|
57
65
|
RouteAmountAll,
|
|
58
66
|
RouteAmountExact,
|
|
67
|
+
SimulatedAmounts,
|
|
59
68
|
SimulationPolicy,
|
|
60
69
|
SimulationRevert,
|
|
61
70
|
SolType,
|
|
@@ -83,10 +92,17 @@ export {
|
|
|
83
92
|
SIMULATE_MAX_TRACKED_BALANCES,
|
|
84
93
|
SIMULATE_MAX_REQUIREMENTS,
|
|
85
94
|
} from '@lifi/compose-spec';
|
|
86
|
-
export {
|
|
95
|
+
export {
|
|
96
|
+
isComposeError,
|
|
97
|
+
isComposePreparationError,
|
|
98
|
+
isComposeSdkOutdatedError,
|
|
99
|
+
isComposeServerOutdatedError,
|
|
100
|
+
} from './errors.js';
|
|
87
101
|
|
|
88
102
|
export type {
|
|
89
103
|
Bindable,
|
|
104
|
+
RefBindable,
|
|
105
|
+
AnyRefBindable,
|
|
90
106
|
InputHandle,
|
|
91
107
|
ResourceInputHandle,
|
|
92
108
|
OutputHandle,
|
package/src/raw.ts
CHANGED
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
* For most use cases, prefer the generated operation methods on {@link FlowBuilder}
|
|
7
7
|
* (e.g. `builder.lifi.swap(...)`) and the typed guard/materialiser helpers.
|
|
8
8
|
*/
|
|
9
|
-
export { rawGuard as guard, ref } from './authoring/raw.js';
|
|
9
|
+
export { rawGuard as guard, literal, ref } from './authoring/raw.js';
|
|
10
10
|
export { materialiser } from './run/inputs.js';
|
package/src/responseSchemas.ts
CHANGED
|
@@ -131,6 +131,9 @@ export interface ServerErrorBody {
|
|
|
131
131
|
readonly kind?: string;
|
|
132
132
|
readonly message?: string;
|
|
133
133
|
readonly path?: string;
|
|
134
|
+
readonly sdkVersion?: string;
|
|
135
|
+
readonly serverVersion?: string;
|
|
136
|
+
readonly minimumSdkVersion?: string;
|
|
134
137
|
readonly details?: SimulationRevert;
|
|
135
138
|
readonly failedOps?: readonly ServerFailedPreparedOp[];
|
|
136
139
|
readonly succeededOps?: readonly string[];
|
|
@@ -158,6 +161,9 @@ const serverErrorBodySchema = z.object({
|
|
|
158
161
|
kind: z.string().optional(),
|
|
159
162
|
message: z.string().optional(),
|
|
160
163
|
path: z.string().optional(),
|
|
164
|
+
sdkVersion: z.string().optional(),
|
|
165
|
+
serverVersion: z.string().optional(),
|
|
166
|
+
minimumSdkVersion: z.string().optional(),
|
|
161
167
|
details: z.custom<SimulationRevert>().optional(),
|
|
162
168
|
failedOps: z.array(failedPreparedOpSchema).readonly().optional(),
|
|
163
169
|
succeededOps: z.array(z.string()).readonly().optional(),
|
package/src/run/inputs.ts
CHANGED
|
@@ -54,6 +54,13 @@ export interface ComposeRunInput<T extends InputSchema = InputSchema> {
|
|
|
54
54
|
* result with the transaction and revert diagnostics.
|
|
55
55
|
*/
|
|
56
56
|
readonly simulationPolicy?: SimulationPolicy;
|
|
57
|
+
/**
|
|
58
|
+
* When `true`, the server also simulates the final user-facing transaction
|
|
59
|
+
* after the structured simulation succeeds; reverts honour
|
|
60
|
+
* `simulationPolicy`. On success the result is echoed back on the response as
|
|
61
|
+
* `userProgramSimulation`. Defaults to `false`.
|
|
62
|
+
*/
|
|
63
|
+
readonly simulateUserProgram?: boolean;
|
|
57
64
|
/**
|
|
58
65
|
* When `true`, the server checks on-chain ERC-20 allowances and omits
|
|
59
66
|
* approvals that are already sufficient. Defaults to `false`.
|
|
@@ -89,6 +96,21 @@ export const materialiser = (
|
|
|
89
96
|
...config,
|
|
90
97
|
});
|
|
91
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Drops the keys whose value is `undefined`, so optional fields are omitted
|
|
101
|
+
* from the wire payload rather than serialized as `null`.
|
|
102
|
+
*
|
|
103
|
+
* A local copy of `pickDefined` from `@lifi/utils`: this package is published
|
|
104
|
+
* to npm and `@lifi/utils` is a private workspace package, so it cannot be a
|
|
105
|
+
* runtime dependency here.
|
|
106
|
+
*/
|
|
107
|
+
const pickDefined = <S extends Record<string, unknown>>(
|
|
108
|
+
obj: S,
|
|
109
|
+
): Partial<{ [K in keyof S]: Exclude<S[K], undefined> }> =>
|
|
110
|
+
Object.fromEntries(
|
|
111
|
+
Object.entries(obj).filter(([, value]) => value !== undefined),
|
|
112
|
+
) as Partial<{ [K in keyof S]: Exclude<S[K], undefined> }>;
|
|
113
|
+
|
|
92
114
|
/**
|
|
93
115
|
* Assembles a {@link ComposeRun} object from individual parameters.
|
|
94
116
|
* Used internally by the SDK; prefer {@link ComposeRunInput} for external use.
|
|
@@ -105,24 +127,21 @@ export const buildRun = (run: {
|
|
|
105
127
|
maxPriceImpactBps?: number;
|
|
106
128
|
sweepTo?: SweepTo;
|
|
107
129
|
simulationPolicy?: SimulationPolicy;
|
|
130
|
+
simulateUserProgram?: boolean;
|
|
108
131
|
checkOnChainAllowances?: boolean;
|
|
109
132
|
}): ComposeRun => ({
|
|
110
133
|
inputs: run.inputs,
|
|
111
|
-
...(run.preconditions?.length && { preconditions: run.preconditions }),
|
|
112
134
|
signer: run.signer,
|
|
113
|
-
...(
|
|
114
|
-
|
|
115
|
-
|
|
135
|
+
...pickDefined({
|
|
136
|
+
// An empty precondition list is the same as none, so drop it too.
|
|
137
|
+
preconditions: run.preconditions?.length ? run.preconditions : undefined,
|
|
138
|
+
assumptions: run.assumptions,
|
|
139
|
+
referrer: run.referrer,
|
|
116
140
|
integratorFeeBps: run.integratorFeeBps,
|
|
117
|
-
}),
|
|
118
|
-
...(run.maxPriceImpactBps !== undefined && {
|
|
119
141
|
maxPriceImpactBps: run.maxPriceImpactBps,
|
|
120
|
-
|
|
121
|
-
...(run.sweepTo !== undefined && { sweepTo: run.sweepTo }),
|
|
122
|
-
...(run.simulationPolicy !== undefined && {
|
|
142
|
+
sweepTo: run.sweepTo,
|
|
123
143
|
simulationPolicy: run.simulationPolicy,
|
|
124
|
-
|
|
125
|
-
...(run.checkOnChainAllowances !== undefined && {
|
|
144
|
+
simulateUserProgram: run.simulateUserProgram,
|
|
126
145
|
checkOnChainAllowances: run.checkOnChainAllowances,
|
|
127
146
|
}),
|
|
128
147
|
});
|