import Web3 from 'web3'; import { DODORoute } from './dodoRoute'; import { UniRoute } from './uniRoute'; import { DODO_SDK_DATA } from './data/constants'; import { GetQuoteTokenList } from './tokenType'; import BigNumber from 'bignumber.js'; import { formatFixed } from '@ethersproject/bignumber'; import * as DODO_PROXY_ABI from './abi/dodoProxy.json'; export class MixRoute { public ETH_ADDRESS: string = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' public WETH_TOKEN: { symbol: string; address: string; decimals: number, name: string, source: string } public NETWORK_ID: number public WEB3_URL: string public DODO_PROXY: string public DODO_APPROVE: string public DODO_SWAP_CALC_HELPER: string public UNI_ROUTER_V2: string public constructor( _NETWORK_ID: number, _WEB3_URL?: string, _DODO_PROXY?: string, _DODO_APPROVE?: string ) { this.NETWORK_ID = _NETWORK_ID switch (_NETWORK_ID) { case 1: this.WEB3_URL = _WEB3_URL || DODO_SDK_DATA.WEB3_URL this.DODO_SWAP_CALC_HELPER = DODO_SDK_DATA.DODO_SWAP_CALC_HELPER this.DODO_APPROVE = _DODO_APPROVE || DODO_SDK_DATA.DODO_APPROVE this.DODO_PROXY = _DODO_PROXY || DODO_SDK_DATA.DODO_PROXY this.WETH_TOKEN = DODO_SDK_DATA.WETH this.UNI_ROUTER_V2 = DODO_SDK_DATA.UNI_ROUTER_V2 break; case 42: this.WEB3_URL = _WEB3_URL || DODO_SDK_DATA.WEB3_URL_KOVAN this.DODO_SWAP_CALC_HELPER = DODO_SDK_DATA.DODO_SWAP_CALC_HELPER_KOVAN this.DODO_APPROVE = _DODO_APPROVE || DODO_SDK_DATA.DODO_APPROVE_KOVAN this.DODO_PROXY = _DODO_PROXY || DODO_SDK_DATA.DODO_PROXY_KOVAN this.WETH_TOKEN = DODO_SDK_DATA.WETH_KOVAN break; case 56: this.WEB3_URL = _WEB3_URL || DODO_SDK_DATA.WEB3_URL_BSC this.DODO_SWAP_CALC_HELPER = DODO_SDK_DATA.DODO_SWAP_CALC_HELPER_BSC this.DODO_APPROVE = _DODO_APPROVE || DODO_SDK_DATA.DODO_APPROVE_BSC this.DODO_PROXY = _DODO_PROXY || DODO_SDK_DATA.DODO_PROXY_BSC this.WETH_TOKEN = DODO_SDK_DATA.WETH_BSC break; } } public async GetAndMakeDODOAndUniRoute( fromToken: { decimals: number; symbol: string; address: string; source: string; name?: string; }, toToken: { symbol: string; address: string; name: string; decimals: number; source: string }, fromAmount: string, assetTo: string, deadline: number = 0, slippage: number = 0, routeDetail: { resAmount: number, resPricePerToToken: number, resPricePerFromToken: number, priceImpact: number, useSource: string, targetDecimals: number, targetApproveAddr: string, to?: string, data?: string, resCostGas?: number } ) { const dodoRoute = new DODORoute(1, this.WEB3_URL) const uniRoute = new UniRoute(this.WEB3_URL) let quoteTokenList = GetQuoteTokenList(1); if (fromToken.address != this.ETH_ADDRESS) { routeDetail.targetApproveAddr = this.DODO_APPROVE; } let fromTokenAddr = fromToken.address let toTokenAddr = toToken.address if (fromToken.address == this.ETH_ADDRESS) fromToken = this.WETH_TOKEN if (toToken.address == this.ETH_ADDRESS) toToken = this.WETH_TOKEN try { if (fromToken.source == 'dodo' && toToken.source != 'dodo') { let funcArr: any[] = [] for (let i = 0; i < quoteTokenList.length; i++) { if (fromToken.address !== quoteTokenList[i].address) funcArr.push(dodoRoute.GetDODOSwapCallParams(fromToken, quoteTokenList[i], fromAmount, true)) } let dodoRes = await Promise.all(funcArr) let uniFuncArr: any[] = [] for (let i = 0; i < dodoRes.length; i++) { let cur = new BigNumber(dodoRes[i][0]) uniFuncArr.push(uniRoute.getSwapCallDetails(dodoRes[i][8], toToken, 2, cur.toString())) } let uniRes = await Promise.all(uniFuncArr) let isNoPath = true; let selectIdx: number = 0 let maxReturnAmount: BigNumber = new BigNumber(0) for (let i = 0; i < uniRes.length; i++) { if (uniRes[i] == null) continue let cur = new BigNumber(uniRes[i].outAmount) if (cur.gt(maxReturnAmount)) { maxReturnAmount = cur selectIdx = i } isNoPath = false } //TODO: fromToken 本身是quote,可以直接走uni if (isNoPath) { console.log("toToken not in dodo,directly uni"); return await this.DirectUniRoute(uniRoute, fromToken, toToken, fromAmount, assetTo, deadline, slippage, routeDetail); } let selectUniRes = uniRes[selectIdx] let selectDodoRes = dodoRes[selectIdx] let resAmount = parseFloat(formatFixed(selectUniRes.outAmount, toToken.decimals)) let resPricePerToToken = parseFloat(formatFixed(fromAmount, fromToken.decimals)) / resAmount routeDetail.resPricePerToToken = resPricePerToToken routeDetail.resPricePerFromToken = 1 / resPricePerToToken; routeDetail.resAmount = resAmount let uniOriginPrice = new BigNumber(selectDodoRes[0]).div(new BigNumber(selectUniRes.outAmount).multipliedBy(1 - selectUniRes.priceImpact / 100)).multipliedBy(10 ** (toToken.decimals - quoteTokenList[selectIdx]['decimals'])).toNumber() let dodoOriginPrice = selectDodoRes[6] let originPrice = uniOriginPrice * dodoOriginPrice //TODO: priceImpact计算有待商榷 let _priceImpact = 1 - 1 / new BigNumber(originPrice).multipliedBy(resPricePerToToken).toNumber() routeDetail.priceImpact = _priceImpact < 0 ? 0 : _priceImpact let minReturnAmount = new BigNumber(selectUniRes.outAmount).multipliedBy(1 - slippage / 100).toFixed(0, BigNumber.ROUND_DOWN) let dodoPairs = selectDodoRes[1] dodoPairs.push(this.UNI_ROUTER_V2) let directions = selectDodoRes[2] directions.push(2) let path = selectUniRes.path routeDetail.data = await this.MakeDODOAndUniRoute(fromTokenAddr, toTokenAddr, fromAmount, minReturnAmount, dodoPairs, directions, path, deadline) return routeDetail } else if (fromToken.source != 'dodo' && toToken.source == 'dodo') { //TODO:toToken 是 quote,可以直接走uni let uniFuncArr: any[] = [] for (let i = 0; i < quoteTokenList.length; i++) { uniFuncArr.push(uniRoute.getSwapCallDetails(fromToken, quoteTokenList[i], 2, fromAmount)) } let uniRes = await Promise.all(uniFuncArr) let isNoPath = true; let funcArr: any[] = [] for (let i = 0; i < uniRes.length; i++) { if (uniRes[i] == null) continue if (toToken.address !== uniRes[i].toToken.address) funcArr.push(dodoRoute.GetDODOSwapCallParams(uniRes[i].toToken, toToken, uniRes[i].outAmount, true)) isNoPath = false } if (isNoPath) { console.log("fromToken not in dodo,directly uni"); return await this.DirectUniRoute(uniRoute, fromToken, toToken, fromAmount, assetTo, deadline, slippage, routeDetail); } let dodoRes = await Promise.all(funcArr) let selectIdx: number = 0 let maxReturnAmount: BigNumber = new BigNumber(0) for (let i = 0; i < dodoRes.length; i++) { let cur = new BigNumber(dodoRes[i][0]) if (cur.gt(maxReturnAmount)) { maxReturnAmount = cur selectIdx = i } } let selectUniRes = uniRes[selectIdx] let selectDodoRes = dodoRes[selectIdx] let resAmount = parseFloat(formatFixed(selectDodoRes[0], toToken.decimals)) let resPricePerToToken = parseFloat(formatFixed(fromAmount, fromToken.decimals)) / resAmount routeDetail.resPricePerToToken = resPricePerToToken routeDetail.resPricePerFromToken = 1 / resPricePerToToken; routeDetail.resAmount = resAmount let uniOriginPrice = new BigNumber(fromAmount).div(new BigNumber(selectUniRes.outAmount).multipliedBy(1 - selectUniRes.priceImpact / 100)).multipliedBy(10 ** (quoteTokenList[selectIdx]['decimals'] - fromToken.decimals)).toNumber() let dodoOriginPrice = selectDodoRes[6] let originPrice = uniOriginPrice * dodoOriginPrice //TODO: priceImpact计算有待商榷 let _priceImpact = 1 - 1 / new BigNumber(originPrice).multipliedBy(resPricePerToToken).toNumber() routeDetail.priceImpact = _priceImpact < 0 ? 0 : _priceImpact let minReturnAmount = new BigNumber(selectDodoRes[0]).multipliedBy(1 - slippage / 100).toFixed(0, BigNumber.ROUND_DOWN) let dodoPairs = selectDodoRes[1] dodoPairs.unshift(this.UNI_ROUTER_V2) let directions = selectDodoRes[2] directions.unshift(2) let uniPath = selectUniRes.path routeDetail.data = await this.MakeDODOAndUniRoute(fromTokenAddr, toTokenAddr, fromAmount, minReturnAmount, dodoPairs, directions, uniPath, deadline) return routeDetail } else if (fromToken.source != 'dodo' && toToken.source != 'dodo') { return await this.DirectUniRoute(uniRoute, fromToken, toToken, fromAmount, assetTo, deadline, slippage, routeDetail); } else { return routeDetail } } catch (error) { console.log("BackUp route calc error"); routeDetail.resAmount = 0 return routeDetail } } private async MakeDODOAndUniRoute( fromTokenAddr: string, toTokenAddr: string, fromAmount: string, minReturnAmount: string, mixPairs: string[], directions: number[], path: string[], deadline: number ): Promise { const web3 = new Web3(new Web3.providers.HttpProvider(this.WEB3_URL)); const dodoSwapInstance = new web3.eth.Contract(DODO_PROXY_ABI['default'], this.DODO_PROXY) if (minReturnAmount == '0' || directions.length == 0) return ""; let data = await dodoSwapInstance.methods.mixSwapV1( fromTokenAddr, toTokenAddr, fromAmount, minReturnAmount, mixPairs, directions, path, deadline ).encodeABI() return data; } private async DirectUniRoute( uniRoute: UniRoute, fromToken: { decimals: number; symbol: string; address: string; source: string; name?: string; }, toToken: { symbol: string; address: string; name: string; decimals: number; source: string }, fromAmount: string, assetTo: string, deadline: number = 0, slippage: number = 0, routeDetail: { resAmount: number, resPricePerToToken: number, resPricePerFromToken: number, priceImpact: number, useSource: string, targetDecimals: number, targetApproveAddr: string, to?: string, data?: string, resCostGas?: number } ) { let uniRes = await uniRoute.getSwapCallDetails(fromToken, toToken, 3, fromAmount) if (uniRes == null) return routeDetail let resAmount = parseFloat(formatFixed(uniRes.outAmount, toToken.decimals)) let resPricePerToToken = parseFloat(formatFixed(fromAmount, fromToken.decimals)) / resAmount routeDetail.resPricePerToToken = resPricePerToToken routeDetail.resPricePerFromToken = 1 / resPricePerToToken; routeDetail.resAmount = resAmount routeDetail.priceImpact = uniRes.priceImpact / 100 let minReturnAmount = new BigNumber(uniRes.outAmount).multipliedBy(1 - slippage / 100).toFixed(0, BigNumber.ROUND_DOWN) let mixPairs = [] mixPairs.push(this.UNI_ROUTER_V2) let directions = [] directions.push(2) let path = uniRes.path routeDetail.data = await this.MakeDODOAndUniRoute(fromToken.address, toToken.address, fromAmount, minReturnAmount, mixPairs, directions, path, deadline) return routeDetail } }