Skip to content

Commit 440232e

Browse files
committed
update strategies for /swaps
1 parent b15c71d commit 440232e

File tree

10 files changed

+814
-736
lines changed

10 files changed

+814
-736
lines changed

src/api/routes/swap/swapRouter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ swapRouter.get(
5656
} catch (error) {
5757
return handleServiceResponse(createFailureResponse(req, error), res)
5858
} finally {
59-
console.log("===== END =====")
59+
console.log("===== SWAP END =====")
6060
}
6161
},
6262
)
@@ -74,7 +74,7 @@ swapRouter.get(
7474
} catch (error) {
7575
return handleServiceResponse(createFailureResponse(req, error), res)
7676
} finally {
77-
console.log("===== END =====")
77+
console.log("===== SWAPS END =====")
7878
}
7979
},
8080
)

src/swapService/config/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export const SWAP_DEFAULT_DEADLINE = 1800n
2+
export const BINARY_SEARCH_TIMEOUT_SECONDS = 30

src/swapService/strategies/strategyBalmySDK.ts

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@ import {
2222
parseAbiParameters,
2323
parseUnits,
2424
} from "viem"
25-
import {
26-
SwapApiResponse,
27-
type SwapApiResponseMulticallItem,
28-
SwapperMode,
29-
} from "../interface"
25+
import { BINARY_SEARCH_TIMEOUT_SECONDS } from "../config/constants"
26+
import { type SwapApiResponseMulticallItem, SwapperMode } from "../interface"
3027
import type { StrategyResult, SwapParams, SwapQuote } from "../types"
3128
import {
3229
SWAPPER_HANDLER_GENERIC,
@@ -40,6 +37,7 @@ import {
4037
encodeSwapMulticallItem,
4138
isExactInRepay,
4239
matchParams,
40+
promiseWithTimeout,
4341
quoteToRoute,
4442
} from "../utils"
4543
import { CustomSourceList } from "./balmySDK/customSourceList"
@@ -48,7 +46,7 @@ import { TokenlistMetadataSource } from "./balmySDK/tokenlistMetadataSource"
4846
const DAO_MULTISIG = "0xcAD001c30E96765aC90307669d578219D4fb1DCe"
4947
const DEFAULT_TIMEOUT = "30000"
5048
// TODO config
51-
const BINARY_SEARCH_EXCLUDE_SOURCES = [] // paraswap is rate limited and fails if selected as best source for binary search
49+
const BINARY_SEARCH_EXCLUDE_SOURCES: any = [] // paraswap is rate limited and fails if selected as best source for binary search
5250

5351
type SourcesFilter =
5452
| Either<
@@ -234,7 +232,7 @@ export class StrategyBalmySDK {
234232
swapperMode: SwapperMode.EXACT_IN,
235233
}
236234

237-
quotes = [await this.#binarySearchOverswapQuote(innerSwapParams)]
235+
quotes = await this.#binarySearchOverswapQuote(innerSwapParams)
238236
}
239237

240238
if (!quotes) throw new Error("Quote not found")
@@ -296,17 +294,6 @@ export class StrategyBalmySDK {
296294
}
297295

298296
async #binarySearchOverswapQuote(swapParams: SwapParams) {
299-
const fetchQuote = async (
300-
sp: SwapParams,
301-
sourcesFilter?: SourcesFilter,
302-
) => {
303-
const quote = (await this.#getAllQuotes(sp, sourcesFilter))[0]
304-
return {
305-
quote,
306-
amountTo: quote.minBuyAmount.amount,
307-
}
308-
}
309-
310297
let sourcesFilter
311298
if (this.config.sourcesFilter?.includeSources) {
312299
sourcesFilter = {
@@ -330,13 +317,14 @@ export class StrategyBalmySDK {
330317
receiver: swapParams.from,
331318
isRepay: false,
332319
}
333-
const { amountTo: unitAmountTo } = await fetchQuote(
320+
const unitQuotes = await this.#getAllQuotes(
334321
{
335322
...swapParamsExactIn,
336323
amount: parseUnits("1", swapParams.tokenIn.decimals),
337324
},
338325
sourcesFilter,
339326
)
327+
const unitAmountTo = unitQuotes[0].minBuyAmount.amount
340328

341329
const estimatedAmountIn = calculateEstimatedAmountFrom(
342330
unitAmountTo,
@@ -354,29 +342,51 @@ export class StrategyBalmySDK {
354342
currentAmountTo < overSwapTarget ||
355343
(currentAmountTo * 1000n) / overSwapTarget > 1005n
356344

357-
let bestSourceId: string
345+
// single run to preselect sources
346+
const initialQuotes = await this.#getAllQuotes({
347+
...swapParams,
348+
amount: estimatedAmountIn,
349+
})
358350

359-
const quote = await binarySearchQuote(
360-
swapParams,
361-
async (swapParams: SwapParams) => {
362-
let bestSourceConfig
363-
if (bestSourceId) {
364-
bestSourceConfig = { includeSources: [bestSourceId] }
365-
}
366-
const q = await fetchQuote(swapParams, bestSourceConfig) // preselect single source to avoid oscilations
367-
if (!bestSourceId) bestSourceId = q.quote.source.id
368-
return q
369-
},
370-
overSwapTarget,
371-
estimatedAmountIn,
372-
shouldContinue,
351+
const allSettled = await Promise.allSettled(
352+
initialQuotes.map(async (initialQuote) =>
353+
promiseWithTimeout(async () => {
354+
const quote = await binarySearchQuote(
355+
swapParams,
356+
async (swapParams: SwapParams) => {
357+
const result = await this.#getAllQuotes(swapParams, {
358+
includeSources: [initialQuote.source.id],
359+
})
360+
return {
361+
quote: result[0],
362+
amountTo: result[0].minBuyAmount.amount,
363+
}
364+
},
365+
overSwapTarget,
366+
estimatedAmountIn,
367+
shouldContinue,
368+
{
369+
quote: initialQuote,
370+
amountTo: initialQuote.minBuyAmount.amount,
371+
},
372+
)
373+
374+
const quoteWithTx = {
375+
...quote,
376+
tx: await this.#getTxForQuote(quote),
377+
}
378+
379+
return this.#getSwapQuoteFromSDKQuoteWithTx(swapParams, quoteWithTx)
380+
}, BINARY_SEARCH_TIMEOUT_SECONDS),
381+
),
373382
)
374-
const quoteWithTx = {
375-
...quote,
376-
tx: await this.#getTxForQuote(quote),
377-
}
378383

379-
return this.#getSwapQuoteFromSDKQuoteWithTx(swapParams, quoteWithTx)
384+
const bestQuotes = allSettled
385+
.filter((q) => q.status === "fulfilled")
386+
.map((q) => q.value)
387+
if (bestQuotes.length === 0) throw new Error("Quotes not found")
388+
389+
return bestQuotes
380390
}
381391

382392
// async #binarySearchOverswapQuote(swapParams: SwapParams) {

src/swapService/strategies/strategyCombinedUniswap.ts

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class StrategyCombinedUniswap {
7474

7575
return {
7676
quote,
77-
amountTo: BigInt(quote.amountOut),
77+
amountTo: BigInt(quote[0].amountOut),
7878
}
7979
}
8080

@@ -106,17 +106,17 @@ export class StrategyCombinedUniswap {
106106
(currentAmountTo * 1000n) / underSwapTarget < 995n
107107

108108
// TODO handle case where 1 wei of input is already too much (eg swap usdc -> weth target 1e6)
109-
const exactInputQuote = (await binarySearchQuote(
109+
const exactInputQuotes = (await binarySearchQuote(
110110
swapParamsExactIn,
111111
fetchQuote,
112112
underSwapTarget,
113113
estimatedAmountFrom,
114114
shouldContinue,
115-
)) as SwapApiResponse
115+
)) as SwapApiResponse[]
116116

117117
const uniswapSwapParams = {
118118
...swapParams,
119-
amount: swapParams.amount - BigInt(exactInputQuote.amountOut),
119+
amount: swapParams.amount - BigInt(exactInputQuotes[0].amountOut),
120120
receiver: swapParams.from,
121121
}
122122
const {
@@ -125,59 +125,62 @@ export class StrategyCombinedUniswap {
125125
amountIn: uniswapAmountIn, // assuming exact out trade
126126
} = await fetchUniswapQuote(uniswapSwapParams)
127127

128-
const uniswapSwapMulticallItem = encodeSwapMulticallItem({
129-
handler:
130-
protocol === "V2"
131-
? SWAPPER_HANDLER_UNISWAP_V2
132-
: SWAPPER_HANDLER_UNISWAP_V3,
133-
mode: BigInt(SwapperMode.TARGET_DEBT),
134-
account: swapParams.accountOut,
135-
tokenIn: swapParams.tokenIn.addressInfo,
136-
tokenOut: swapParams.tokenOut.addressInfo,
137-
vaultIn: swapParams.vaultIn,
138-
accountIn: swapParams.accountIn,
139-
receiver: swapParams.receiver,
140-
amountOut: swapParams.targetDebt,
141-
data: path as Hex,
142-
})
143-
144-
const combinedMulticallItems = [
145-
...exactInputQuote.swap.multicallItems,
146-
uniswapSwapMulticallItem,
147-
]
148-
149-
const swap = buildApiResponseSwap(swapParams.from, combinedMulticallItems)
150-
151-
const verify = buildApiResponseVerifyDebtMax(
152-
swapParams.chainId,
153-
swapParams.receiver,
154-
swapParams.accountOut,
155-
swapParams.targetDebt,
156-
swapParams.deadline,
157-
)
128+
result.quotes = exactInputQuotes.map((exactInputQuote) => {
129+
const uniswapSwapMulticallItem = encodeSwapMulticallItem({
130+
handler:
131+
protocol === "V2"
132+
? SWAPPER_HANDLER_UNISWAP_V2
133+
: SWAPPER_HANDLER_UNISWAP_V3,
134+
mode: BigInt(SwapperMode.TARGET_DEBT),
135+
account: swapParams.accountOut,
136+
tokenIn: swapParams.tokenIn.addressInfo,
137+
tokenOut: swapParams.tokenOut.addressInfo,
138+
vaultIn: swapParams.vaultIn,
139+
accountIn: swapParams.accountIn,
140+
receiver: swapParams.receiver,
141+
amountOut: swapParams.targetDebt,
142+
data: path as Hex,
143+
})
144+
145+
const combinedMulticallItems = [
146+
...exactInputQuote.swap.multicallItems,
147+
uniswapSwapMulticallItem,
148+
]
149+
150+
const swap = buildApiResponseSwap(
151+
swapParams.from,
152+
combinedMulticallItems,
153+
)
154+
155+
const verify = buildApiResponseVerifyDebtMax(
156+
swapParams.chainId,
157+
swapParams.receiver,
158+
swapParams.accountOut,
159+
swapParams.targetDebt,
160+
swapParams.deadline,
161+
)
162+
163+
const amountIn =
164+
BigInt(exactInputQuote.amountIn) + BigInt(uniswapAmountIn)
165+
const amountInMax = applySlippage(amountIn, swapParams.slippage, true)
158166

159-
const amountIn =
160-
BigInt(exactInputQuote.amountIn) + BigInt(uniswapAmountIn)
161-
const amountInMax = applySlippage(amountIn, swapParams.slippage, true)
162-
163-
const combinedQuote = {
164-
amountIn: String(amountIn),
165-
amountInMax: String(amountInMax),
166-
amountOut: String(swapParams.amount),
167-
amountOutMin: String(swapParams.amount),
168-
vaultIn: swapParams.vaultIn,
169-
receiver: swapParams.receiver,
170-
accountIn: swapParams.accountIn,
171-
accountOut: swapParams.accountOut,
172-
tokenIn: swapParams.tokenIn,
173-
tokenOut: swapParams.tokenOut,
174-
slippage: swapParams.slippage,
175-
route: [...exactInputQuote.route, { providerName: "Uniswap" }],
176-
swap,
177-
verify,
178-
}
179-
180-
result.quotes = [combinedQuote]
167+
return {
168+
amountIn: String(amountIn),
169+
amountInMax: String(amountInMax),
170+
amountOut: String(swapParams.amount),
171+
amountOutMin: String(swapParams.amount),
172+
vaultIn: swapParams.vaultIn,
173+
receiver: swapParams.receiver,
174+
accountIn: swapParams.accountIn,
175+
accountOut: swapParams.accountOut,
176+
tokenIn: swapParams.tokenIn,
177+
tokenOut: swapParams.tokenOut,
178+
slippage: swapParams.slippage,
179+
route: [...exactInputQuote.route, { providerName: "Uniswap" }],
180+
swap,
181+
verify,
182+
}
183+
})
181184
} catch (error) {
182185
result.error = error
183186
}

0 commit comments

Comments
 (0)