@lifi/composer-sdk 0.4.0-staging.1 → 0.4.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/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/generated/materialisers.generated.cjs +2 -8
- 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 +1 -6
- package/dist/generated/materialisers.generated.js.map +1 -1
- package/dist/generated/operations.generated.cjs +1 -39
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +2 -111
- package/dist/generated/operations.generated.d.ts +2 -111
- package/dist/generated/operations.generated.js +1 -39
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/{materialisers.generated-BfWJ-g7t.d.ts → materialisers.generated-BMkBQgWZ.d.ts} +3 -8
- package/dist/{materialisers.generated-DaJRwiRe.d.cts → materialisers.generated-BWq5oePO.d.cts} +3 -8
- package/package.json +4 -3
- package/src/generated/materialisers.generated.ts +0 -12
- package/src/generated/operations.generated.ts +0 -214
- package/src/examples/staged/aaveBorrow.ts +0 -128
- package/src/examples/staged/aaveDebtRebalanceExactOut.ts +0 -301
- package/src/examples/staged/aaveHealthFactorExplode.ts +0 -70
- package/src/examples/staged/aaveToMorphoDebtMigration.ts +0 -200
- package/src/examples/staged/aaveToMorphoDebtMigrationWithSwap.ts +0 -231
- package/src/examples/staged/aaveToMorphoDebtMigrationWithSwapExactOut.ts +0 -307
- package/src/examples/staged/flashloanRepay.ts +0 -142
- package/src/examples/staged/manualRelayContinuation.ts +0 -161
- package/src/examples/staged/morphoBlueBorrow.ts +0 -93
- package/src/examples/staged/morphoBlueRepay.ts +0 -132
- package/src/examples/staged/registry.ts +0 -89
|
@@ -1,231 +0,0 @@
|
|
|
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 {
|
|
6
|
-
padForAaveDebtRounding,
|
|
7
|
-
trimForAaveWithdrawalRounding,
|
|
8
|
-
} from '../aaveRounding.js';
|
|
9
|
-
import { API_KEY, BASE_URL } from '../config.js';
|
|
10
|
-
|
|
11
|
-
// Base mainnet token addresses.
|
|
12
|
-
const BASE_WETH = '0x4200000000000000000000000000000000000006';
|
|
13
|
-
const BASE_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
14
|
-
// Circle EURC on Base (6 decimals).
|
|
15
|
-
const BASE_EURC = '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42';
|
|
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
|
-
// Slippage budget on the EURC → USDC swap. The caller must size
|
|
40
|
-
// `morphoBorrowAmount` such that the swap's slippage-adjusted minimum covers
|
|
41
|
-
// `debtAmount + computeDefaultFee('aave-v3', debtAmount)`.
|
|
42
|
-
const SWAP_SLIPPAGE = 0.03;
|
|
43
|
-
|
|
44
|
-
export interface AaveToMorphoDebtMigrationWithSwapInput {
|
|
45
|
-
readonly owner: Address;
|
|
46
|
-
/** WETH collateral the user posts (in smallest units, 18 decimals). */
|
|
47
|
-
readonly collateralAmount: `${bigint}`;
|
|
48
|
-
/** USDC principal borrowed from Aave (6 decimals). */
|
|
49
|
-
readonly debtAmount: `${bigint}`;
|
|
50
|
-
/**
|
|
51
|
-
* EURC to borrow from Morpho (6 decimals). Must be large enough that, after
|
|
52
|
-
* the EURC → USDC swap at `SWAP_SLIPPAGE`, the swap's minimum output covers
|
|
53
|
-
* `debtAmount + computeDefaultFee('aave-v3', debtAmount)`. Caller sizes this
|
|
54
|
-
* off-chain from a live LI.FI quote, e.g.
|
|
55
|
-
* morphoBorrowAmount =
|
|
56
|
-
* ceil((debtAmount + aaveV3Fee) * eurcPerUsdc / (1 - slippage))
|
|
57
|
-
* Note: EUR/USD is not 1:1 — the example fixture assumes ~1.075 USD/EUR.
|
|
58
|
-
*/
|
|
59
|
-
readonly morphoBorrowAmount: `${bigint}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Atomically migrate an Aave V3 USDC borrow on Base into the Morpho Blue
|
|
64
|
-
* WETH/EURC market — same WETH collateral, different stablecoin debt. At the
|
|
65
|
-
* time of writing Morpho's WETH/EURC borrow APY is ~1.02% vs ~5.30% on
|
|
66
|
-
* USDC-denominated WETH-collateral markets, so a long-running borrow position
|
|
67
|
-
* can be made materially cheaper by migrating the debt asset.
|
|
68
|
-
*
|
|
69
|
-
* Topology mirrors `aaveToMorphoDebtMigration`, with one extra step: after
|
|
70
|
-
* Morpho mints EURC debt, the borrow proceeds are swapped to USDC via
|
|
71
|
-
* `lifi.swap`, and the swap's `amountOut` (which carries the slippage-adjusted
|
|
72
|
-
* minimum) settles the USDC flashloan leg.
|
|
73
|
-
*
|
|
74
|
-
* Phase 1 — provision the source position on Aave V3:
|
|
75
|
-
* - User direct-deposits WETH.
|
|
76
|
-
* - `lifi.zap` supplies the WETH to Aave (WETH → aBasWETH routing edge).
|
|
77
|
-
* - `aave.borrow` mints USDC debt; the borrowed USDC is swept back to the
|
|
78
|
-
* signer as the user's loan proceeds.
|
|
79
|
-
*
|
|
80
|
-
* Phase 2 — migrate to Morpho using two concurrent flashloans:
|
|
81
|
-
* - Flashloan #1 (Aave V3, USDC) bridges the Aave debt payoff before the
|
|
82
|
-
* Morpho borrow + swap exists.
|
|
83
|
-
* - Flashloan #2 (Balancer V2, WETH) bridges the Morpho collateral supply
|
|
84
|
-
* before the Aave collateral is freed.
|
|
85
|
-
* - `aave.repay` clears the Aave USDC debt with flashloan #1.
|
|
86
|
-
* - `lifi.zap` withdraws the freed WETH from Aave (aBasWETH → WETH).
|
|
87
|
-
* - `morphoBlue.supplyCollateral` deposits flashloan #2's WETH into Morpho.
|
|
88
|
-
* - `morphoBlue.borrow` mints `morphoBorrowAmount` EURC.
|
|
89
|
-
* - `lifi.swap` converts the borrowed EURC into USDC. `amountOut` is the
|
|
90
|
-
* slippage-adjusted minimum that settles the USDC flashloan leg.
|
|
91
|
-
* - Two `lifi.flashloanRepay` ops settle each leg: the USDC leg from the swap
|
|
92
|
-
* output, the WETH leg from the Aave withdrawal (Balancer V2 charges no
|
|
93
|
-
* fee, so the withdrawn principal alone covers it).
|
|
94
|
-
*
|
|
95
|
-
* After execution the signer holds a Morpho Blue position with
|
|
96
|
-
* `collateralAmount` WETH collateral and `morphoBorrowAmount` EURC debt; the
|
|
97
|
-
* Aave position is closed. Any EURC dust (Morpho residual above what the swap
|
|
98
|
-
* consumed) and USDC residual from `aave.repay`'s `mode: 'max'` clamp flow
|
|
99
|
-
* back to the signer via `sweepTo`.
|
|
100
|
-
*/
|
|
101
|
-
export const buildAaveToMorphoDebtMigrationWithSwap = ({
|
|
102
|
-
owner,
|
|
103
|
-
collateralAmount,
|
|
104
|
-
debtAmount,
|
|
105
|
-
morphoBorrowAmount,
|
|
106
|
-
}: AaveToMorphoDebtMigrationWithSwapInput): {
|
|
107
|
-
flow: Flow;
|
|
108
|
-
request: ComposeCompileRequest;
|
|
109
|
-
} => {
|
|
110
|
-
const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
|
|
111
|
-
|
|
112
|
-
// Both flashloan legs absorb Aave's 1-wei scaled-balance skew: the USDC
|
|
113
|
-
// leg funds a max-mode repay, the WETH leg is covered by a withdrawal.
|
|
114
|
-
const debtFlashloanAmount = padForAaveDebtRounding(debtAmount);
|
|
115
|
-
const collateralFlashloanAmount =
|
|
116
|
-
trimForAaveWithdrawalRounding(collateralAmount);
|
|
117
|
-
|
|
118
|
-
const builder = sdk.flow(8453, {
|
|
119
|
-
name: 'aave-to-morpho-debt-migration-with-swap',
|
|
120
|
-
inputs: {
|
|
121
|
-
initialCollateral: resources.erc20(BASE_WETH, 8453),
|
|
122
|
-
debtFlashloan: resources.erc20(BASE_USDC, 8453),
|
|
123
|
-
collateralFlashloan: resources.erc20(BASE_WETH, 8453),
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// --- Phase 1: provision the Aave V3 borrow position ----------------------
|
|
128
|
-
|
|
129
|
-
// WETH → aBasWETH via the aave routing edge family (Aave V3 supply).
|
|
130
|
-
const supplyToAave = builder.lifi.zap('supply-to-aave', {
|
|
131
|
-
bind: { amountIn: builder.inputs.initialCollateral },
|
|
132
|
-
config: { resourceOut: resources.erc20(A_BAS_WETH, 8453) },
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Borrow USDC against the freshly supplied collateral. The `borrowed`
|
|
136
|
-
// resource is left unbound; request-level `sweepTo` forwards it to the
|
|
137
|
-
// signer as the user's loan proceeds.
|
|
138
|
-
builder.aave.borrow('borrow-from-aave', {
|
|
139
|
-
bind: {},
|
|
140
|
-
config: {
|
|
141
|
-
pool: AAVE_V3_POOL_BASE,
|
|
142
|
-
asset: BASE_USDC,
|
|
143
|
-
variableDebtToken: VDEBT_BAS_USDC,
|
|
144
|
-
amount: debtAmount,
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// --- Phase 2: migrate to Morpho with two concurrent flashloans -----------
|
|
149
|
-
|
|
150
|
-
// Settle the Aave USDC debt with flashloan #1. mode: 'max' makes Aave's
|
|
151
|
-
// Pool clamp paybackAmount to userVariableDebt exactly, so any unspent
|
|
152
|
-
// flashloan principal flows through the op's residual port (swept by
|
|
153
|
-
// request-level sweepTo).
|
|
154
|
-
builder.aave.repay('repay-aave', {
|
|
155
|
-
bind: {
|
|
156
|
-
assetIn: builder.inputs.debtFlashloan,
|
|
157
|
-
onBehalfOf: builder.context.executionAddress,
|
|
158
|
-
},
|
|
159
|
-
config: { pool: AAVE_V3_POOL_BASE, mode: 'max' },
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Withdraw the freed WETH from Aave (aBasWETH → WETH) via routing.
|
|
163
|
-
const withdrawFromAave = builder.lifi.zap('withdraw-from-aave', {
|
|
164
|
-
bind: { amountIn: supplyToAave.amountOut },
|
|
165
|
-
config: { resourceOut: resources.erc20(BASE_WETH, 8453) },
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Open the Morpho Blue position with flashloan #2's WETH.
|
|
169
|
-
builder.morphoBlue.supplyCollateral('supply-to-morpho', {
|
|
170
|
-
bind: { assetIn: builder.inputs.collateralFlashloan },
|
|
171
|
-
config: { marketParams: MORPHO_WETH_EURC_MARKET, mode: 'exact' },
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Borrow EURC at the destination's (cheaper) APR.
|
|
175
|
-
const morphoBorrow = builder.morphoBlue.borrow('borrow-from-morpho', {
|
|
176
|
-
bind: {},
|
|
177
|
-
config: {
|
|
178
|
-
marketParams: MORPHO_WETH_EURC_MARKET,
|
|
179
|
-
amount: morphoBorrowAmount,
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// Swap EURC → USDC so the borrow proceeds can settle the USDC flashloan
|
|
184
|
-
// leg. `amountOut` carries `providesMinimum: true`; the caller is
|
|
185
|
-
// responsible for sizing `morphoBorrowAmount` so the slippage-adjusted
|
|
186
|
-
// minimum covers `debtAmount + aaveV3Fee(debtAmount)`.
|
|
187
|
-
const swapDebt = builder.lifi.swap('swap-borrow-to-usdc', {
|
|
188
|
-
bind: { amountIn: morphoBorrow.borrowed },
|
|
189
|
-
config: {
|
|
190
|
-
resourceOut: resources.erc20(BASE_USDC, 8453),
|
|
191
|
-
slippage: SWAP_SLIPPAGE,
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// Settle the USDC flashloan leg with the swap output.
|
|
196
|
-
builder.lifi.flashloanRepay('repay-debt-flashloan', {
|
|
197
|
-
bind: { funds: swapDebt.amountOut },
|
|
198
|
-
config: { leg: 'debtFlashloan' },
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Settle the WETH flashloan leg with the Aave withdrawal. Balancer V2
|
|
202
|
-
// charges no fee, so the withdrawn principal alone covers the leg.
|
|
203
|
-
builder.lifi.flashloanRepay('repay-collateral-flashloan', {
|
|
204
|
-
bind: { funds: withdrawFromAave.amountOut },
|
|
205
|
-
config: { leg: 'collateralFlashloan' },
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const flow = builder.build();
|
|
209
|
-
|
|
210
|
-
const request = sdk.request(flow, {
|
|
211
|
-
signer: owner,
|
|
212
|
-
inputs: {
|
|
213
|
-
initialCollateral: materialisers.directDeposit({
|
|
214
|
-
amount: collateralAmount,
|
|
215
|
-
}),
|
|
216
|
-
debtFlashloan: materialisers.flashloan({
|
|
217
|
-
providerKind: 'aave-v3',
|
|
218
|
-
amount: debtFlashloanAmount,
|
|
219
|
-
}),
|
|
220
|
-
collateralFlashloan: materialisers.flashloan({
|
|
221
|
-
providerKind: 'balancer-v2',
|
|
222
|
-
amount: collateralFlashloanAmount,
|
|
223
|
-
}),
|
|
224
|
-
},
|
|
225
|
-
// Sweep Aave borrow proceeds, aave.repay's residual, and any EURC / USDC
|
|
226
|
-
// dust left after the swap back to the signer.
|
|
227
|
-
sweepTo: builder.context.sender,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
return { flow, request };
|
|
231
|
-
};
|
|
@@ -1,307 +0,0 @@
|
|
|
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 { API_KEY, 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, apiKey: API_KEY });
|
|
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 assumptions: Record<string, bigint> = {
|
|
277
|
-
'buy-usdc-exact.amountIn': BigInt(morphoBorrowAmount),
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
const request = sdk.request(flow, {
|
|
281
|
-
signer: owner,
|
|
282
|
-
inputs: {
|
|
283
|
-
// Supply a few extra wei over the flashloaned `collateralAmount` so the
|
|
284
|
-
// Aave withdrawal (which rounds down by up to 1 wei) still fully covers
|
|
285
|
-
// the exact-principal Balancer collateral-flashloan repayment.
|
|
286
|
-
initialCollateral: materialisers.directDeposit({
|
|
287
|
-
amount: (
|
|
288
|
-
BigInt(collateralAmount) + AAVE_WITHDRAW_ROUNDING_BUFFER
|
|
289
|
-
).toString() as `${bigint}`,
|
|
290
|
-
}),
|
|
291
|
-
debtFlashloan: materialisers.flashloan({
|
|
292
|
-
providerKind: 'aave-v3',
|
|
293
|
-
amount: debtAmount,
|
|
294
|
-
}),
|
|
295
|
-
collateralFlashloan: materialisers.flashloan({
|
|
296
|
-
providerKind: 'balancer-v2',
|
|
297
|
-
amount: collateralAmount,
|
|
298
|
-
}),
|
|
299
|
-
},
|
|
300
|
-
// Sweep Aave borrow proceeds, aave.repay's residual, and any EURC over-repay
|
|
301
|
-
// residual from the capped Morpho repay back to the signer.
|
|
302
|
-
sweepTo: builder.context.sender,
|
|
303
|
-
assumptions,
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
return { flow, request };
|
|
307
|
-
};
|
|
@@ -1,142 +0,0 @@
|
|
|
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 { API_KEY, 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, apiKey: API_KEY });
|
|
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
|
-
// The smoke flow returns the full principal + fee to the adapter and
|
|
73
|
-
// produces no user output, so the guard sees the fee top-up as 100%
|
|
74
|
-
// loss. 0 skips the check; the user's real cost is only the provider fee.
|
|
75
|
-
maxPriceImpactBps: 0,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return { flow, request };
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export interface SwapAndRepayInput {
|
|
82
|
-
readonly owner: Address;
|
|
83
|
-
readonly amount: `${bigint}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Realistic variant: borrow USDC, swap to WETH, swap back to USDC, then
|
|
88
|
-
* repay using the swap-back's `amountOut` as the funds handle. No
|
|
89
|
-
* `feeBuffer` is needed because the swap-back's output is sized to cover
|
|
90
|
-
* `principal + fee` by construction.
|
|
91
|
-
*/
|
|
92
|
-
export const buildSwapAndRepay = ({
|
|
93
|
-
owner,
|
|
94
|
-
amount,
|
|
95
|
-
}: SwapAndRepayInput): {
|
|
96
|
-
flow: Flow;
|
|
97
|
-
request: ComposeCompileRequest;
|
|
98
|
-
} => {
|
|
99
|
-
const sdk = createComposeSdk({ baseUrl: BASE_URL, apiKey: API_KEY });
|
|
100
|
-
|
|
101
|
-
const builder = sdk.flow(1, {
|
|
102
|
-
name: 'flashloan-swap-and-repay',
|
|
103
|
-
inputs: {
|
|
104
|
-
borrowed: resources.erc20(USDC, 1),
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const toWeth = builder.lifi.swap('swap-to-weth', {
|
|
109
|
-
bind: { amountIn: builder.inputs.borrowed },
|
|
110
|
-
config: {
|
|
111
|
-
resourceOut: resources.erc20(WETH, 1),
|
|
112
|
-
slippage: 0.005,
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const backToUsdc = builder.lifi.swap('swap-back-to-usdc', {
|
|
117
|
-
bind: { amountIn: toWeth.amountOut },
|
|
118
|
-
config: {
|
|
119
|
-
resourceOut: resources.erc20(USDC, 1),
|
|
120
|
-
slippage: 0.005,
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
builder.lifi.flashloanRepay('repay', {
|
|
125
|
-
bind: { funds: backToUsdc.amountOut },
|
|
126
|
-
config: { leg: 'borrowed' },
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const flow = builder.build();
|
|
130
|
-
|
|
131
|
-
const request = sdk.request(flow, {
|
|
132
|
-
signer: owner,
|
|
133
|
-
inputs: {
|
|
134
|
-
borrowed: materialisers.flashloan({
|
|
135
|
-
providerKind: 'aave-v3',
|
|
136
|
-
amount,
|
|
137
|
-
}),
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
return { flow, request };
|
|
142
|
-
};
|