Skip to content

Commit af1b7e1

Browse files
committed
update strategies for /swaps
1 parent 58b7baf commit af1b7e1

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<
@@ -228,7 +226,7 @@ export class StrategyBalmySDK {
228226
swapperMode: SwapperMode.EXACT_IN,
229227
}
230228

231-
quotes = [await this.#binarySearchOverswapQuote(innerSwapParams)]
229+
quotes = await this.#binarySearchOverswapQuote(innerSwapParams)
232230
}
233231

234232
if (!quotes) throw new Error("Quote not found")
@@ -290,17 +288,6 @@ export class StrategyBalmySDK {
290288
}
291289

292290
async #binarySearchOverswapQuote(swapParams: SwapParams) {
293-
const fetchQuote = async (
294-
sp: SwapParams,
295-
sourcesFilter?: SourcesFilter,
296-
) => {
297-
const quote = (await this.#getAllQuotes(sp, sourcesFilter))[0]
298-
return {
299-
quote,
300-
amountTo: quote.minBuyAmount.amount,
301-
}
302-
}
303-
304291
let sourcesFilter
305292
if (this.config.sourcesFilter?.includeSources) {
306293
sourcesFilter = {
@@ -324,13 +311,14 @@ export class StrategyBalmySDK {
324311
receiver: swapParams.from,
325312
isRepay: false,
326313
}
327-
const { amountTo: unitAmountTo } = await fetchQuote(
314+
const unitQuotes = await this.#getAllQuotes(
328315
{
329316
...swapParamsExactIn,
330317
amount: parseUnits("1", swapParams.tokenIn.decimals),
331318
},
332319
sourcesFilter,
333320
)
321+
const unitAmountTo = unitQuotes[0].minBuyAmount.amount
334322

335323
const estimatedAmountIn = calculateEstimatedAmountFrom(
336324
unitAmountTo,
@@ -348,29 +336,51 @@ export class StrategyBalmySDK {
348336
currentAmountTo < overSwapTarget ||
349337
(currentAmountTo * 1000n) / overSwapTarget > 1005n
350338

351-
let bestSourceId: string
339+
// single run to preselect sources
340+
const initialQuotes = await this.#getAllQuotes({
341+
...swapParams,
342+
amount: estimatedAmountIn,
343+
})
352344

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

373-
return this.#getSwapQuoteFromSDKQuoteWithTx(swapParams, quoteWithTx)
378+
const bestQuotes = allSettled
379+
.filter((q) => q.status === "fulfilled")
380+
.map((q) => q.value)
381+
if (bestQuotes.length === 0) throw new Error("Quotes not found")
382+
383+
return bestQuotes
374384
}
375385

376386
// 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)