Skip to content

Commit ba444ba

Browse files
committed
feat: refactor mtbill strategy to use pipeline
1 parent 3334f65 commit ba444ba

File tree

4 files changed

+86
-71
lines changed

4 files changed

+86
-71
lines changed

src/swapService/config/mainnet.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,20 @@ const mainnetRoutingConfig: ChainRoutingConfig = [
100100
},
101101
// FALLBACKS
102102

103-
// first try lifi and their exact out
103+
// fallback for target debt - 1inch binary search
104104
{
105105
strategy: StrategyBalmySDK.name(),
106106
config: {
107107
sourcesFilter: {
108-
includeSources: ["li-fi"],
108+
includeSources: ["1inch"],
109109
},
110-
tryExactOut: true,
111-
onlyExactOut: true,
112110
},
113-
match: {},
111+
match: {
112+
swapperModes: [SwapperMode.TARGET_DEBT],
113+
},
114114
},
115115

116-
// then anything available through balmy. Overswap exact out
116+
// then anything available through balmy, binary search overswap exact out
117117
{
118118
strategy: StrategyBalmySDK.name(),
119119
config: {

src/swapService/strategies/strategyBalmySDK.ts

+32-7
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import {
33
type QuoteRequest,
44
type QuoteResponse,
55
type QuoteResponseWithTx,
6+
type SourceId,
67
buildSDK,
78
} from "@balmy/sdk"
89
import { buildFetchService } from "@balmy/sdk/dist/sdk/builders/fetch-builder"
910
import { buildProviderService } from "@balmy/sdk/dist/sdk/builders/provider-builder"
11+
import type { Either } from "@balmy/sdk/dist/utility-types"
1012
import { type Hex, encodeAbiParameters, parseAbiParameters } from "viem"
1113
import { SwapperMode } from "../interface"
1214
import type { StrategyResult, SwapParams, SwapQuote } from "../types"
@@ -27,12 +29,23 @@ import { CustomSourceList } from "./balmySDK/customSourceList"
2729
const DAO_MULTISIG = "0xcAD001c30E96765aC90307669d578219D4fb1DCe"
2830
const BINARY_SEARCH_EXCLUDE_SOURCES = ["paraswap"] // paraswap is rate limited and fails if selected as best source for binary search
2931

32+
type SourcesFilter =
33+
| Either<
34+
{
35+
includeSources: SourceId[]
36+
},
37+
{
38+
excludeSources: SourceId[]
39+
}
40+
>
41+
| undefined
42+
3043
export const defaultConfig = {
3144
referrer: {
3245
address: DAO_MULTISIG,
3346
name: "euler",
3447
},
35-
sourcesFilter: {},
48+
sourcesFilter: undefined,
3649
tryExactOut: false, // tries buy order search through balmy before falling back to binary search.
3750
// Use only if exact out behavior is known for source
3851
onlyExactOut: false, // don't try overswapping when exact out not available
@@ -195,7 +208,10 @@ export class StrategyBalmySDK {
195208
}
196209

197210
async #binarySearchOverswapQuote(swapParams: SwapParams) {
198-
const fetchQuote = async (sp: SwapParams, sourcesFilter?: any) => {
211+
const fetchQuote = async (
212+
sp: SwapParams,
213+
sourcesFilter?: SourcesFilter,
214+
) => {
199215
const quote = await this.#getBestSDKQuote(sp, sourcesFilter)
200216
return {
201217
quote,
@@ -210,9 +226,12 @@ export class StrategyBalmySDK {
210226
swapperMode: SwapperMode.EXACT_IN,
211227
}
212228

213-
const reverseQuote = await fetchQuote(reverseSwapParams, {
214-
excludeSources: BINARY_SEARCH_EXCLUDE_SOURCES,
215-
}) // TODO config
229+
const reverseQuote = await fetchQuote(
230+
reverseSwapParams,
231+
this.config.sourcesFilter || {
232+
excludeSources: BINARY_SEARCH_EXCLUDE_SOURCES,
233+
},
234+
) // TODO config
216235
const estimatedAmountIn = reverseQuote.amountTo
217236
if (estimatedAmountIn === 0n) throw new Error("quote not found")
218237

@@ -241,7 +260,10 @@ export class StrategyBalmySDK {
241260
return this.#getSwapQuoteFromSDKQuoteWithTx(swapParams, quoteWithTx)
242261
}
243262

244-
async #getBestSDKQuote(swapParams: SwapParams, sourcesFilter?: any) {
263+
async #getBestSDKQuote(
264+
swapParams: SwapParams,
265+
sourcesFilter?: SourcesFilter,
266+
) {
245267
// TODO type
246268
const bestQuote = await this.sdk.quoteService.getBestQuote({
247269
request: this.#getSDKQuoteFromSwapParams(swapParams, sourcesFilter),
@@ -255,7 +277,10 @@ export class StrategyBalmySDK {
255277
return bestQuote
256278
}
257279

258-
async #getBestSDKQuoteWithTx(swapParams: SwapParams, sourcesFilter?: any) {
280+
async #getBestSDKQuoteWithTx(
281+
swapParams: SwapParams,
282+
sourcesFilter?: SourcesFilter,
283+
) {
259284
const bestQuote = await this.#getBestSDKQuote(swapParams, sourcesFilter)
260285

261286
const bestQuoteWithTx = {

src/swapService/strategies/strategyERC4626Wrapper.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,7 @@ export class StrategyERC4626Wrapper {
138138
result.response =
139139
await this.targetDebtFromUnderlyingToVault(swapParams)
140140
} else {
141-
result.response =
142-
await this.targetDebtToFromAnyToVault(swapParams)
141+
result.response = await this.targetDebtFromAnyToVault(swapParams)
143142
}
144143
}
145144
break
@@ -438,7 +437,7 @@ export class StrategyERC4626Wrapper {
438437
}
439438
const {
440439
swapMulticallItem: withdrawMulticallItem,
441-
amountIn: redeemInstantAmountIn,
440+
amountIn: withdrawAmountIn,
442441
} = await encodeWithdraw(
443442
withdrawSwapParams,
444443
vaultData.vault,
@@ -463,8 +462,8 @@ export class StrategyERC4626Wrapper {
463462
)
464463

465464
return {
466-
amountIn: String(redeemInstantAmountIn),
467-
amountInMax: String(redeemInstantAmountIn),
465+
amountIn: String(withdrawAmountIn),
466+
amountInMax: String(withdrawAmountIn),
468467
amountOut: String(innerQuote.amountOut),
469468
amountOutMin: String(innerQuote.amountOutMin),
470469
vaultIn: swapParams.vaultIn,
@@ -529,7 +528,7 @@ export class StrategyERC4626Wrapper {
529528
}
530529
}
531530

532-
async targetDebtToFromAnyToVault(
531+
async targetDebtFromAnyToVault(
533532
swapParams: SwapParams,
534533
): Promise<SwapApiResponse> {
535534
const vaultData = this.getSupportedVault(swapParams.tokenOut.addressInfo)

src/swapService/strategies/strategyMTBILL.ts

+43-52
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
} from "viem"
1212
import { SwapperMode } from "../interface"
1313
import type { SwapApiResponse } from "../interface"
14-
import { fetchLiFiExactOutQuote } from "../quoters"
1514
import { runPipeline } from "../runner"
1615
import type { StrategyResult, SwapParams } from "../types"
1716
import {
@@ -26,7 +25,6 @@ import {
2625
findToken,
2726
isExactInRepay,
2827
matchParams,
29-
quoteToRoute,
3028
} from "../utils"
3129

3230
export const MTBILL_MAINNET = "0xdd629e5241cbc5919847783e6c96b2de4754e438"
@@ -87,9 +85,9 @@ export class StrategyMTBILL {
8785
}
8886
} else {
8987
if (isAddressEqual(swapParams.tokenIn.addressInfo, USDC_MAINNET)) {
90-
result.response = await this.exactInToMTokenFromUSDC(swapParams)
88+
result.response = await this.exactInFromUSDCToMToken(swapParams)
9189
} else {
92-
result.response = await this.exactInToMTokenFromAny(swapParams)
90+
result.response = await this.exactInFromAnyToMToken(swapParams)
9391
}
9492
}
9593
break
@@ -105,9 +103,9 @@ export class StrategyMTBILL {
105103
} else {
106104
if (isAddressEqual(swapParams.tokenIn.addressInfo, USDC_MAINNET)) {
107105
result.response =
108-
await this.targetDebtToMTokenFromUSDC(swapParams)
106+
await this.targetDebtFromUSDCToMToken(swapParams)
109107
} else {
110-
result.response = await this.targetDebtToMTokenFromAny(swapParams)
108+
result.response = await this.targetDebtFromAnyToMToken(swapParams)
111109
}
112110
}
113111
break
@@ -224,7 +222,7 @@ export class StrategyMTBILL {
224222
}
225223
}
226224

227-
async exactInToMTokenFromUSDC(
225+
async exactInFromUSDCToMToken(
228226
swapParams: SwapParams,
229227
): Promise<SwapApiResponse> {
230228
const {
@@ -273,7 +271,7 @@ export class StrategyMTBILL {
273271
}
274272
}
275273

276-
async exactInToMTokenFromAny(
274+
async exactInFromAnyToMToken(
277275
swapParams: SwapParams,
278276
): Promise<SwapApiResponse> {
279277
const innerSwapParams = {
@@ -392,25 +390,13 @@ export class StrategyMTBILL {
392390
async targetDebtFromMTokenToAny(
393391
swapParams: SwapParams,
394392
): Promise<SwapApiResponse> {
395-
const lifiSwapParams = {
393+
const innerSwapParams = {
396394
...swapParams,
397395
tokenIn: findToken(swapParams.chainId, USDC_MAINNET),
398-
receiver: swapParams.from,
396+
vaultIn: USDC_ESCROW_VAULT_MAINNET,
397+
onlyFixedInputExactOut: true, // eliminate dust in the intermediate asset (vault underlying)
399398
}
400-
const lifiQuote = await fetchLiFiExactOutQuote(lifiSwapParams)
401-
402-
const lifiSwapMulticallItem = encodeSwapMulticallItem({
403-
handler: SWAPPER_HANDLER_GENERIC,
404-
mode: BigInt(SwapperMode.TARGET_DEBT), // will deposit overswap from lifi, and also USDC although it shouldn't be necessary
405-
account: swapParams.accountOut,
406-
tokenIn: swapParams.tokenIn.addressInfo,
407-
tokenOut: swapParams.tokenOut.addressInfo,
408-
vaultIn: swapParams.vaultIn,
409-
accountIn: swapParams.accountIn,
410-
receiver: swapParams.receiver,
411-
amountOut: swapParams.targetDebt,
412-
data: lifiQuote.data,
413-
})
399+
const innerQuote = await runPipeline(innerSwapParams)
414400

415401
const redeemSwapParams = {
416402
...swapParams,
@@ -421,12 +407,15 @@ export class StrategyMTBILL {
421407
amountIn: redeemInstantAmountIn,
422408
} = await encodeMTBILLRedeemInstant(
423409
redeemSwapParams,
424-
lifiQuote.amountIn,
410+
BigInt(innerQuote.amountIn),
425411
false,
426412
USDC_MAINNET,
427413
)
428414

429-
const multicallItems = [redeemInstantMulticallItem, lifiSwapMulticallItem]
415+
const multicallItems = [
416+
redeemInstantMulticallItem,
417+
...innerQuote.swap.multicallItems,
418+
]
430419

431420
const swap = buildApiResponseSwap(swapParams.from, multicallItems)
432421

@@ -441,22 +430,22 @@ export class StrategyMTBILL {
441430
return {
442431
amountIn: String(redeemInstantAmountIn),
443432
amountInMax: String(redeemInstantAmountIn),
444-
amountOut: String(lifiQuote.amountOut),
445-
amountOutMin: String(lifiQuote.amountOutMin),
433+
amountOut: String(innerQuote.amountOut),
434+
amountOutMin: String(innerQuote.amountOutMin),
446435
vaultIn: swapParams.vaultIn,
447436
receiver: swapParams.receiver,
448437
accountIn: swapParams.accountIn,
449438
accountOut: swapParams.accountOut,
450439
tokenIn: swapParams.tokenIn,
451440
tokenOut: swapParams.tokenOut,
452441
slippage: swapParams.slippage,
453-
route: [MTBILL_ROUTE, ...quoteToRoute(lifiQuote)],
442+
route: [MTBILL_ROUTE, ...innerQuote.route],
454443
swap,
455444
verify,
456445
}
457446
}
458447

459-
async targetDebtToMTokenFromUSDC(
448+
async targetDebtFromUSDCToMToken(
460449
swapParams: SwapParams,
461450
): Promise<SwapApiResponse> {
462451
const depositInstantAmount = adjustForInterest(swapParams.amount) // TODO move to config, helper
@@ -503,12 +492,12 @@ export class StrategyMTBILL {
503492
}
504493
}
505494

506-
async targetDebtToMTokenFromAny(
495+
async targetDebtFromAnyToMToken(
507496
swapParams: SwapParams,
508497
): Promise<SwapApiResponse> {
509498
const targetDeposit = adjustForInterest(swapParams.amount) // TODO move to config, helper
510499

511-
const swapParamsDeposit = {
500+
const depositSwapParams = {
512501
...swapParams,
513502
tokenIn: findToken(swapParams.chainId, USDC_MAINNET),
514503
vaultIn: USDC_ESCROW_VAULT_MAINNET,
@@ -518,34 +507,36 @@ export class StrategyMTBILL {
518507
amountIn: depositInstantAmountIn,
519508
amountOut,
520509
} = await encodeMTBILLDepositInstant(
521-
swapParamsDeposit,
510+
depositSwapParams,
522511
targetDeposit,
523512
true,
524513
USDC_MAINNET,
525514
)
526515

527-
const lifiSwapParams = {
516+
const innerSwapParams = {
528517
...swapParams,
529518
amount: depositInstantAmountIn,
530519
tokenOut: findToken(swapParams.chainId, USDC_MAINNET),
531-
swapperMode: SwapperMode.EXACT_IN,
532520
receiver: swapParams.from,
521+
onlyFixedInputExactOut: true, // this option will overswap, which should cover growing exchange rate
533522
}
534-
const lifiQuote = await fetchLiFiExactOutQuote(lifiSwapParams)
535-
const lifiSwapMulticallItem = encodeSwapMulticallItem({
536-
handler: SWAPPER_HANDLER_GENERIC,
537-
mode: BigInt(SwapperMode.EXACT_IN),
538-
account: swapParams.accountOut,
539-
tokenIn: swapParams.tokenIn.addressInfo,
540-
tokenOut: swapParams.tokenOut.addressInfo,
541-
vaultIn: swapParams.vaultIn,
542-
accountIn: swapParams.accountIn,
543-
receiver: lifiSwapParams.receiver,
544-
amountOut: 0n, // ignored
545-
data: lifiQuote.data,
523+
524+
const innerQuote = await runPipeline(innerSwapParams)
525+
526+
// re-encode inner swap from target debt to exact out so that repay is not executed before mint TODO fix with exact out support in all strategies
527+
const innerSwapItems = innerQuote.swap.multicallItems.map((item) => {
528+
if (item.functionName !== "swap") return item
529+
530+
const newItem = encodeSwapMulticallItem({
531+
...item.args[0],
532+
mode: BigInt(SwapperMode.EXACT_OUT),
533+
})
534+
535+
return newItem
546536
})
547-
// deposit instant is encoded in target debt mode, so repay will happen automatically
548-
const multicallItems = [lifiSwapMulticallItem, depositInstantMulticallItem]
537+
538+
// repay is done through deposit item, which will return unused input, which is the intermediate asset
539+
const multicallItems = [...innerSwapItems, depositInstantMulticallItem]
549540

550541
const swap = buildApiResponseSwap(swapParams.from, multicallItems)
551542

@@ -558,8 +549,8 @@ export class StrategyMTBILL {
558549
)
559550

560551
return {
561-
amountIn: String(lifiQuote.amountIn),
562-
amountInMax: String(lifiQuote.amountIn),
552+
amountIn: String(innerQuote.amountIn),
553+
amountInMax: String(innerQuote.amountInMax),
563554
amountOut: String(amountOut),
564555
amountOutMin: String(amountOut),
565556
vaultIn: swapParams.vaultIn,
@@ -569,7 +560,7 @@ export class StrategyMTBILL {
569560
tokenIn: swapParams.tokenIn,
570561
tokenOut: swapParams.tokenOut,
571562
slippage: swapParams.slippage,
572-
route: [...quoteToRoute(lifiQuote), MTBILL_ROUTE],
563+
route: [...innerQuote.route, MTBILL_ROUTE],
573564
swap,
574565
verify,
575566
}

0 commit comments

Comments
 (0)