@lifi/composer-sdk 0.0.1-alpha → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -1
- 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/authoring/signatureArgs.cjs +2 -2
- package/dist/authoring/signatureArgs.cjs.map +1 -1
- package/dist/authoring/signatureArgs.js +2 -2
- package/dist/authoring/signatureArgs.js.map +1 -1
- package/dist/client.cjs +2 -2
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/dist/generated/config.generated.cjs.map +1 -1
- package/dist/generated/config.generated.d.cts +327 -16
- package/dist/generated/config.generated.d.ts +327 -16
- package/dist/generated/guards.generated.cjs +6 -0
- package/dist/generated/guards.generated.cjs.map +1 -1
- package/dist/generated/guards.generated.d.cts +1 -1
- package/dist/generated/guards.generated.d.ts +1 -1
- package/dist/generated/guards.generated.js +5 -0
- package/dist/generated/guards.generated.js.map +1 -1
- package/dist/generated/materialisers.generated.cjs.map +1 -1
- package/dist/generated/materialisers.generated.d.cts +1 -1
- package/dist/generated/materialisers.generated.d.ts +1 -1
- package/dist/generated/materialisers.generated.js.map +1 -1
- package/dist/generated/operations.generated.cjs +103 -14
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +255 -10
- package/dist/generated/operations.generated.d.ts +255 -10
- package/dist/generated/operations.generated.js +103 -14
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/guards.generated-BPLFwe7y.d.ts +19 -0
- package/dist/guards.generated-M3VAIevI.d.cts +19 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/materialisers.generated-BMkBQgWZ.d.ts +24 -0
- package/dist/materialisers.generated-BWq5oePO.d.cts +24 -0
- package/dist/run/inputs.cjs +8 -5
- 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 +8 -5
- package/dist/run/inputs.js.map +1 -1
- package/dist/sdk.cjs +1 -0
- package/dist/sdk.cjs.map +1 -1
- package/dist/sdk.js +1 -0
- package/dist/sdk.js.map +1 -1
- package/package.json +16 -7
- package/src/authoring/handles.ts +19 -10
- package/src/authoring/signatureArgs.ts +2 -2
- package/src/client.ts +1 -1
- package/src/examples/aaveClaimRewards.ts +80 -0
- package/src/examples/aaveRepay.ts +69 -0
- package/src/examples/aaveRepayWithATokens.ts +81 -0
- package/src/examples/aaveSetEMode.ts +68 -0
- package/src/examples/emitCustomEvent.ts +75 -0
- package/src/examples/invariantChecks.ts +95 -0
- package/src/examples/run.ts +41 -4
- package/src/examples/swapToRecipient.ts +0 -19
- package/src/examples/swapWithFee.ts +59 -0
- package/src/generated/config.generated.ts +388 -16
- package/src/generated/guards.generated.ts +12 -1
- package/src/generated/materialisers.generated.ts +12 -12
- package/src/generated/operations.generated.ts +438 -9
- package/src/run/inputs.ts +16 -5
- package/src/sdk.ts +1 -0
- package/dist/guards.generated-DKKDKREA.d.ts +0 -14
- package/dist/guards.generated-hrQ1KPNv.d.cts +0 -14
- package/dist/materialisers.generated-BsA1uDW8.d.ts +0 -24
- package/dist/materialisers.generated-nMxTAaVi.d.cts +0 -24
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifi/composer-sdk",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Public Composer SDK for building and submitting flows",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/lifinance/Yggdrasil.git",
|
|
9
|
+
"directory": "ts/packages/composer-sdk"
|
|
10
|
+
},
|
|
6
11
|
"type": "module",
|
|
7
12
|
"sideEffects": false,
|
|
8
13
|
"main": "./dist/index.cjs",
|
|
@@ -30,20 +35,23 @@
|
|
|
30
35
|
"build": "tsc --noEmit && tsup",
|
|
31
36
|
"clean": "rm -rf dist out",
|
|
32
37
|
"pack:local": "yarn build && mkdir -p out && yarn pack --out 'out/%s-%v.tgz'",
|
|
33
|
-
"generate": "tsx scripts/generate-sdk.ts",
|
|
38
|
+
"generate": "tsx scripts/generate-sdk/index.ts",
|
|
39
|
+
"generate:staged": "tsx scripts/generate-sdk/index.ts --staged",
|
|
34
40
|
"test": "vitest run --config vitest.config.ts",
|
|
35
41
|
"check:types": "tsc --noEmit",
|
|
36
42
|
"check:sdk-drift": "vitest run --config vitest.drift.config.ts",
|
|
37
43
|
"fix": "run-s fix:lint fix:prettier",
|
|
38
44
|
"fix:lint": "eslint src --fix",
|
|
39
|
-
"fix:prettier": "prettier \"src/**/*.ts\" --write",
|
|
45
|
+
"fix:prettier": "prettier \"src/**/*.ts\" \"scripts/**/*.ts\" --write",
|
|
40
46
|
"lint": "eslint src",
|
|
41
47
|
"test:lint": "eslint src",
|
|
42
|
-
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
|
|
43
|
-
"
|
|
48
|
+
"test:prettier": "prettier \"src/**/*.ts\" \"scripts/**/*.ts\" --list-different",
|
|
49
|
+
"verify:staged": "bash scripts/verify-staged.sh",
|
|
50
|
+
"example": "tsx src/examples/run.ts",
|
|
51
|
+
"example:staged": "tsx src/examples/run.ts --staged"
|
|
44
52
|
},
|
|
45
53
|
"peerDependencies": {
|
|
46
|
-
"@lifi/compose-spec": "0.0
|
|
54
|
+
"@lifi/compose-spec": "0.1.0"
|
|
47
55
|
},
|
|
48
56
|
"dependencies": {
|
|
49
57
|
"abitype": "^1.2.3",
|
|
@@ -76,6 +84,7 @@
|
|
|
76
84
|
"!src/**/*.test.ts",
|
|
77
85
|
"!src/**/*.mock.ts",
|
|
78
86
|
"!src/**/*.typecheck.ts",
|
|
79
|
-
"!dist/**/*.typecheck.*"
|
|
87
|
+
"!dist/**/*.typecheck.*",
|
|
88
|
+
"!src/examples/staged/**"
|
|
80
89
|
]
|
|
81
90
|
}
|
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>>;
|
|
@@ -187,7 +187,7 @@ const resolvePositionalArgs = (
|
|
|
187
187
|
|
|
188
188
|
return paramNames.map((name) => {
|
|
189
189
|
const bindable = bind[name];
|
|
190
|
-
if (bindable
|
|
190
|
+
if (bindable === undefined) {
|
|
191
191
|
throw new Error(
|
|
192
192
|
`${label}: bind value for parameter "${name}" is undefined`,
|
|
193
193
|
);
|
|
@@ -211,7 +211,7 @@ export const buildCallWireFormat = (
|
|
|
211
211
|
args.bind,
|
|
212
212
|
args.config,
|
|
213
213
|
);
|
|
214
|
-
const hasResource = args.resource
|
|
214
|
+
const hasResource = args.resource !== undefined;
|
|
215
215
|
|
|
216
216
|
return {
|
|
217
217
|
op: hasResource ? 'core.call' : 'core.invoke',
|
package/src/client.ts
CHANGED
|
@@ -201,7 +201,7 @@ export const createComposeClient = (
|
|
|
201
201
|
options?: GetZapPacksOptions,
|
|
202
202
|
): Promise<readonly ZapPackOverview[]> => {
|
|
203
203
|
const params = new URLSearchParams();
|
|
204
|
-
if (options?.protocols
|
|
204
|
+
if (options?.protocols !== undefined) {
|
|
205
205
|
// Backend expects a single comma-separated value, not repeated keys.
|
|
206
206
|
const raw = options.protocols;
|
|
207
207
|
const list = typeof raw === 'string' ? raw : raw.join(',');
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
// Aave v3 incentives contracts on Ethereum mainnet.
|
|
9
|
+
const REWARDS_CONTROLLER = '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb';
|
|
10
|
+
const AAVE = '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9';
|
|
11
|
+
// Receipt-token positions the proxy holds and that accrue AAVE rewards.
|
|
12
|
+
const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c';
|
|
13
|
+
const VDEBT_USDC = '0x72E95b8931767C79bA4EeE721354d6E99a61D004';
|
|
14
|
+
|
|
15
|
+
export interface AaveClaimRewardsInput {
|
|
16
|
+
readonly owner: Address;
|
|
17
|
+
readonly recipient: Address;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Claim accrued AAVE rewards from a set of Aave v3 receipt-token positions,
|
|
22
|
+
* then forward the claimed amount to a recipient.
|
|
23
|
+
*
|
|
24
|
+
* The op surfaces the claimed AAVE as a tracked resource (`claimed`) so
|
|
25
|
+
* downstream nodes can spend or forward it. With no `amount` configured,
|
|
26
|
+
* Aave claims the full accrued balance (MaxUint256 sentinel).
|
|
27
|
+
*
|
|
28
|
+
* Demonstrates:
|
|
29
|
+
* - `aave.claimRewards` against multiple aToken/vDebt positions.
|
|
30
|
+
* - Routing the `claimed` resource output into `core.transfer`.
|
|
31
|
+
* - The `claimedAmount` handle for assertions or downstream arithmetic.
|
|
32
|
+
*/
|
|
33
|
+
export const buildAaveClaimRewards = ({
|
|
34
|
+
owner,
|
|
35
|
+
recipient,
|
|
36
|
+
}: AaveClaimRewardsInput): {
|
|
37
|
+
flow: Flow;
|
|
38
|
+
request: ComposeCompileRequest;
|
|
39
|
+
} => {
|
|
40
|
+
const sdk = createComposeSdk({ baseUrl: BASE_URL });
|
|
41
|
+
|
|
42
|
+
const builder = sdk.flow(1, {
|
|
43
|
+
name: 'aave-claim-rewards',
|
|
44
|
+
inputs: {
|
|
45
|
+
recipient: 'address',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const claimOut = builder.aave.claimRewards('claim', {
|
|
50
|
+
bind: {},
|
|
51
|
+
config: {
|
|
52
|
+
rewardsController: REWARDS_CONTROLLER,
|
|
53
|
+
assets: [A_ETH_USDC, VDEBT_USDC],
|
|
54
|
+
reward: AAVE,
|
|
55
|
+
// amount omitted → claim full accrued balance.
|
|
56
|
+
// to omitted → rewards land on the proxy (executionAddress).
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Forward the claimed AAVE to the requested recipient.
|
|
61
|
+
builder.core.transfer('forward-rewards', {
|
|
62
|
+
bind: {
|
|
63
|
+
amount: claimOut.claimed,
|
|
64
|
+
recipient: builder.inputs.recipient,
|
|
65
|
+
},
|
|
66
|
+
config: {},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const flow = builder.build();
|
|
70
|
+
|
|
71
|
+
const request = sdk.request(flow, {
|
|
72
|
+
signer: owner,
|
|
73
|
+
inputs: {
|
|
74
|
+
recipient,
|
|
75
|
+
},
|
|
76
|
+
sweepTo: builder.context.sender,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return { flow, request };
|
|
80
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
// Aave v3 contracts on Ethereum mainnet.
|
|
9
|
+
const AAVE_V3_POOL = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2';
|
|
10
|
+
const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
|
|
11
|
+
|
|
12
|
+
export interface AaveRepayInput {
|
|
13
|
+
readonly owner: Address;
|
|
14
|
+
readonly amount: `${bigint}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
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.
|
|
23
|
+
*
|
|
24
|
+
* 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.
|
|
29
|
+
*/
|
|
30
|
+
export const buildAaveRepay = ({
|
|
31
|
+
owner,
|
|
32
|
+
amount,
|
|
33
|
+
}: AaveRepayInput): {
|
|
34
|
+
flow: Flow;
|
|
35
|
+
request: ComposeCompileRequest;
|
|
36
|
+
} => {
|
|
37
|
+
const sdk = createComposeSdk({ baseUrl: BASE_URL });
|
|
38
|
+
|
|
39
|
+
const builder = sdk.flow(1, {
|
|
40
|
+
name: 'aave-repay-weth',
|
|
41
|
+
inputs: {
|
|
42
|
+
assetIn: resources.erc20(WETH, 1),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
builder.aave.repay('repay', {
|
|
47
|
+
bind: {
|
|
48
|
+
assetIn: builder.inputs.assetIn,
|
|
49
|
+
onBehalfOf: builder.context.executionAddress,
|
|
50
|
+
},
|
|
51
|
+
config: {
|
|
52
|
+
pool: AAVE_V3_POOL,
|
|
53
|
+
mode: 'exact',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const flow = builder.build();
|
|
58
|
+
|
|
59
|
+
const request = sdk.request(flow, {
|
|
60
|
+
signer: owner,
|
|
61
|
+
inputs: {
|
|
62
|
+
assetIn: materialisers.directDeposit({ amount }),
|
|
63
|
+
},
|
|
64
|
+
// Any unspent WETH (residual) is swept back to the signer.
|
|
65
|
+
sweepTo: builder.context.sender,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return { flow, request };
|
|
69
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ComposeCompileRequest, Flow } from '@lifi/compose-spec';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createComposeSdk,
|
|
5
|
+
materialisers,
|
|
6
|
+
preconditions,
|
|
7
|
+
resources,
|
|
8
|
+
} from '../index.js';
|
|
9
|
+
import type { Address } from '../types.js';
|
|
10
|
+
|
|
11
|
+
import { BASE_URL } from './config.js';
|
|
12
|
+
|
|
13
|
+
// Aave v3 contracts on Ethereum mainnet.
|
|
14
|
+
const AAVE_V3_POOL = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2';
|
|
15
|
+
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
|
|
16
|
+
const A_ETH_USDC = '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c';
|
|
17
|
+
|
|
18
|
+
export interface AaveRepayWithATokensInput {
|
|
19
|
+
readonly owner: Address;
|
|
20
|
+
readonly proxyAddress: Address;
|
|
21
|
+
readonly expectedATokenBalance: `${bigint}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
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.
|
|
31
|
+
*
|
|
32
|
+
* 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).
|
|
37
|
+
*/
|
|
38
|
+
export const buildAaveRepayWithATokens = ({
|
|
39
|
+
owner,
|
|
40
|
+
proxyAddress,
|
|
41
|
+
expectedATokenBalance,
|
|
42
|
+
}: AaveRepayWithATokensInput): {
|
|
43
|
+
flow: Flow;
|
|
44
|
+
request: ComposeCompileRequest;
|
|
45
|
+
} => {
|
|
46
|
+
const sdk = createComposeSdk({ baseUrl: BASE_URL });
|
|
47
|
+
|
|
48
|
+
const builder = sdk.flow(1, {
|
|
49
|
+
name: 'aave-repay-with-atokens',
|
|
50
|
+
inputs: {
|
|
51
|
+
aTokenIn: resources.erc20(A_ETH_USDC, 1),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
builder.aave.repayWithATokens('repay-atokens', {
|
|
56
|
+
bind: { aTokenIn: builder.inputs.aTokenIn },
|
|
57
|
+
config: {
|
|
58
|
+
pool: AAVE_V3_POOL,
|
|
59
|
+
asset: USDC,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const flow = builder.build();
|
|
64
|
+
|
|
65
|
+
const request = sdk.request(flow, {
|
|
66
|
+
signer: owner,
|
|
67
|
+
inputs: {
|
|
68
|
+
aTokenIn: materialisers.balanceOf({ owner: proxyAddress }),
|
|
69
|
+
},
|
|
70
|
+
preconditions: [
|
|
71
|
+
preconditions.erc20Balance({
|
|
72
|
+
wallet: proxyAddress,
|
|
73
|
+
token: A_ETH_USDC,
|
|
74
|
+
balance: expectedATokenBalance,
|
|
75
|
+
}),
|
|
76
|
+
],
|
|
77
|
+
sweepTo: builder.context.sender,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return { flow, request };
|
|
81
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
// Aave v3 contracts on Ethereum mainnet.
|
|
9
|
+
const AAVE_V3_POOL = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2';
|
|
10
|
+
|
|
11
|
+
// Mainnet ETH-correlated eMode category. Categories are per-pool; check
|
|
12
|
+
// `Pool.getEModeCategoryData(id)` on the target chain before reusing this id.
|
|
13
|
+
const ETH_CORRELATED_CATEGORY = 1;
|
|
14
|
+
|
|
15
|
+
export interface AaveSetEModeInput {
|
|
16
|
+
readonly owner: Address;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Switch the executionAddress's Aave v3 eMode category.
|
|
21
|
+
*
|
|
22
|
+
* eMode is a per-user setting on the pool that allows higher LTV when both
|
|
23
|
+
* collateral and debt are correlated assets (e.g. ETH-LSTs, stablecoins).
|
|
24
|
+
* The setting persists across transactions until changed again.
|
|
25
|
+
*
|
|
26
|
+
* Demonstrates:
|
|
27
|
+
* - `aave.setEMode` as a side-effecting op with no resource inputs or outputs.
|
|
28
|
+
* - The op's category is validated at request-validation time against the
|
|
29
|
+
* protocol's uint8 range. Per-pool category existence (Ethereum has 40+
|
|
30
|
+
* categories, Base has 15+, Arbitrum 10+; new ones are added by governance)
|
|
31
|
+
* is verified at simulation time, not in the schema.
|
|
32
|
+
*
|
|
33
|
+
* Real flows typically combine this op with `aave.supply` + `aave.borrow` of
|
|
34
|
+
* correlated assets. The reset call (`category: 0`) is intentionally omitted —
|
|
35
|
+
* leaving eMode on persists the leveraged position past the tx, and resetting
|
|
36
|
+
* to 0 after a high-LTV borrow would revert with a sub-1 health factor.
|
|
37
|
+
*/
|
|
38
|
+
export const buildAaveSetEMode = ({
|
|
39
|
+
owner,
|
|
40
|
+
}: AaveSetEModeInput): {
|
|
41
|
+
flow: Flow;
|
|
42
|
+
request: ComposeCompileRequest;
|
|
43
|
+
} => {
|
|
44
|
+
const sdk = createComposeSdk({ baseUrl: BASE_URL });
|
|
45
|
+
|
|
46
|
+
const builder = sdk.flow(1, {
|
|
47
|
+
name: 'aave-set-emode',
|
|
48
|
+
inputs: {},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
builder.aave.setEMode('enter-eth-emode', {
|
|
52
|
+
bind: {},
|
|
53
|
+
config: {
|
|
54
|
+
pool: AAVE_V3_POOL,
|
|
55
|
+
category: ETH_CORRELATED_CATEGORY,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const flow = builder.build();
|
|
60
|
+
|
|
61
|
+
const request = sdk.request(flow, {
|
|
62
|
+
signer: owner,
|
|
63
|
+
inputs: {},
|
|
64
|
+
sweepTo: builder.context.sender,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return { flow, request };
|
|
68
|
+
};
|
|
@@ -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,95 @@
|
|
|
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.numeric` asserting the swap output is at least a constant
|
|
24
|
+
* threshold (`amountOut >= minAmountOut`) — a constant-threshold check that
|
|
25
|
+
* needs no second on-chain value to compare against.
|
|
26
|
+
* - `invariant.allowanceAtLeast` asserting the execution proxy still permits
|
|
27
|
+
* `spender` to move at least the swapped USDC on its behalf.
|
|
28
|
+
* - Binding a swap's `amountOut` resource handle into both a `uint256` numeric
|
|
29
|
+
* slot and a `resource` allowance slot.
|
|
30
|
+
*
|
|
31
|
+
* Both ops compile to on-chain `AssertRawInvariant` instructions; the run
|
|
32
|
+
* reverts if either assertion is violated.
|
|
33
|
+
*/
|
|
34
|
+
export const buildInvariantChecks = ({
|
|
35
|
+
owner,
|
|
36
|
+
spender,
|
|
37
|
+
amount,
|
|
38
|
+
minAmountOut,
|
|
39
|
+
}: InvariantChecksInput): {
|
|
40
|
+
flow: Flow;
|
|
41
|
+
request: ComposeCompileRequest;
|
|
42
|
+
} => {
|
|
43
|
+
const sdk = createComposeSdk({ baseUrl: BASE_URL });
|
|
44
|
+
|
|
45
|
+
// One WETH resource input plus a scalar spender address.
|
|
46
|
+
const builder = sdk.flow(1, {
|
|
47
|
+
name: 'invariant-checks',
|
|
48
|
+
inputs: {
|
|
49
|
+
amountIn: resources.erc20(WETH, 1),
|
|
50
|
+
spender: 'address',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Swap WETH → USDC via LI.FI.
|
|
55
|
+
const swapOutputs = builder.lifi.swap('swap', {
|
|
56
|
+
bind: { amountIn: builder.inputs.amountIn },
|
|
57
|
+
config: {
|
|
58
|
+
resourceOut: resources.erc20(USDC, 1),
|
|
59
|
+
slippage: 0.03,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Assert the swap produced at least `minAmountOut` USDC. The threshold is a
|
|
64
|
+
// configured constant, so this is `invariant.numeric` rather than a
|
|
65
|
+
// handle-vs-handle compare.
|
|
66
|
+
builder.invariant.numeric('min-out', {
|
|
67
|
+
bind: { value: swapOutputs.amountOut },
|
|
68
|
+
config: { op: 'gte', threshold: minAmountOut },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Assert the execution proxy still grants `spender` an allowance of at least
|
|
72
|
+
// the swapped USDC amount.
|
|
73
|
+
builder.invariant.allowanceAtLeast('check-allowance', {
|
|
74
|
+
bind: {
|
|
75
|
+
minimumAmount: swapOutputs.amountOut,
|
|
76
|
+
owner: builder.context.executionAddress,
|
|
77
|
+
spender: builder.inputs.spender,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const flow = builder.build();
|
|
82
|
+
|
|
83
|
+
// directDeposit marks amountIn as a fixed amount of WETH pre-deposited into
|
|
84
|
+
// the VM; the spender address is supplied as a plain scalar input.
|
|
85
|
+
const request = sdk.request(flow, {
|
|
86
|
+
signer: owner,
|
|
87
|
+
inputs: {
|
|
88
|
+
amountIn: materialisers.directDeposit({ amount }),
|
|
89
|
+
spender,
|
|
90
|
+
},
|
|
91
|
+
sweepTo: builder.context.sender,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return { flow, request };
|
|
95
|
+
};
|