import BigNumber from "bignumber.js"
import { Address, Pair } from "./pair"
export interface Route {
pairs: Pair[]
path: Address[]
pathAmounts: BigNumber[]
outputToken: Address
outputAmount: BigNumber
}
export interface RouterOpts {
maxSwaps?: number,
tokenWhitelist?: Address[],
}
export const findBestRoutesForFixedInputAmount = (
pairsByToken: Map
,
inputToken: Address,
outputToken: Address,
inputAmount: BigNumber,
opts?: RouterOpts) => {
const maxSwaps = opts?.maxSwaps || 10
const tokenWhitelistSet = opts?.tokenWhitelist ? new Set(opts.tokenWhitelist) : null
const completedRoutes: Route[] = []
let currentRoutes = new Map([
[
inputToken, {
pairs: [],
path: [inputToken],
pathAmounts: [inputAmount],
outputToken: inputToken,
outputAmount: inputAmount,
}
]
])
const maxOutputAmounts = new Map([
[inputToken, inputAmount],
])
for (let d = 0; d < maxSwaps; d += 1) {
const nextRoutes = new Map()
for (const route of currentRoutes.values()) {
const matchingPairs = pairsByToken.get(route.outputToken) || []
for (const pair of matchingPairs) {
const outputT =
pair.tokenA === route.outputToken ? pair.tokenB :
pair.tokenB === route.outputToken ? pair.tokenA : null
if (!outputT) {
throw new Error(`pairsByToken is invalid? ${pair.tokenA}/${pair.tokenB} !== ${route.outputToken}`)
}
if (tokenWhitelistSet && !tokenWhitelistSet.has(outputT)) {
continue // not in whitelist
}
if (pair.pairKey !== null && route.pairs.find((p) => p.pairKey === pair.pairKey)) {
continue // skip already used or conflicting pairs.
}
const outputTAmount = pair.outputAmount(route.outputToken, route.outputAmount)
const maxOutputAmount = maxOutputAmounts.get(outputT) || new BigNumber(0)
if (maxOutputAmount.gte(outputTAmount)) {
continue // we have already explored better routes before.
}
maxOutputAmounts.set(outputT, outputTAmount)
const routeT: Route = {
pairs: [...route.pairs, pair],
path: [...route.path, outputT],
pathAmounts: [...route.pathAmounts, outputTAmount],
outputToken: outputT,
outputAmount: outputTAmount,
}
nextRoutes.set(outputT, routeT)
if (outputT === outputToken) {
completedRoutes.push(routeT)
}
}
}
currentRoutes = nextRoutes
// console.debug(`UBE ROUTER: Depth: ${d+1}, routes: ${currentRoutes.size}, completed: ${completedRoutes.length}`)
if (currentRoutes.size === 0) {
break
}
}
completedRoutes.sort((a, b) => a.outputAmount.gt(b.outputAmount) ? -1 : 1)
return completedRoutes
}