@lifi/composer-sdk 0.2.0-staging.4 → 0.2.0-staging.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/authoring/handles.cjs.map +1 -1
  2. package/dist/authoring/handles.d.cts +13 -6
  3. package/dist/authoring/handles.d.ts +13 -6
  4. package/dist/authoring/handles.js.map +1 -1
  5. package/dist/client.cjs +1 -1
  6. package/dist/client.js +1 -1
  7. package/dist/generated/config.generated.cjs.map +1 -1
  8. package/dist/generated/config.generated.d.cts +39 -1
  9. package/dist/generated/config.generated.d.ts +39 -1
  10. package/dist/generated/operations.generated.cjs +55 -1
  11. package/dist/generated/operations.generated.cjs.map +1 -1
  12. package/dist/generated/operations.generated.d.cts +204 -2
  13. package/dist/generated/operations.generated.d.ts +204 -2
  14. package/dist/generated/operations.generated.js +55 -1
  15. package/dist/generated/operations.generated.js.map +1 -1
  16. package/dist/index.d.cts +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/package.json +2 -2
  19. package/src/authoring/handles.ts +19 -10
  20. package/src/examples/aaveRepay.ts +55 -16
  21. package/src/examples/aaveRepayWithATokens.ts +41 -30
  22. package/src/examples/aaveRounding.ts +42 -0
  23. package/src/examples/aaveSetEMode.ts +0 -1
  24. package/src/examples/config.ts +1 -1
  25. package/src/examples/emitCustomEvent.ts +75 -0
  26. package/src/examples/invariantChecks.ts +96 -0
  27. package/src/examples/run.ts +20 -18
  28. package/src/examples/staged/aaveBorrow.ts +38 -14
  29. package/src/examples/staged/aaveToMorphoDebtMigration.ts +13 -3
  30. package/src/examples/staged/aaveToMorphoDebtMigrationWithSwap.ts +12 -2
  31. package/src/examples/staged/aaveToMorphoDebtMigrationWithSwapExactOut.ts +5 -0
  32. package/src/examples/staged/flashloanRepay.ts +4 -0
  33. package/src/examples/staged/morphoBlueBorrow.ts +4 -0
  34. package/src/examples/staged/morphoBlueRepay.ts +59 -18
  35. package/src/examples/staged/registry.ts +7 -5
  36. package/src/examples/untypedOpWithTypedRef.ts +26 -20
  37. package/src/generated/config.generated.ts +50 -0
  38. package/src/generated/operations.generated.ts +330 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/composer-sdk",
3
- "version": "0.2.0-staging.4",
3
+ "version": "0.2.0-staging.5",
4
4
  "description": "Public Composer SDK for building and submitting flows",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "example:staged": "tsx src/examples/run.ts --staged"
52
52
  },
53
53
  "peerDependencies": {
54
- "@lifi/compose-spec": "0.2.0-staging.4"
54
+ "@lifi/compose-spec": "0.2.0-staging.5"
55
55
  },
