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

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.
@@ -0,0 +1,302 @@
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
+ import { BASE_URL } from '../config.js';
6
+
7
+ // Base mainnet token addresses.
8
+ const BASE_WETH = '0x4200000000000000000000000000000000000006';
9
+ const BASE_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
10
+ // Circle EURC on Base (6 decimals).
11
+ const BASE_EURC = '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42';
12
+
13
+ // Token decimals for the Paraswap BUY (raw-address requests require them).
14
+ const EURC_DECIMALS = 6;
15
+ const USDC_DECIMALS = 6;
16
+
17
+ // Aave V3 on Base (pool + aWETH receipt + variable-debt USDC).
18
+ const AAVE_V3_POOL_BASE = '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5';
19
+ const A_BAS_WETH = '0xD4a0e0b9149BCee3C920d2E00b5dE09138fd8bb7';
20
+ const VDEBT_BAS_USDC = '0x59dca05b6c26dbd64b5381374aAaC5CD05644C28';
21
+
22
+ // Morpho Blue WETH/EURC market on Base. Real listed market — borrow APY
23
+ // ~1.02% at the time of writing vs ~5.30% on the WETH/USDC market, which is
24
+ // what makes this migration economically interesting. Market ID:
25
+ // 0xa9b5142fa687a24c275faf731f13b52faa9873252bb4e1cb6077aa1f412edb0b
26
+ // (= keccak256(MarketParams) of the values below).
27
+ const ADAPTIVE_CURVE_IRM_BASE = '0x46415998764C29aB2a25CbeA6254146D50D22687';
28
+ const WETH_EURC_ORACLE = '0xE1bb8E5b4930eC9FeC7f7943FCF6227649F14B37';
29
+ const WETH_EURC_LLTV = '860000000000000000';
30
+
31
+ const MORPHO_WETH_EURC_MARKET = {
32
+ loanToken: BASE_EURC,
33
+ collateralToken: BASE_WETH,
34
+ oracle: WETH_EURC_ORACLE,
35
+ irm: ADAPTIVE_CURVE_IRM_BASE,
36
+ lltv: WETH_EURC_LLTV,
37
+ };
38
+
39
+ // Aave V3 flashloan premium (FLASHLOAN_PREMIUM_TOTAL) = 5 bps of principal.
40
+ // Mirrors the builtins package's `computeDefaultFee('aave-v3', amount)`,
41
+ // inlined so the example stays free of compose-internals imports (the SDK must
42
+ // not cross the manifest boundary into the builtins package). Override if the
43
+ // pool's premium has changed on-chain.
44
+ const AAVE_V3_FEE_BPS = 5n;
45
+ const BPS_DENOMINATOR = 10_000n;
46
+ const computeAaveV3Fee = (principal: bigint): bigint =>
47
+ (principal * AAVE_V3_FEE_BPS) / BPS_DENOMINATOR;
48
+
49
+ // Aave's aToken round-trips through ray math: withdrawing freshly-supplied
50
+ // collateral returns up to 1 wei LESS than supplied. The Balancer V2 collateral
51
+ // flashloan, however, must be repaid to the exact wei of its principal. To keep
52
+ // the flashloan (and the migrated Morpho collateral) at the full
53
+ // `collateralAmount`, we supply a few extra wei to Aave so the withdrawal always
54
+ // covers the repay. The tiny remainder is swept back to the signer.
55
+ const AAVE_WITHDRAW_ROUNDING_BUFFER = 2n;
56
+
57
+ // Source-cap slippage on the EURC → USDC BUY, in basis points. This bounds how
58
+ // much extra EURC Augustus may pull above the quoted source before refunding.
59
+ // `morphoBorrowAmount` must exceed the resulting `maxAmountIn`.
60
+ const DEFAULT_MAX_SLIPPAGE_BPS = 100; // 1%
61
+
62
+ export interface AaveToMorphoDebtMigrationWithSwapExactOutInput {
63
+ readonly owner: Address;
64
+ /** WETH collateral the user posts (in smallest units, 18 decimals). */
65
+ readonly collateralAmount: `${bigint}`;
66
+ /** USDC principal borrowed from Aave (6 decimals). */
67
+ readonly debtAmount: `${bigint}`;
68
+ /**
69
+ * EURC to borrow from Morpho (6 decimals). Sized as a *generous upper bound*
70
+ * — large enough that the exact-output BUY can always produce
71
+ * `debtAmount + aaveV3Fee` USDC even after `maxSlippageBps` of source slippage
72
+ * (i.e. `morphoBorrowAmount > maxAmountIn`). The BUY pulls only what it needs;
73
+ * the unspent EURC is repaid straight back into the freshly-minted Morpho
74
+ * debt, so the *net* migrated debt is the original Aave debt (in EURC terms)
75
+ * plus only the slippage/fee actually consumed — NOT the full upper bound.
76
+ * Note: EUR/USD is not 1:1 — size off a live BUY quote, e.g.
77
+ * morphoBorrowAmount ≈ ceil((debtAmount + aaveV3Fee) * eurcPerUsdc * (1 + slippage))
78
+ */
79
+ readonly morphoBorrowAmount: `${bigint}`;
80
+ /** Optional source-cap slippage on the BUY, in bps (default 100 = 1%). */
81
+ readonly maxSlippageBps?: number;
82
+ /** Optional integrator label for analytics / fee capture. */
83
+ readonly partner?: string;
84
+ /** Optional fee recipient; required to actually receive a partner fee. */
85
+ readonly partnerAddress?: Address;
86
+ /** Optional fixed partner fee in bps (e.g. '50' = 0.5%); needs partnerAddress. */
87
+ readonly partnerFeeBps?: string;
88
+ }
89
+
90
+ /**
91
+ * Atomically migrate an Aave V3 USDC borrow on Base into the Morpho Blue
92
+ * WETH/EURC market — same WETH collateral, different stablecoin debt — using a
93
+ * native **exact-output** (Paraswap/Velora BUY) swap instead of the exact-input
94
+ * `lifi.swap`.
95
+ *
96
+ * Contrast with `aaveToMorphoDebtMigrationWithSwap.ts` (the exact-input
97
+ * variant): there the author must over-size the Morpho borrow so a slippage-
98
+ * adjusted *minimum* output still clears the fixed flashloan need, and the
99
+ * buffer lands on the borrow leg as lasting debt. Here `paraswap.buy` produces
100
+ * *exactly* `debtAmount + aaveV3Fee` USDC, pulls only the EURC it needs, and the
101
+ * genuinely-unspent EURC is refunded to the proxy and surfaced as `unspentIn` —
102
+ * which we route straight into a capped `morphoBlue.repay` to unwind the
103
+ * over-borrow. Net migrated debt = original debt + only the consumed
104
+ * slippage/fee, not a standing buffer.
105
+ *
106
+ * Phase 1 — provision the source position on Aave V3:
107
+ * - User direct-deposits WETH.
108
+ * - `lifi.zap` supplies the WETH to Aave (WETH → aBasWETH routing edge).
109
+ * - `aave.borrow` mints USDC debt; the borrowed USDC is swept back to the
110
+ * signer as the user's loan proceeds.
111
+ *
112
+ * Phase 2 — migrate to Morpho using two concurrent flashloans:
113
+ * - Flashloan #1 (Aave V3, USDC) bridges the Aave debt payoff.
114
+ * - Flashloan #2 (Balancer V2, WETH) bridges the Morpho collateral supply.
115
+ * - `aave.repay(mode: 'max')` clears the Aave USDC debt with flashloan #1.
116
+ * - `lifi.zap` withdraws the freed WETH from Aave (aBasWETH → WETH).
117
+ * - `morphoBlue.supplyCollateral` deposits flashloan #2's WETH into Morpho.
118
+ * - `morphoBlue.borrow` mints a *capped* `morphoBorrowAmount` of EURC.
119
+ * - `paraswap.buy` buys *exactly* `debtAmount + aaveV3Fee` USDC with the
120
+ * borrowed EURC. `amountOut` is the exact figure that settles the USDC
121
+ * flashloan leg; `unspentIn` is the refunded, unspent EURC.
122
+ * - `lifi.flashloanRepay` settles the USDC leg from `buy.amountOut` (no
123
+ * feeBuffer: `amountOut == principal + fee` by construction) and the WETH leg
124
+ * from the Aave withdrawal (Balancer V2 charges no fee).
125
+ * - `morphoBlue.repay(mode: 'max')` repays the unspent EURC (`buy.unspentIn`)
126
+ * straight back into the freshly-minted Morpho debt, unwinding the borrow
127
+ * buffer. Any genuine over-repay surfaces through the repay op's `residual`,
128
+ * which `sweepTo` forwards to the signer.
129
+ *
130
+ * After execution the signer holds a Morpho Blue position with
131
+ * `collateralAmount` WETH collateral and EURC debt equal to the original Aave
132
+ * debt plus only the consumed slippage/fee; the Aave position is closed.
133
+ */
134
+ export const buildAaveToMorphoDebtMigrationWithSwapExactOut = ({
135
+ owner,
136
+ collateralAmount,
137
+ debtAmount,
138
+ morphoBorrowAmount,
139
+ maxSlippageBps = DEFAULT_MAX_SLIPPAGE_BPS,
140
+ partner,
141
+ partnerAddress,
142
+ partnerFeeBps,
143
+ }: AaveToMorphoDebtMigrationWithSwapExactOutInput): {
144
+ flow: Flow;
145
+ request: ComposeCompileRequest;
146
+ } => {
147
+ const sdk = createComposeSdk({ baseUrl: BASE_URL });
148
+
149
+ // The USDC the BUY must produce: the flashloan principal plus the Aave V3
150
+ // flashloan premium. This is exactly what settles the debt flashloan leg.
151
+ const exactUsdcOut = (
152
+ BigInt(debtAmount) + computeAaveV3Fee(BigInt(debtAmount))
153
+ ).toString() as `${bigint}`;
154
+
155
+ const builder = sdk.flow(8453, {
156
+ name: 'aave-to-morpho-debt-migration-with-swap-exact-out',
157
+ inputs: {
158
+ initialCollateral: resources.erc20(BASE_WETH, 8453),
159
+ debtFlashloan: resources.erc20(BASE_USDC, 8453),
160
+ collateralFlashloan: resources.erc20(BASE_WETH, 8453),
161
+ },
162
+ });
163
+
164
+ // --- Phase 1: provision the Aave V3 borrow position ----------------------
165
+
166
+ // WETH → aBasWETH via the aave routing edge family (Aave V3 supply).
167
+ const supplyToAave = builder.lifi.zap('supply-to-aave', {
168
+ bind: { amountIn: builder.inputs.initialCollateral },
169
+ config: { resourceOut: resources.erc20(A_BAS_WETH, 8453) },
170
+ });
171
+
172
+ // Borrow USDC against the freshly supplied collateral. The `borrowed`
173
+ // resource is left unbound; request-level `sweepTo` forwards it to the
174
+ // signer as the user's loan proceeds.
175
+ builder.aave.borrow('borrow-from-aave', {
176
+ bind: {},
177
+ config: {
178
+ pool: AAVE_V3_POOL_BASE,
179
+ asset: BASE_USDC,
180
+ variableDebtToken: VDEBT_BAS_USDC,
181
+ amount: debtAmount,
182
+ },
183
+ });
184
+
185
+ // --- Phase 2: migrate to Morpho with two concurrent flashloans -----------
186
+
187
+ // Settle the Aave USDC debt with flashloan #1. mode: 'max' makes Aave's Pool
188
+ // clamp paybackAmount to the outstanding debt exactly; unspent flashloan
189
+ // principal flows through the op's residual port (swept).
190
+ builder.aave.repay('repay-aave', {
191
+ bind: {
192
+ assetIn: builder.inputs.debtFlashloan,
193
+ onBehalfOf: builder.context.executionAddress,
194
+ },
195
+ config: { pool: AAVE_V3_POOL_BASE, mode: 'max' },
196
+ });
197
+
198
+ // Withdraw the freed WETH from Aave (aBasWETH → WETH) via routing.
199
+ const withdrawFromAave = builder.lifi.zap('withdraw-from-aave', {
200
+ bind: { amountIn: supplyToAave.amountOut },
201
+ config: { resourceOut: resources.erc20(BASE_WETH, 8453) },
202
+ });
203
+
204
+ // Open the Morpho Blue position with flashloan #2's WETH.
205
+ builder.morphoBlue.supplyCollateral('supply-to-morpho', {
206
+ bind: { assetIn: builder.inputs.collateralFlashloan },
207
+ config: { marketParams: MORPHO_WETH_EURC_MARKET, mode: 'exact' },
208
+ });
209
+
210
+ // Borrow a capped (generously over-sized) EURC amount at the destination's
211
+ // cheaper APR. The over-borrow is intentional and is unwound below.
212
+ const morphoBorrow = builder.morphoBlue.borrow('borrow-from-morpho', {
213
+ bind: {},
214
+ config: {
215
+ marketParams: MORPHO_WETH_EURC_MARKET,
216
+ amount: morphoBorrowAmount,
217
+ },
218
+ });
219
+
220
+ // Exact-output BUY: produce EXACTLY `exactUsdcOut` USDC from the borrowed
221
+ // EURC, pulling only the EURC needed and refunding the rest to the proxy
222
+ // (surfaced as `unspentIn`). `amountOut` is the exact figure that settles the
223
+ // USDC flashloan leg.
224
+ const buyUsdc = builder.paraswap.buy('buy-usdc-exact', {
225
+ bind: { amountIn: morphoBorrow.borrowed },
226
+ config: {
227
+ resourceOut: resources.erc20(BASE_USDC, 8453),
228
+ exactAmountOut: exactUsdcOut,
229
+ srcDecimals: EURC_DECIMALS,
230
+ destDecimals: USDC_DECIMALS,
231
+ maxSlippageBps,
232
+ ...(partner !== undefined ? { partner } : {}),
233
+ ...(partnerAddress !== undefined ? { partnerAddress } : {}),
234
+ ...(partnerFeeBps !== undefined ? { partnerFeeBps } : {}),
235
+ },
236
+ });
237
+
238
+ // Settle the USDC flashloan leg with the exact BUY output. No feeBuffer:
239
+ // `amountOut == principal + fee` by construction.
240
+ builder.lifi.flashloanRepay('repay-debt-flashloan', {
241
+ bind: { funds: buyUsdc.amountOut },
242
+ config: { leg: 'debtFlashloan' },
243
+ });
244
+
245
+ // Settle the WETH flashloan leg with the Aave withdrawal. Balancer V2 charges
246
+ // no fee, so the withdrawn principal alone covers the leg.
247
+ builder.lifi.flashloanRepay('repay-collateral-flashloan', {
248
+ bind: { funds: withdrawFromAave.amountOut },
249
+ config: { leg: 'collateralFlashloan' },
250
+ });
251
+
252
+ // Unwind the over-borrow: repay the proxy's FULL residual EURC back into the
253
+ // freshly-minted Morpho debt, so the migrated debt equals exactly the EURC the
254
+ // swap consumed. We read the live EURC balance rather than binding
255
+ // `buy.unspentIn`: unspentIn is only Augustus's refund (cap − used), but the
256
+ // proxy ALSO still holds the headroom we over-borrowed above the swap cap
257
+ // (morphoBorrowAmount − cap), which Augustus never pulled. The live balance
258
+ // captures both, leaving zero idle EURC. mode: 'exact' pulls precisely this
259
+ // balance; mode: 'max' would repay the FULL Morpho debt in shares mode
260
+ // (reading Position.borrowShares) and pull far more EURC than the residual,
261
+ // reverting with "transfer amount exceeds balance".
262
+ const eurcResidual = builder.core.balanceOf('read-eurc-residual', {
263
+ bind: {},
264
+ config: { token: BASE_EURC },
265
+ });
266
+ builder.morphoBlue.repay('repay-morpho-remainder', {
267
+ bind: {
268
+ assetIn: eurcResidual.balance,
269
+ onBehalfOf: builder.context.executionAddress,
270
+ },
271
+ config: { marketParams: MORPHO_WETH_EURC_MARKET, mode: 'exact' },
272
+ });
273
+
274
+ const flow = builder.build();
275
+
276
+ const request = sdk.request(flow, {
277
+ signer: owner,
278
+ inputs: {
279
+ // Supply a few extra wei over the flashloaned `collateralAmount` so the
280
+ // Aave withdrawal (which rounds down by up to 1 wei) still fully covers
281
+ // the exact-principal Balancer collateral-flashloan repayment.
282
+ initialCollateral: materialisers.directDeposit({
283
+ amount: (
284
+ BigInt(collateralAmount) + AAVE_WITHDRAW_ROUNDING_BUFFER
285
+ ).toString() as `${bigint}`,
286
+ }),
287
+ debtFlashloan: materialisers.flashloan({
288
+ providerKind: 'aave-v3',
289
+ amount: debtAmount,
290
+ }),
291
+ collateralFlashloan: materialisers.flashloan({
292
+ providerKind: 'balancer-v2',
293
+ amount: collateralAmount,
294
+ }),
295
+ },
296
+ // Sweep Aave borrow proceeds, aave.repay's residual, and any EURC over-repay
297
+ // residual from the capped Morpho repay back to the signer.
298
+ sweepTo: builder.context.sender,
299
+ });
300
+
301
+ return { flow, request };
302
+ };
@@ -0,0 +1,138 @@
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
+ import { BASE_URL } from '../config.js';
6
+
7
+ const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
8
+ const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
9
+
10
+ export interface FlashloanRepayInput {
11
+ readonly owner: Address;
12
+ /** Principal to borrow (in the borrowed token's smallest units). */
13
+ readonly amount: `${bigint}`;
14
+ /**
15
+ * Provider fee top-up (in the borrowed token's smallest units). Equals
16
+ * `computeDefaultFee(providerKind, amount)` for the chosen provider.
17
+ * For Aave v3 this is 5 bps of the principal.
18
+ */
19
+ readonly fee: `${bigint}`;
20
+ }
21
+
22
+ /**
23
+ * Trivial smoke-test variant: borrow USDC via Aave v3, top up the protocol
24
+ * fee via a `directDeposit`, and repay principal + fee back to the LiFi
25
+ * flashloan adapter.
26
+ *
27
+ * Demonstrates the leg-vs-funds split introduced by the repay redesign:
28
+ * - `config.leg` names the materialised flashloan input ("borrowed")
29
+ * - `bind.funds` provides the principal-side handle (the borrowed handle)
30
+ * - `bind.feeBuffer` provides the fee-side handle (the directDeposit)
31
+ *
32
+ * Both bindings exist purely to satisfy linearity; the op emits a single
33
+ * `SafeTransfer(token, adapter, principal + fee)`.
34
+ */
35
+ export const buildTrivialFlashloanRepay = ({
36
+ owner,
37
+ amount,
38
+ fee,
39
+ }: FlashloanRepayInput): {
40
+ flow: Flow;
41
+ request: ComposeCompileRequest;
42
+ } => {
43
+ const sdk = createComposeSdk({ baseUrl: BASE_URL });
44
+
45
+ const builder = sdk.flow(1, {
46
+ name: 'flashloan-repay-trivial',
47
+ inputs: {
48
+ borrowed: resources.erc20(USDC, 1),
49
+ feeTopUp: resources.erc20(USDC, 1),
50
+ },
51
+ });
52
+
53
+ builder.lifi.flashloanRepay('repay', {
54
+ bind: {
55
+ funds: builder.inputs.borrowed,
56
+ feeBuffer: builder.inputs.feeTopUp,
57
+ },
58
+ config: { leg: 'borrowed' },
59
+ });
60
+
61
+ const flow = builder.build();
62
+
63
+ const request = sdk.request(flow, {
64
+ signer: owner,
65
+ inputs: {
66
+ borrowed: materialisers.flashloan({
67
+ providerKind: 'aave-v3',
68
+ amount,
69
+ }),
70
+ feeTopUp: materialisers.directDeposit({ amount: fee }),
71
+ },
72
+ });
73
+
74
+ return { flow, request };
75
+ };
76
+
77
+ export interface SwapAndRepayInput {
78
+ readonly owner: Address;
79
+ readonly amount: `${bigint}`;
80
+ }
81
+
82
+ /**
83
+ * Realistic variant: borrow USDC, swap to WETH, swap back to USDC, then
84
+ * repay using the swap-back's `amountOut` as the funds handle. No
85
+ * `feeBuffer` is needed because the swap-back's output is sized to cover
86
+ * `principal + fee` by construction.
87
+ */
88
+ export const buildSwapAndRepay = ({
89
+ owner,
90
+ amount,
91
+ }: SwapAndRepayInput): {
92
+ flow: Flow;
93
+ request: ComposeCompileRequest;
94
+ } => {
95
+ const sdk = createComposeSdk({ baseUrl: BASE_URL });
96
+
97
+ const builder = sdk.flow(1, {
98
+ name: 'flashloan-swap-and-repay',
99
+ inputs: {
100
+ borrowed: resources.erc20(USDC, 1),
101
+ },
102
+ });
103
+
104
+ const toWeth = builder.lifi.swap('swap-to-weth', {
105
+ bind: { amountIn: builder.inputs.borrowed },
106
+ config: {
107
+ resourceOut: resources.erc20(WETH, 1),
108
+ slippage: 0.005,
109
+ },
110
+ });
111
+
112
+ const backToUsdc = builder.lifi.swap('swap-back-to-usdc', {
113
+ bind: { amountIn: toWeth.amountOut },
114
+ config: {
115
+ resourceOut: resources.erc20(USDC, 1),
116
+ slippage: 0.005,
117
+ },
118
+ });
119
+
120
+ builder.lifi.flashloanRepay('repay', {
121
+ bind: { funds: backToUsdc.amountOut },
122
+ config: { leg: 'borrowed' },
123
+ });
124
+
125
+ const flow = builder.build();
126
+
127
+ const request = sdk.request(flow, {
128
+ signer: owner,
129
+ inputs: {
130
+ borrowed: materialisers.flashloan({
131
+ providerKind: 'aave-v3',
132
+ amount,
133
+ }),
134
+ },
135
+ });
136
+
137
+ return { flow, request };
138
+ };
@@ -0,0 +1,89 @@
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
+ import { BASE_URL } from '../config.js';
6
+
7
+ // Morpho Blue WETH/USDC market on Base (chainId 8453).
8
+ // The Morpho singleton address (0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb)
9
+ // is the default `morpho` config field and is read from compose-builtins.
10
+ const ADAPTIVE_CURVE_IRM_BASE = '0x46415998764C29aB2a25CbeA6254146D50D22687';
11
+ const BASE_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
12
+ const BASE_WETH = '0x4200000000000000000000000000000000000006';
13
+ const WETH_USDC_ORACLE = '0xFEa2D58cEfCb9fcb597723c6bAE66fFE4193aFE4';
14
+ const WETH_USDC_LLTV = '860000000000000000'; // 0.86e18
15
+
16
+ const WETH_USDC_MARKET = {
17
+ loanToken: BASE_USDC,
18
+ collateralToken: BASE_WETH,
19
+ oracle: WETH_USDC_ORACLE,
20
+ irm: ADAPTIVE_CURVE_IRM_BASE,
21
+ lltv: WETH_USDC_LLTV,
22
+ };
23
+
24
+ export interface MorphoBlueBorrowInput {
25
+ readonly owner: Address;
26
+ readonly collateralAmount: `${bigint}`;
27
+ readonly borrowAmount: `${bigint}`;
28
+ }
29
+
30
+ /**
31
+ * Open a leveraged-borrow position on the Base Morpho Blue WETH/USDC market.
32
+ *
33
+ * - `morphoBlue.supplyCollateral` debits the proxy's WETH and credits
34
+ * `Morpho.position(WETH_USDC_MARKET_ID, proxy).collateral`. There is no
35
+ * receipt token — collateral is non-transferable accounting state.
36
+ * - `morphoBlue.borrow` mints debt shares against that collateral and
37
+ * transfers USDC to the proxy. The borrowed asset is exposed as the
38
+ * `borrowed` Resource port for downstream nodes.
39
+ * - The flow sweeps the borrowed USDC back to the signer's wallet via
40
+ * `sweepTo` on the request.
41
+ */
42
+ export const buildMorphoBlueBorrow = ({
43
+ owner,
44
+ collateralAmount,
45
+ borrowAmount,
46
+ }: MorphoBlueBorrowInput): {
47
+ flow: Flow;
48
+ request: ComposeCompileRequest;
49
+ } => {
50
+ const sdk = createComposeSdk({ baseUrl: BASE_URL });
51
+
52
+ const builder = sdk.flow(8453, {
53
+ name: 'morpho-blue-borrow-weth-usdc',
54
+ inputs: {
55
+ assetIn: resources.erc20(BASE_WETH, 8453),
56
+ },
57
+ });
58
+
59
+ builder.morphoBlue.supplyCollateral('supply-collateral', {
60
+ bind: {
61
+ assetIn: builder.inputs.assetIn,
62
+ },
63
+ config: {
64
+ marketParams: WETH_USDC_MARKET,
65
+ mode: 'exact',
66
+ },
67
+ });
68
+
69
+ builder.morphoBlue.borrow('borrow', {
70
+ bind: {},
71
+ config: {
72
+ marketParams: WETH_USDC_MARKET,
73
+ amount: borrowAmount,
74
+ },
75
+ });
76
+
77
+ const flow = builder.build();
78
+
79
+ const request = sdk.request(flow, {
80
+ signer: owner,
81
+ inputs: {
82
+ assetIn: materialisers.directDeposit({ amount: collateralAmount }),
83
+ },
84
+ // Sweep the borrowed USDC back to the signer.
85
+ sweepTo: builder.context.sender,
86
+ });
87
+
88
+ return { flow, request };
89
+ };
@@ -0,0 +1,91 @@
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
+ import { BASE_URL } from '../config.js';
6
+
7
+ // Morpho Blue WETH/USDC market on Base (chainId 8453).
8
+ const ADAPTIVE_CURVE_IRM_BASE = '0x46415998764C29aB2a25CbeA6254146D50D22687';
9
+ const BASE_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
10
+ const BASE_WETH = '0x4200000000000000000000000000000000000006';
11
+ const WETH_USDC_ORACLE = '0xFEa2D58cEfCb9fcb597723c6bAE66fFE4193aFE4';
12
+ const WETH_USDC_LLTV = '860000000000000000';
13
+
14
+ const WETH_USDC_MARKET = {
15
+ loanToken: BASE_USDC,
16
+ collateralToken: BASE_WETH,
17
+ oracle: WETH_USDC_ORACLE,
18
+ irm: ADAPTIVE_CURVE_IRM_BASE,
19
+ lltv: WETH_USDC_LLTV,
20
+ };
21
+
22
+ export interface MorphoBlueRepayInput {
23
+ readonly owner: Address;
24
+ readonly repayAmount: `${bigint}`;
25
+ readonly collateralAmount: `${bigint}`;
26
+ }
27
+
28
+ /**
29
+ * Close a Morpho Blue WETH/USDC position on Base:
30
+ *
31
+ * - User direct-deposits USDC (slightly over-funded to exercise the residual
32
+ * port).
33
+ * - `morphoBlue.repay` with `mode: 'max'` reads the proxy's current
34
+ * `borrowShares` via a precursor `Morpho.position` static call and passes
35
+ * the share count back into `repay` to retire the debt to the wei. Any
36
+ * unused USDC surfaces on the `residual` Resource port.
37
+ * - `morphoBlue.withdrawCollateral` pulls the freed WETH back to the signer's
38
+ * wallet.
39
+ * - The flow sweeps both the freed WETH and the USDC residual to the signer.
40
+ */
41
+ export const buildMorphoBlueRepay = ({
42
+ owner,
43
+ repayAmount,
44
+ collateralAmount,
45
+ }: MorphoBlueRepayInput): {
46
+ flow: Flow;
47
+ request: ComposeCompileRequest;
48
+ } => {
49
+ const sdk = createComposeSdk({ baseUrl: BASE_URL });
50
+
51
+ const builder = sdk.flow(8453, {
52
+ name: 'morpho-blue-repay-usdc-withdraw-weth',
53
+ inputs: {
54
+ assetIn: resources.erc20(BASE_USDC, 8453),
55
+ },
56
+ });
57
+
58
+ builder.morphoBlue.repay('repay', {
59
+ bind: {
60
+ assetIn: builder.inputs.assetIn,
61
+ onBehalfOf: builder.context.executionAddress,
62
+ },
63
+ config: {
64
+ marketParams: WETH_USDC_MARKET,
65
+ mode: 'max',
66
+ },
67
+ });
68
+
69
+ // The withdrawn WETH lands on the proxy; the request-level `sweepTo`
70
+ // pushes it (and the USDC `residual` from the repay) to the signer.
71
+ builder.morphoBlue.withdrawCollateral('withdraw-collateral', {
72
+ bind: {},
73
+ config: {
74
+ marketParams: WETH_USDC_MARKET,
75
+ amount: collateralAmount,
76
+ },
77
+ });
78
+
79
+ const flow = builder.build();
80
+
81
+ const request = sdk.request(flow, {
82
+ signer: owner,
83
+ inputs: {
84
+ assetIn: materialisers.directDeposit({ amount: repayAmount }),
85
+ },
86
+ // Sweep freed WETH and the USDC residual back to the signer.
87
+ sweepTo: builder.context.sender,
88
+ });
89
+
90
+ return { flow, request };
91
+ };