@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.
- package/dist/authoring/handles.cjs.map +1 -1
- package/dist/authoring/handles.d.cts +13 -6
- package/dist/authoring/handles.d.ts +13 -6
- package/dist/authoring/handles.js.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.js +1 -1
- package/dist/generated/config.generated.cjs.map +1 -1
- package/dist/generated/config.generated.d.cts +39 -1
- package/dist/generated/config.generated.d.ts +39 -1
- package/dist/generated/operations.generated.cjs +55 -1
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +204 -2
- package/dist/generated/operations.generated.d.ts +204 -2
- package/dist/generated/operations.generated.js +55 -1
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +2 -2
- package/src/authoring/handles.ts +19 -10
- package/src/examples/aaveRepay.ts +55 -16
- package/src/examples/aaveRepayWithATokens.ts +41 -30
- package/src/examples/aaveRounding.ts +42 -0
- package/src/examples/aaveSetEMode.ts +0 -1
- package/src/examples/config.ts +1 -1
- package/src/examples/emitCustomEvent.ts +75 -0
- package/src/examples/invariantChecks.ts +96 -0
- package/src/examples/run.ts +20 -18
- package/src/examples/staged/aaveBorrow.ts +38 -14
- package/src/examples/staged/aaveToMorphoDebtMigration.ts +13 -3
- package/src/examples/staged/aaveToMorphoDebtMigrationWithSwap.ts +12 -2
- package/src/examples/staged/aaveToMorphoDebtMigrationWithSwapExactOut.ts +5 -0
- package/src/examples/staged/flashloanRepay.ts +4 -0
- package/src/examples/staged/morphoBlueBorrow.ts +4 -0
- package/src/examples/staged/morphoBlueRepay.ts +59 -18
- package/src/examples/staged/registry.ts +7 -5
- package/src/examples/untypedOpWithTypedRef.ts +26 -20
- package/src/generated/config.generated.ts +50 -0
- 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.
|
|
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.
|
|
54
|
+
"@lifi/compose-spec": "0.2.0-staging.5"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"abitype": "^1.2.3",
|
package/src/authoring/handles.ts
CHANGED
|
@@ -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,
|
|
63
|
-
*
|
|
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
|
|
66
|
-
?
|
|
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
|
-
* - `
|
|
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<
|
|
80
|
-
| InputHandle<
|
|
81
|
-
| TypedRef<
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
* - `
|
|
26
|
-
* -
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
85
|
+
assetIn: builder.inputs.repayIn,
|
|
49
86
|
onBehalfOf: builder.context.executionAddress,
|
|
50
87
|
},
|
|
51
88
|
config: {
|
|
52
89
|
pool: AAVE_V3_POOL,
|
|
53
|
-
mode: '
|
|
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
|
-
|
|
99
|
+
collateralIn: materialisers.directDeposit({ amount: collateralAmount }),
|
|
100
|
+
repayIn: materialisers.directDeposit({ amount: repayAmount }),
|
|
63
101
|
},
|
|
64
|
-
//
|
|
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
|
-
|
|
21
|
-
readonly
|
|
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
|
-
*
|
|
26
|
-
*
|
|
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
|
-
* - `
|
|
34
|
-
* - `
|
|
35
|
-
* - `
|
|
36
|
-
* -
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
84
|
+
collateralIn: materialisers.directDeposit({ amount: collateralAmount }),
|
|
69
85
|
},
|
|
70
|
-
|
|
71
|
-
|
|
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}`;
|
package/src/examples/config.ts
CHANGED
|
@@ -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 = '
|
|
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
|
+
};
|
package/src/examples/run.ts
CHANGED
|
@@ -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-
|
|
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({
|
|
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
|
-
|
|
78
|
-
|
|
75
|
+
collateralAmount: '1000000000',
|
|
76
|
+
borrowAmount: '500000000',
|
|
79
77
|
}).request,
|
|
80
|
-
'aave-
|
|
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(
|
|
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);
|