56
56
  "dependencies": {
57
57
  "abitype": "^1.2.3",
@@ -1,4 +1,4 @@
1
- import type { Resource } from '@lifi/compose-spec';
1
+ import type { Resource, StaticSolType } from '@lifi/compose-spec';
2
2
 
3
3
  import type { OutputKind, TypedRef } from '../types.js';
4
4
 
@@ -59,23 +59,32 @@ export const handleToRef = (h: AnyHandle): { $ref: string } => {
59
59
  };
60
60
 
61
61
  /**
62
- * For a bind slot expecting type T, also accept `'resource'`-tagged
63
- * handles when T is `'uint256'` (resources are uint256 amounts).
62
+ * For a bind slot expecting type T, widen the set of accepted handle types:
63
+ * - `'bytes32'` is the raw 32-byte word type: accept any static-32-byte handle
64
+ * (`uint256`/`uintN`/`address`/`bool`/`bytes32`) plus `'resource'` (a uint256
65
+ * amount). At the IR1 boundary every value is a type-erased word, so any
66
+ * static-32-byte handle is a sound bind for a `bytes32` payload port.
67
+ * - `'uint256'` keeps its existing widening to also accept `'resource'`-tagged
68
+ * handles (resources are uint256 amounts).
69
+ * - every other type keeps exact-SolType matching.
64
70
  */
65
- type ResourceAssignable<T extends OutputKind> = T extends 'uint256'
66
- ? T | 'resource'
67
- : T;
71
+ type SlotAssignable<T extends OutputKind> = T extends 'bytes32'
72
+ ? StaticSolType | 'resource'
73
+ : T extends 'uint256'
74
+ ? T | 'resource'
75
+ : T;
68
76
 
69
77
  /**
70
78
  * The set of values accepted by a typed bind slot.
71
79
  * - `OutputHandle<T>` / `InputHandle<T>` (same type)
72
80
  * - For `'uint256'` slots, also `'resource'`-tagged handles
73
- * - `TypedRef<T>` (typed context refs, subject to `ResourceAssignable`)
81
+ * - For `'bytes32'` slots, any static-32-byte handle (and `'resource'`)
82
+ * - `TypedRef<T>` (typed context refs, subject to `SlotAssignable`)
74
83
  *
75
84
  * Plain `{ $ref: '...' }` objects are **not** accepted — use `raw.ref<T>()`
76
85
  * to create a typed ref when you need to reference a raw path.
77
86
  */
78
87
  export type Bindable<T extends OutputKind> =
79
- | OutputHandle<ResourceAssignable<T>>
80
- | InputHandle<ResourceAssignable<T>>
81
- | TypedRef<ResourceAssignable<T>>;
88
+ | OutputHandle<SlotAssignable<T>>
89
+ | InputHandle<SlotAssignable<T>>
90
+ | TypedRef<SlotAssignable<T>>;
@@ -7,29 +7,44 @@ import { BASE_URL } from './config.js';
7
7
 
8
8
  // Aave v3 contracts on Ethereum mainnet.
9
9
  const AAVE_V3_POOL = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2';
10
+ const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
10
11
  const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
12
+ // Aave v3 aEthUSDC receipt token and variable-debt WETH token.
13
+ const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c';
14
+ const VDEBT_WETH = '0xeA51d7853EEFb32b6ee06b1C12E6dcCA88Be0fFE';
11
15
 
12
16
  export interface AaveRepayInput {
13
17
  readonly owner: Address;
14
- readonly amount: `${bigint}`;
18
+ /** USDC supplied as collateral so the proxy can borrow WETH (6 dp). */
19
+ readonly collateralAmount: `${bigint}`;
20
+ /** WETH borrowed against the collateral (18 dp). */
21
+ readonly borrowAmount: `${bigint}`;
22
+ /**
23
+ * WETH deposited to repay the debt (18 dp). Must exceed `borrowAmount`:
24
+ * Aave's scaled-balance rounding can leave the debt 1 wei above the
25
+ * borrowed amount, so the `max`-mode repay needs a little headroom.
26
+ */
27
+ readonly repayAmount: `${bigint}`;
15
28
  }
16
29
 
17
30
  /**
18
- * Repay an Aave v3 variable-rate WETH debt by supplying WETH to the proxy.
19
- *
20
- * Aave internally caps the repay at `min(amount, outstandingDebt)`, so the
21
- * op returns any unspent WETH on the `residual` resource port. The
22
- * residual is swept back to the sender on completion.
31
+ * Supply USDC to Aave v3, borrow WETH against it, then repay the WETH borrow —
32
+ * a self-contained supply → borrow → repay round trip.
23
33
  *
24
34
  * Demonstrates:
25
- * - `aave.repay` with a resource input (the WETH used to repay).
26
- * - Binding `onBehalfOf` from the runtime context — the proxy repays its
27
- * own debt position.
28
- * - The `residual` port for surfacing any unused input back to the caller.
35
+ * - `lifi.zap` to supply USDC into Aave (USDC → aEthUSDC routing edge)
36
+ * - `aave.borrow` to mint variable WETH debt against the supplied collateral
37
+ * - `aave.repay` (mode: 'max') funded by a fresh WETH deposit, clearing the
38
+ * debt exactly so the collateral is freed
39
+ * - `sweepTo` to return all leftover proxy-held resources (the freed
40
+ * aEthUSDC collateral, the borrowed WETH, and the repay residual) to the
41
+ * sender
29
42
  */
30
43
  export const buildAaveRepay = ({
31
44
  owner,
32
- amount,
45
+ collateralAmount,
46
+ borrowAmount,
47
+ repayAmount,
33
48
  }: AaveRepayInput): {
34
49
  flow: Flow;
35
50
  request: ComposeCompileRequest;
@@ -39,18 +54,40 @@ export const buildAaveRepay = ({
39
54
  const builder = sdk.flow(1, {
40
55
  name: 'aave-repay-weth',
41
56
  inputs: {
42
- assetIn: resources.erc20(WETH, 1),
57
+ collateralIn: resources.erc20(USDC, 1),
58
+ repayIn: resources.erc20(WETH, 1),
59
+ },
60
+ });
61
+
62
+ // Supply USDC as collateral (USDC → aEthUSDC) so the proxy can borrow.
63
+ builder.lifi.zap('supply', {
64
+ bind: { amountIn: builder.inputs.collateralIn },
65
+ config: { resourceOut: resources.erc20(A_ETH_USDC, 1) },
66
+ });
67
+
68
+ // Borrow WETH against the collateral; the borrowed WETH stays on the
69
+ // proxy as a terminal resource and is swept back to the sender.
70
+ builder.aave.borrow('borrow', {
71
+ bind: {},
72
+ config: {
73
+ pool: AAVE_V3_POOL,
74
+ asset: WETH,
75
+ variableDebtToken: VDEBT_WETH,
76
+ amount: borrowAmount,
43
77
  },
44
78
  });
45
79
 
80
+ // Repay the just-opened WETH debt from the deposited WETH. mode 'max'
81
+ // passes type(uint256).max so Aave clamps to the outstanding debt exactly
82
+ // — required to clear the borrow flag and free the collateral for sweeping.
46
83
  builder.aave.repay('repay', {
47
84
  bind: {
48
- assetIn: builder.inputs.assetIn,
85
+ assetIn: builder.inputs.repayIn,
49
86
  onBehalfOf: builder.context.executionAddress,
50
87
  },
51
88
  config: {
52
89
  pool: AAVE_V3_POOL,
53
- mode: 'exact',
90
+ mode: 'max',
54
91
  },
55
92
  });
56
93
 
@@ -59,9 +96,11 @@ export const buildAaveRepay = ({
59
96
  const request = sdk.request(flow, {
60
97
  signer: owner,
61
98
  inputs: {
62
- assetIn: materialisers.directDeposit({ amount }),
99
+ collateralIn: materialisers.directDeposit({ amount: collateralAmount }),
100
+ repayIn: materialisers.directDeposit({ amount: repayAmount }),
63
101
  },
64
- // Any unspent WETH (residual) is swept back to the signer.
102
+ // Once the debt is cleared, sweep the freed aEthUSDC collateral, the
103
+ // borrowed WETH, and the repay residual back to the sender.
65
104
  sweepTo: builder.context.sender,
66
105
  });
67
106
 
@@ -1,11 +1,6 @@
1
1
  import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
2
2
 
3
- import {
4
- createComposeSdk,
5
- materialisers,
6
- preconditions,
7
- resources,
8
- } from '../index.js';
3
+ import { createComposeSdk, materialisers, resources } from '../index.js';
9
4
  import type { Address } from '../types.js';
10
5
 
11
6
  import { BASE_URL } from './config.js';
@@ -13,32 +8,33 @@ import { BASE_URL } from './config.js';
13
8
  // Aave v3 contracts on Ethereum mainnet.
14
9
  const AAVE_V3_POOL = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2';
15
10
  const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
11
+ // Aave v3 aEthUSDC receipt token and variable-debt USDC token.
16
12
  const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c';
13
+ const VDEBT_USDC = '0x72E95b8931767C79bA4EeE721354d6E99a61D004';
17
14
 
18
15
  export interface AaveRepayWithATokensInput {
19
16
  readonly owner: Address;
20
- readonly proxyAddress: Address;
21
- readonly expectedATokenBalance: `${bigint}`;
17
+ /** USDC supplied as collateral, minting the aUSDC burned by the repay (6 dp). */
18
+ readonly collateralAmount: `${bigint}`;
19
+ /** USDC borrowed against the collateral, then repaid by this flow (6 dp). */
20
+ readonly borrowAmount: `${bigint}`;
22
21
  }
23
22
 
24
23
  /**
25
- * Repay an Aave v3 variable-rate USDC debt by burning aToken collateral
26
- * already held by the proxy (no fresh asset transfer required).
27
- *
28
- * The aTokens are sourced from the proxy's existing balance via the
29
- * `balanceOf` materialiser. The `erc20Balance` precondition fails fast
30
- * if the expected aTokens haven't landed on the proxy yet.
24
+ * Supply USDC to Aave v3, borrow USDC against it, then repay the debt by
25
+ * burning the supplied aUSDC directly — no fresh asset transfer required.
31
26
  *
32
27
  * Demonstrates:
33
- * - `aave.repayWithATokens` with the aToken as the resource input.
34
- * - `balanceOf` materialiser to consume proxy-held aTokens directly.
35
- * - `erc20Balance` precondition to guard against silent zero-value repays.
36
- * - The `residual` port returns any aTokens not burned (when debt < input).
28
+ * - `lifi.zap` to supply USDC into Aave (USDC → aEthUSDC routing edge)
29
+ * - `aave.borrow` to mint variable USDC debt against the supplied collateral
30
+ * - `aave.repayWithATokens` to clear the debt by burning the aUSDC collateral
31
+ * - `sweepTo` to return the borrowed USDC and the unburned aUSDC to the
32
+ * sender once the debt is cleared
37
33
  */
38
34
  export const buildAaveRepayWithATokens = ({
39
35
  owner,
40
- proxyAddress,
41
- expectedATokenBalance,
36
+ collateralAmount,
37
+ borrowAmount,
42
38
  }: AaveRepayWithATokensInput): {
43
39
  flow: Flow;
44
40
  request: ComposeCompileRequest;
@@ -48,12 +44,32 @@ export const buildAaveRepayWithATokens = ({
48
44
  const builder = sdk.flow(1, {
49
45
  name: 'aave-repay-with-atokens',
50
46
  inputs: {
51
- aTokenIn: resources.erc20(A_ETH_USDC, 1),
47
+ collateralIn: resources.erc20(USDC, 1),
48
+ },
49
+ });
50
+
51
+ // Supply USDC as collateral (USDC → aEthUSDC); the aUSDC is burned below.
52
+ const supplyOut = builder.lifi.zap('supply', {
53
+ bind: { amountIn: builder.inputs.collateralIn },
54
+ config: { resourceOut: resources.erc20(A_ETH_USDC, 1) },
55
+ });
56
+
57
+ // Borrow USDC against the supplied collateral to open a debt position.
58
+ builder.aave.borrow('borrow', {
59
+ bind: {},
60
+ config: {
61
+ pool: AAVE_V3_POOL,
62
+ asset: USDC,
63
+ variableDebtToken: VDEBT_USDC,
64
+ amount: borrowAmount,
52
65
  },
53
66
  });
54
67
 
68
+ // Repay the USDC debt by burning the aUSDC collateral directly. The aToken
69
+ // input exceeds the debt, so Aave clamps the payback to the outstanding
70
+ // debt exactly — clearing the borrow flag and freeing the rest for the sweep.
55
71
  builder.aave.repayWithATokens('repay-atokens', {
56
- bind: { aTokenIn: builder.inputs.aTokenIn },
72
+ bind: { aTokenIn: supplyOut.amountOut },
57
73
  config: {
58
74
  pool: AAVE_V3_POOL,
59
75
  asset: USDC,
@@ -65,15 +81,10 @@ export const buildAaveRepayWithATokens = ({
65
81
  const request = sdk.request(flow, {
66
82
  signer: owner,
67
83
  inputs: {
68
- aTokenIn: materialisers.balanceOf({ owner: proxyAddress }),
84
+ collateralIn: materialisers.directDeposit({ amount: collateralAmount }),
69
85
  },
70
- preconditions: [
71
- preconditions.erc20Balance({
72
- wallet: proxyAddress,
73
- token: A_ETH_USDC,
74
- balance: expectedATokenBalance,
75
- }),
76
- ],
86
+ // Once the debt is cleared, sweep the borrowed USDC and the unburned
87
+ // aUSDC collateral back to the sender.
77
88
  sweepTo: builder.context.sender,
78
89
  });
79
90
 
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Helpers for Aave v3's scaled-balance rounding.
3
+ *
4
+ * Aave v3 does not store balances directly. Every aToken / variable-debt
5
+ * balance is kept as scaled shares and converted on each access using the
6
+ * market's live index (RAY = 1e27 precision):
7
+ *
8
+ * - write: shares = amount.rayDiv(index) — round half-up to 1 ray-wei
9
+ * - read: balance = shares.rayMul(index) — round half-up to 1 wei
10
+ *
11
+ * The write+read round trip therefore drifts by up to ~index/RAY wei in
12
+ * either direction — about 1 wei while the index is below ~2e27, which
13
+ * holds for all current Aave v3 markets. Concretely, in the same block:
14
+ *
15
+ * - a debt opened with `borrow(x)` can read back as `x + 1` wei, and
16
+ * - collateral supplied with `supply(x)` can read back as `x - 1` wei.
17
+ *
18
+ * The drift direction depends on how the amount aligns with the index
19
+ * remainder, and the index accrues every block — so a simulation that
20
+ * passes with exact amounts at block N can still revert on-chain at block
21
+ * N+k. Any flow that settles an exact amount against a same-block Aave
22
+ * readback (flashloan legs, exact transfers) must budget the worst case:
23
+ *
24
+ * - `padForAaveDebtRounding` — for amounts that must *cover* a same-block
25
+ * debt readback, e.g. a flashloan principal funding a max-mode repay.
26
+ * - `trimForAaveWithdrawalRounding` — for amounts that must be *covered
27
+ * by* a same-block balance readback, e.g. a flashloan leg repaid from
28
+ * an Aave withdrawal.
29
+ *
30
+ * The over- or under-shoot wei surface as residual/dust and are swept.
31
+ */
32
+
33
+ /** Worst-case same-block readback drift for indices below ~2e27. */
34
+ export const AAVE_SAME_BLOCK_SKEW_WEI = 1n;
35
+
36
+ export const padForAaveDebtRounding = (amount: `${bigint}`): `${bigint}` =>
37
+ (BigInt(amount) + AAVE_SAME_BLOCK_SKEW_WEI).toString() as `${bigint}`;
38
+
39
+ export const trimForAaveWithdrawalRounding = (
40
+ amount: `${bigint}`,
41
+ ): `${bigint}` =>
42
+ (BigInt(amount) - AAVE_SAME_BLOCK_SKEW_WEI).toString() as `${bigint}`;
@@ -61,7 +61,6 @@ export const buildAaveSetEMode = ({
61
61
  const request = sdk.request(flow, {
62
62
  signer: owner,
63
63
  inputs: {},
64
- sweepTo: builder.context.sender,
65
64
  });
66
65
 
67
66
  return { flow, request };
@@ -6,4 +6,4 @@ export const API_KEY: string | undefined = process.env['LIFI_API_KEY'];
6
6
 
7
7
  export const OWNER = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
8
8
  export const RECIPIENT = '0x1234567890abcdef1234567890abcdef12345678';
9
- export const PROXY = '0x2222222222222222222222222222222222222222';
9
+ export const PROXY = '0x0921afa7ab4895814f6130fd85aea549c345a109';
@@ -0,0 +1,75 @@
1
+ import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
2
+
3
+ import { createComposeSdk } from '../index.js';
4
+ import type { Address } from '../types.js';
5
+
6
+ import { BASE_URL } from './config.js';
7
+
8
+ // Example ERC-4626 vault address (Steakhouse USDC on Ethereum mainnet)
9
+ const VAULT = '0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB';
10
+
11
+ export interface EmitCustomEventInput {
12
+ readonly owner: Address;
13
+ }
14
+
15
+ /**
16
+ * Emit a custom on-chain event from a compose flow.
17
+ *
18
+ * Demonstrates:
19
+ * - core.emitEvent2 — surfacing an arbitrary `Rebalance(address,uint256)` event
20
+ * - Binding mixed-type handles (an `address` context ref and a `uint256` read)
21
+ * directly to the op's `bytes32` value ports — no `raw.ref` cast, because
22
+ * bytes32 ports are the raw 32-byte word type and accept any static-32-byte
23
+ * handle.
24
+ *
25
+ * The emitted log is a fixed VM event whose first data word is
26
+ * keccak256("Rebalance(address,uint256)") (a discriminator), followed by the
27
+ * execution address and the vault total — an indexer reads the discriminator
28
+ * to recognise the event and decodes the two values.
29
+ */
30
+ export const buildEmitCustomEvent = ({
31
+ owner,
32
+ }: EmitCustomEventInput): {
33
+ flow: Flow;
34
+ request: ComposeCompileRequest;
35
+ } => {
36
+ const sdk = createComposeSdk({ baseUrl: BASE_URL });
37
+
38
+ const builder = sdk.flow(1, {
39
+ name: 'emit-custom-event',
40
+ inputs: {},
41
+ });
42
+
43
+ // Read the vault's total assets at execution time — a fresh uint256 handle.
44
+ const vaultTotal = builder.core.staticCall('vault-total', {
45
+ bind: {},
46
+ config: {
47
+ target: VAULT,
48
+ functionSignature: 'function totalAssets() view returns (uint256)',
49
+ },
50
+ });
51
+
52
+ // Emit Rebalance(address,uint256): the executing address plus the vault total.
53
+ // value0 (address) and value1 (uint256) bind directly to the bytes32 ports.
54
+ builder.core.emitEvent2('log-rebalance', {
55
+ bind: {
56
+ value0: builder.context.executionAddress,
57
+ value1: vaultTotal.result,
58
+ },
59
+ config: {
60
+ signature: 'Rebalance(address,uint256)',
61
+ },
62
+ });
63
+
64
+ const flow = builder.build();
65
+
66
+ // No `sweepTo`: this flow only reads and emits a log — it moves no funds, so
67
+ // there are no proxy-held terminal resources to sweep. Specifying `sweepTo`
68
+ // here would fail compilation with "no ... terminal resources ... to sweep".
69
+ const request = sdk.request(flow, {
70
+ signer: owner,
71
+ inputs: {},
72
+ });
73
+
74
+ return { flow, request };
75
+ };
@@ -0,0 +1,96 @@
1
+ import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
2
+
3
+ import { createComposeSdk, materialisers, resources } from '../index.js';
4
+ import type { Address } from '../types.js';
5
+
6
+ import { BASE_URL } from './config.js';
7
+
8
+ const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
9
+ const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
10
+
11
+ export interface InvariantChecksInput {
12
+ readonly owner: Address;
13
+ readonly spender: Address;
14
+ readonly amount: `${bigint}`;
15
+ readonly minAmountOut: `${bigint}`;
16
+ }
17
+
18
+ /**
19
+ * Swap WETH → USDC via LI.FI, then layer two of the newly-exposed invariant
20
+ * ops on the result.
21
+ *
22
+ * Demonstrates:
23
+ * - `invariant.gte` asserting the swap output is at least a constant threshold
24
+ * (`amountOut >= minAmountOut`) — the per-comparison shorthand for
25
+ * `invariant.numeric` with `{ op: 'gte' }`, so the comparison lives in the op
26
+ * name and the config is just `{ threshold }`.
27
+ * - `invariant.allowanceAtLeast` asserting the execution proxy still permits
28
+ * `spender` to move at least the swapped USDC on its behalf.
29
+ * - Binding a swap's `amountOut` resource handle into both a `uint256` numeric
30
+ * slot and a `resource` allowance slot.
31
+ *
32
+ * Both ops compile to on-chain `AssertRawInvariant` instructions; the run
33
+ * reverts if either assertion is violated.
34
+ */
35
+ export const buildInvariantChecks = ({
36
+ owner,
37
+ spender,
38
+ amount,
39
+ minAmountOut,
40
+ }: InvariantChecksInput): {
41
+ flow: Flow;
42
+ request: ComposeCompileRequest;
43
+ } => {
44
+ const sdk = createComposeSdk({ baseUrl: BASE_URL });
45
+
46
+ // One WETH resource input plus a scalar spender address.
47
+ const builder = sdk.flow(1, {
48
+ name: 'invariant-checks',
49
+ inputs: {
50
+ amountIn: resources.erc20(WETH, 1),
51
+ spender: 'address',
52
+ },
53
+ });
54
+
55
+ // Swap WETH → USDC via LI.FI.
56
+ const swapOutputs = builder.lifi.swap('swap', {
57
+ bind: { amountIn: builder.inputs.amountIn },
58
+ config: {
59
+ resourceOut: resources.erc20(USDC, 1),
60
+ slippage: 0.03,
61
+ },
62
+ });
63
+
64
+ // Assert the swap produced at least `minAmountOut` USDC. The threshold is a
65
+ // configured constant, so this is a numeric (not handle-vs-handle) compare;
66
+ // `invariant.gte` is the shorthand that bakes the comparison into the op id.
67
+ builder.invariant.gte('min-out', {
68
+ bind: { value: swapOutputs.amountOut },
69
+ config: { threshold: minAmountOut },
70
+ });
71
+
72
+ // Assert the execution proxy still grants `spender` an allowance of at least
73
+ // the swapped USDC amount.
74
+ builder.invariant.allowanceAtLeast('check-allowance', {
75
+ bind: {
76
+ minimumAmount: swapOutputs.amountOut,
77
+ owner: builder.context.executionAddress,
78
+ spender: builder.inputs.spender,
79
+ },
80
+ });
81
+
82
+ const flow = builder.build();
83
+
84
+ // directDeposit marks amountIn as a fixed amount of WETH pre-deposited into
85
+ // the VM; the spender address is supplied as a plain scalar input.
86
+ const request = sdk.request(flow, {
87
+ signer: owner,
88
+ inputs: {
89
+ amountIn: materialisers.directDeposit({ amount }),
90
+ spender,
91
+ },
92
+ sweepTo: builder.context.sender,
93
+ });
94
+
95
+ return { flow, request };
96
+ };
@@ -10,14 +10,13 @@
10
10
  * Available examples:
11
11
  * aave-repay — repay an Aave v3 WETH debt by supplying WETH
12
12
  * aave-repay-atoken — repay an Aave v3 USDC debt by burning proxy aTokens
13
- * aave-claim — claim AAVE rewards and forward to a recipient
13
+ * aave-emode — switch the proxy's Aave v3 eMode category
14
14
  * approve-deposit — approve vault allowance then deposit USDC
15
15
  * consolidate — consolidate ERC-20 balances to USDC
16
16
  * consolidate-eth — consolidate ERC-20 balances to native ETH
17
17
  * deposit-proxy — deposit tokens already on the proxy into Aave
18
18
  * dust-sweep — split USDC 80/20 and sweep leftover dust
19
19
  * swap — swap WETH to USDC via LI.FI
20
- * swap-fee — swap WETH to USDC with a 50 bps integrator fee
21
20
  * split-zap — split USDC 60/40 and zap into Aave + Morpho vaults
22
21
  * split-arithmetic — split USDC 70/30 with arithmetic assertions
23
22
  * zap — zap USDC into Aave lending position
@@ -29,7 +28,6 @@
29
28
  * raw-call — query a contract with raw calldata + arithmetic
30
29
  * read-state — read on-chain state via peek, staticCall, balanceOf
31
30
  * redeem — redeem ERC-4626 vault shares via core.call
32
- * claim — claim rewards from a contract (resource-free call)
33
31
  * wrap-eth — wrap native ETH into WETH via ValueCall
34
32
  * transfer — transfer full token balance to a recipient
35
33
  * partial-transfer — transfer a specific amount, keeping the remainder
@@ -39,15 +37,11 @@ import type { ComposeCompileRequest } from '@lifi/compose-spec';
39
37
 
40
38
  import { createComposeSdk } from '../sdk.js';
41
39
 
42
- import { buildAaveClaimRewards } from './aaveClaimRewards.js';
43
40
  import { buildAaveRepay } from './aaveRepay.js';
44
41
  import { buildAaveRepayWithATokens } from './aaveRepayWithATokens.js';
42
+ import { buildAaveSetEMode } from './aaveSetEMode.js';
45
43
  import { buildApproveAndDeposit } from './approveAndDeposit.js';
46
- import {
47
- buildClaimRewards,
48
- buildRedeemFromVault,
49
- buildWrapEth,
50
- } from './callContract.js';
44
+ import { buildRedeemFromVault, buildWrapEth } from './callContract.js';
51
45
  import { API_KEY, BASE_URL, OWNER, PROXY, RECIPIENT } from './config.js';
52
46
  import { buildConsolidateToEth } from './consolidateToEth.js';
53
47
  import { buildConsolidateStablesToUsdc } from './consolidateToUsdc.js';
@@ -63,22 +57,25 @@ import { buildSwapAndZapExample } from './swapAndZap.js';
63
57
  import { buildSwapToRecipient } from './swapToRecipient.js';
64
58
  import { buildSwapWithAllowRevertExample } from './swapWithAllowRevert.js';
65
59
  import { buildSwapWithBalanceCheck } from './swapWithBalanceCheck.js';
66
- import { buildSwapWithFeeExample } from './swapWithFee.js';
67
60
  import { buildSwapWithOutputValidation } from './swapWithOutputValidation.js';
68
61
  import { buildPartialTransfer, buildTransferTokens } from './transferTokens.js';
69
62
  import { buildUntypedOpWithTypedRef } from './untypedOpWithTypedRef.js';
70
63
 
71
64
  const EXAMPLES: Record<string, () => ComposeCompileRequest> = {
72
65
  'aave-repay': () =>
73
- buildAaveRepay({ owner: OWNER, amount: '500000000000000000' }).request,
66
+ buildAaveRepay({
67
+ owner: OWNER,
68
+ collateralAmount: '1000000000',
69
+ borrowAmount: '100000000000000000',
70
+ repayAmount: '101000000000000000',
71
+ }).request,
74
72
  'aave-repay-atoken': () =>
75
73
  buildAaveRepayWithATokens({
76
74
  owner: OWNER,
77
- proxyAddress: PROXY,
78
- expectedATokenBalance: '1000000000',
75
+ collateralAmount: '1000000000',
76
+ borrowAmount: '500000000',
79
77
  }).request,
80
- 'aave-claim': () =>
81
- buildAaveClaimRewards({ owner: OWNER, recipient: RECIPIENT }).request,
78
+ 'aave-emode': () => buildAaveSetEMode({ owner: OWNER }).request,
82
79
  'approve-deposit': () =>
83
80
  buildApproveAndDeposit({ owner: OWNER, amount: '1000000000' }).request,
84
81
  consolidate: () =>
@@ -105,7 +102,6 @@ const EXAMPLES: Record<string, () => ComposeCompileRequest> = {
105
102
  }).request,
106
103
  'dust-sweep': () => buildDustSweepExample().request,
107
104
  swap: () => buildLifiSwapExample().request,
108
- 'swap-fee': () => buildSwapWithFeeExample().request,
109
105
  'split-zap': () => buildSplitAndZapExample().request,
110
106
  'split-arithmetic': () =>
111
107
  buildSplitWithArithmetic({ owner: OWNER, amount: '1000000000' }).request,
@@ -133,7 +129,6 @@ const EXAMPLES: Record<string, () => ComposeCompileRequest> = {
133
129
  redeem: () =>
134
130
  buildRedeemFromVault({ owner: OWNER, amount: '1000000000000000000' })
135
131
  .request,
136
- claim: () => buildClaimRewards({ owner: OWNER }).request,
137
132
  'wrap-eth': () =>
138
133
  buildWrapEth({ owner: OWNER, amount: '1000000000000000000' }).request,
139
134
  transfer: () =>
@@ -190,7 +185,14 @@ const run = async () => {
190
185
  const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
191
186
 
192
187
  console.log('--- Request ---');
193
- console.log(JSON.stringify(request, null, 2));
188
+ console.log(
189
+ JSON.stringify(
190
+ request,
191
+ (_key, value: unknown) =>
192
+ typeof value === 'bigint' ? value.toString() : value,
193
+ 2,
194
+ ),
195
+ );
194
196
 
195
197
  console.log(`\n--- Compiling against ${BASE_URL} ---`);
196
198
  const result = await sdk.client.compile(request);