simple-sushiswap-sdk
Version:
Simple easy to understand SDK for sushiswap
468 lines (467 loc) • 23.6 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SushiswapRouterFactory = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const ethereum_multicall_1 = require("ethereum-multicall");
const contract_context_1 = require("../../common/contract-context");
const error_codes_1 = require("../../common/errors/error-codes");
const sushiswap_error_1 = require("../../common/errors/sushiswap-error");
const comp_1 = require("../../common/tokens/comp");
const dai_1 = require("../../common/tokens/dai");
const usdc_1 = require("../../common/tokens/usdc");
const usdt_1 = require("../../common/tokens/usdt");
const weth_1 = require("../../common/tokens/weth");
const format_ether_1 = require("../../common/utils/format-ether");
const hexlify_1 = require("../../common/utils/hexlify");
const only_unique_1 = require("../../common/utils/only-unique");
const parse_ether_1 = require("../../common/utils/parse-ether");
const trade_path_1 = require("../../common/utils/trade-path");
const chain_id_1 = require("../../enums/chain-id");
const trade_path_2 = require("../../enums/trade-path");
const router_direction_1 = require("./enums/router-direction");
class SushiswapRouterFactory {
constructor(_fromToken, _toToken, _disableMultihops, _ethersProvider) {
this._fromToken = _fromToken;
this._toToken = _toToken;
this._disableMultihops = _disableMultihops;
this._ethersProvider = _ethersProvider;
this._multicall = new ethereum_multicall_1.Multicall({
ethersProvider: this._ethersProvider.provider,
});
}
/**
* Get all possible routes will only go up to 4 due to gas increase the more routes
* you go.
*/
getAllPossibleRoutes() {
return __awaiter(this, void 0, void 0, function* () {
let findPairs = [];
if (!this._disableMultihops) {
findPairs = [
this.mainCurrenciesPairsForFromToken,
this.mainCurrenciesPairsForToToken,
this.mainCurrenciesPairsForUSDT,
this.mainCurrenciesPairsForCOMP,
this.mainCurrenciesPairsForDAI,
this.mainCurrenciesPairsForUSDC,
this.mainCurrenciesPairsForWETH,
[[this._fromToken, this._toToken]],
];
}
else {
// multihops turned off so only go direct
findPairs = [[[this._fromToken, this._toToken]]];
}
const contractCallContext = {
reference: 'sushiswap-pairs',
contractAddress: contract_context_1.ContractContext.pairAddress,
abi: contract_context_1.ContractContext.pairAbi,
calls: [],
};
for (let pairs = 0; pairs < findPairs.length; pairs++) {
for (let tokenPairs = 0; tokenPairs < findPairs[pairs].length; tokenPairs++) {
const fromToken = findPairs[pairs][tokenPairs][0];
const toToken = findPairs[pairs][tokenPairs][1];
contractCallContext.calls.push({
reference: `${fromToken.contractAddress}-${toToken.contractAddress}-${fromToken.symbol}/${toToken.symbol}`,
methodName: 'getPair',
methodParameters: [
fromToken.contractAddress,
toToken.contractAddress,
],
});
}
}
const contractCallResults = yield this._multicall.call(contractCallContext);
const results = contractCallResults.results[contractCallContext.reference];
const availablePairs = results.callsReturnContext.filter((c) => c.returnValues[0] !== '0x0000000000000000000000000000000000000000');
const fromTokenRoutes = {
token: this._fromToken,
pairs: {
fromTokenPairs: this.getTokenAvailablePairs(this._fromToken, availablePairs, router_direction_1.RouterDirection.from),
},
};
const toTokenRoutes = {
token: this._toToken,
pairs: {
toTokenPairs: this.getTokenAvailablePairs(this._toToken, availablePairs, router_direction_1.RouterDirection.to),
},
};
const allMainRoutes = [];
for (let i = 0; i < this.allMainTokens.length; i++) {
const fromTokenPairs = this.getTokenAvailablePairs(this.allMainTokens[i], availablePairs, router_direction_1.RouterDirection.from);
const toTokenPairs = this.getTokenAvailablePairs(this.allMainTokens[i], availablePairs, router_direction_1.RouterDirection.to);
allMainRoutes.push({
token: this.allMainTokens[i],
pairs: { fromTokenPairs, toTokenPairs },
});
}
return this.workOutAllPossibleRoutes(fromTokenRoutes, toTokenRoutes, allMainRoutes);
});
}
getAllPossibleRoutesWithQuotes(amountToTrade) {
return __awaiter(this, void 0, void 0, function* () {
const tradeAmount = this.formatAmountToTrade(amountToTrade);
const routes = yield this.getAllPossibleRoutes();
const contractCallContext = {
reference: 'sushiswap-route-quotes',
contractAddress: contract_context_1.ContractContext.routerAddress,
abi: contract_context_1.ContractContext.routerAbi,
calls: [],
context: routes,
};
for (let i = 0; i < routes.length; i++) {
const routeCombo = routes[i];
contractCallContext.calls.push({
reference: `route${i}`,
methodName: 'getAmountsOut',
methodParameters: [
tradeAmount,
routeCombo.map((c) => {
return c.contractAddress;
}),
],
});
}
const contractCallResults = yield this._multicall.call(contractCallContext);
const results = contractCallResults.results[contractCallContext.reference];
return this.buildRouteQuotesFromResults(results);
});
}
/**
* Finds the best route
* @param amountToTrade The amount they want to trade
*/
findBestRoute(amountToTrade) {
return __awaiter(this, void 0, void 0, function* () {
const allRoutes = yield this.getAllPossibleRoutesWithQuotes(amountToTrade);
if (allRoutes.length === 0) {
throw new sushiswap_error_1.SushiswapError(`No routes found for ${this._fromToken.contractAddress} > ${this._toToken.contractAddress}`, error_codes_1.ErrorCodes.noRoutesFound);
}
return {
bestRouteQuote: allRoutes[0],
triedRoutesQuote: allRoutes.map((route) => {
return {
expectedConvertQuote: route.expectedConvertQuote,
routePathArrayTokenMap: route.routePathArrayTokenMap,
routeText: route.routeText,
routePathArray: route.routePathArray,
};
}),
};
});
}
/**
* Works out every possible route it can take
* @param fromTokenRoutes The from token routes
* @param toTokenRoutes The to token routes
* @param allMainRoutes All the main routes
*/
workOutAllPossibleRoutes(fromTokenRoutes, toTokenRoutes, allMainRoutes) {
const jointCompatibleRoutes = toTokenRoutes.pairs.toTokenPairs.filter((t) => fromTokenRoutes.pairs.fromTokenPairs.find((f) => f.contractAddress === t.contractAddress));
const routes = [];
if (fromTokenRoutes.pairs.fromTokenPairs.find((t) => t.contractAddress === toTokenRoutes.token.contractAddress)) {
routes.push([fromTokenRoutes.token, toTokenRoutes.token]);
}
for (let i = 0; i < allMainRoutes.length; i++) {
const tokenRoute = allMainRoutes[i];
if (jointCompatibleRoutes.find((c) => c.contractAddress === tokenRoute.token.contractAddress)) {
routes.push([
fromTokenRoutes.token,
tokenRoute.token,
toTokenRoutes.token,
]);
for (let f = 0; f < fromTokenRoutes.pairs.fromTokenPairs.length; f++) {
const fromSupportedToken = fromTokenRoutes.pairs.fromTokenPairs[f];
if (tokenRoute.pairs.toTokenPairs.find((pair) => pair.contractAddress === fromSupportedToken.contractAddress)) {
const workedOutFromRoute = [
fromTokenRoutes.token,
fromSupportedToken,
tokenRoute.token,
toTokenRoutes.token,
];
if (workedOutFromRoute.filter(only_unique_1.onlyUnique).length ===
workedOutFromRoute.length) {
routes.push(workedOutFromRoute);
}
}
}
for (let f = 0; f < toTokenRoutes.pairs.toTokenPairs.length; f++) {
const toSupportedToken = toTokenRoutes.pairs.toTokenPairs[f];
if (tokenRoute.pairs.fromTokenPairs.find((pair) => pair.contractAddress === toSupportedToken.contractAddress)) {
const workedOutToRoute = [
fromTokenRoutes.token,
tokenRoute.token,
toSupportedToken,
toTokenRoutes.token,
];
if (workedOutToRoute.filter(only_unique_1.onlyUnique).length ===
workedOutToRoute.length) {
routes.push(workedOutToRoute);
}
}
}
}
}
return routes;
}
getTokenAvailablePairs(token, allAvailablePairs, direction) {
switch (direction) {
case router_direction_1.RouterDirection.from:
return this.getFromRouterDirectionAvailablePairs(token, allAvailablePairs);
case router_direction_1.RouterDirection.to:
return this.getToRouterDirectionAvailablePairs(token, allAvailablePairs);
}
}
getFromRouterDirectionAvailablePairs(token, allAvailablePairs) {
const fromRouterDirection = allAvailablePairs.filter((c) => c.reference.split('-')[0] === token.contractAddress);
const tokens = [];
for (let index = 0; index < fromRouterDirection.length; index++) {
const context = fromRouterDirection[index];
tokens.push(this.allTokens.find((t) => t.contractAddress === context.reference.split('-')[1]));
}
return tokens;
}
getToRouterDirectionAvailablePairs(token, allAvailablePairs) {
const toRouterDirection = allAvailablePairs.filter((c) => c.reference.split('-')[1] === token.contractAddress);
const tokens = [];
for (let index = 0; index < toRouterDirection.length; index++) {
const context = toRouterDirection[index];
tokens.push(this.allTokens.find((t) => t.contractAddress === context.reference.split('-')[0]));
}
return tokens;
}
/**
* Build up route quotes from results
* @param contractCallReturnContext The contract call return context
*/
buildRouteQuotesFromResults(contractCallReturnContext) {
const tradePath = this.tradePath();
const result = [];
if (contractCallReturnContext) {
for (let i = 0; i < contractCallReturnContext.callsReturnContext.length; i++) {
const callReturnContext = contractCallReturnContext.callsReturnContext[i];
switch (tradePath) {
case trade_path_2.TradePath.ethToErc20:
result.push(this.buildRouteQuoteForEthToErc20(callReturnContext));
break;
case trade_path_2.TradePath.erc20ToEth:
result.push(this.buildRouteQuoteForErc20ToEth(callReturnContext));
break;
case trade_path_2.TradePath.erc20ToErc20:
result.push(this.buildRouteQuoteForErc20ToErc20(callReturnContext));
break;
default:
throw new sushiswap_error_1.SushiswapError(`${tradePath} not found`, error_codes_1.ErrorCodes.tradePathIsNotSupported);
}
}
return result.sort((a, b) => {
if (new bignumber_js_1.default(a.expectedConvertQuote).isGreaterThan(b.expectedConvertQuote)) {
return -1;
}
return new bignumber_js_1.default(a.expectedConvertQuote).isLessThan(b.expectedConvertQuote)
? 1
: 0;
});
}
return result;
}
/**
* Build up the route quote for erc20 > erc20
* @param callReturnContext The call return context
*/
buildRouteQuoteForErc20ToErc20(callReturnContext) {
return this.buildRouteQuoteForEthToErc20(callReturnContext);
}
/**
* Build up route quote for eth > erc20
* @param callReturnContext The call return context
*/
buildRouteQuoteForEthToErc20(callReturnContext) {
const convertQuoteUnformatted = new bignumber_js_1.default(callReturnContext.returnValues[callReturnContext.returnValues.length - 1].hex);
return {
expectedConvertQuote: convertQuoteUnformatted
.shiftedBy(this._toToken.decimals * -1)
.toFixed(this._toToken.decimals),
routePathArrayTokenMap: callReturnContext.methodParameters[1].map((c) => {
return this.allTokens.find((t) => t.contractAddress === c);
}),
routeText: callReturnContext.methodParameters[1]
.map((c) => {
return this.allTokens.find((t) => t.contractAddress === c).symbol;
})
.join(' > '),
// route array is always in the 1 index of the method parameters
routePathArray: callReturnContext.methodParameters[1],
};
}
/**
* Build up the route quote for erc20 > eth
* @param callReturnContext The call return context
*/
buildRouteQuoteForErc20ToEth(callReturnContext) {
const convertQuoteUnformatted = new bignumber_js_1.default(callReturnContext.returnValues[callReturnContext.returnValues.length - 1].hex);
return {
expectedConvertQuote: new bignumber_js_1.default(format_ether_1.formatEther(convertQuoteUnformatted)).toFixed(this._toToken.decimals),
routePathArrayTokenMap: callReturnContext.methodParameters[1].map((c) => {
return this.allTokens.find((t) => t.contractAddress === c);
}),
routeText: callReturnContext.methodParameters[1]
.map((c) => {
return this.allTokens.find((t) => t.contractAddress === c).symbol;
})
.join(' > '),
// route array is always in the 1 index of the method parameters
routePathArray: callReturnContext.methodParameters[1],
};
}
/**
* Format amount to trade into callable formats
* @param amountToTrade The amount to trade
*/
formatAmountToTrade(amountToTrade) {
switch (this.tradePath()) {
case trade_path_2.TradePath.ethToErc20:
const amountToTradeWei = parse_ether_1.parseEther(amountToTrade);
return hexlify_1.hexlify(amountToTradeWei);
case trade_path_2.TradePath.erc20ToEth:
case trade_path_2.TradePath.erc20ToErc20:
return hexlify_1.hexlify(amountToTrade.shiftedBy(this._fromToken.decimals));
default:
throw new sushiswap_error_1.SushiswapError(`Internal trade path ${this.tradePath()} is not supported`, error_codes_1.ErrorCodes.tradePathIsNotSupported);
}
}
/**
* Get the trade path
*/
tradePath() {
const network = this._ethersProvider.network();
return trade_path_1.getTradePath(network.chainId, this._fromToken, this._toToken);
}
get allTokens() {
return [this._fromToken, this._toToken, ...this.allMainTokens];
}
get allMainTokens() {
if (this._ethersProvider.provider.network.chainId === chain_id_1.ChainId.MAINNET) {
return [
this.USDTTokenForConnectedNetwork,
this.COMPTokenForConnectedNetwork,
this.USDCTokenForConnectedNetwork,
this.DAITokenForConnectedNetwork,
this.WETHTokenForConnectedNetwork,
];
}
return [this.WETHTokenForConnectedNetwork];
}
get mainCurrenciesPairsForFromToken() {
if (this._ethersProvider.provider.network.chainId === chain_id_1.ChainId.MAINNET) {
const pairs = [
[this._fromToken, this.USDTTokenForConnectedNetwork],
[this._fromToken, this.COMPTokenForConnectedNetwork],
[this._fromToken, this.USDCTokenForConnectedNetwork],
[this._fromToken, this.DAITokenForConnectedNetwork],
[this._fromToken, this.WETHTokenForConnectedNetwork],
];
return pairs.filter((t) => t[0].contractAddress !== t[1].contractAddress);
}
const pairs = [[this._fromToken, this.WETHTokenForConnectedNetwork]];
return pairs.filter((t) => t[0].contractAddress !== t[1].contractAddress);
}
get mainCurrenciesPairsForToToken() {
if (this._ethersProvider.provider.network.chainId === chain_id_1.ChainId.MAINNET) {
const pairs = [
[this.USDTTokenForConnectedNetwork, this._toToken],
[this.COMPTokenForConnectedNetwork, this._toToken],
[this.USDCTokenForConnectedNetwork, this._toToken],
[this.DAITokenForConnectedNetwork, this._toToken],
[this.WETHTokenForConnectedNetwork, this._toToken],
];
return pairs.filter((t) => t[0].contractAddress !== t[1].contractAddress);
}
const pairs = [
[this.WETHTokenForConnectedNetwork, this._toToken],
];
return pairs.filter((t) => t[0].contractAddress !== t[1].contractAddress);
}
get mainCurrenciesPairsForUSDT() {
if (this._ethersProvider.provider.network.chainId === chain_id_1.ChainId.MAINNET) {
return [
[this.USDTTokenForConnectedNetwork, this.COMPTokenForConnectedNetwork],
[this.USDTTokenForConnectedNetwork, this.DAITokenForConnectedNetwork],
[this.USDTTokenForConnectedNetwork, this.USDCTokenForConnectedNetwork],
[this.USDTTokenForConnectedNetwork, this.WETHTokenForConnectedNetwork],
];
}
return [];
}
get mainCurrenciesPairsForCOMP() {
if (this._ethersProvider.provider.network.chainId === chain_id_1.ChainId.MAINNET) {
return [
[this.COMPTokenForConnectedNetwork, this.USDTTokenForConnectedNetwork],
[this.COMPTokenForConnectedNetwork, this.DAITokenForConnectedNetwork],
[this.COMPTokenForConnectedNetwork, this.USDCTokenForConnectedNetwork],
[this.COMPTokenForConnectedNetwork, this.WETHTokenForConnectedNetwork],
];
}
return [];
}
get mainCurrenciesPairsForDAI() {
if (this._ethersProvider.provider.network.chainId === chain_id_1.ChainId.MAINNET) {
return [
[this.DAITokenForConnectedNetwork, this.COMPTokenForConnectedNetwork],
[this.DAITokenForConnectedNetwork, this.WETHTokenForConnectedNetwork],
];
}
return [];
}
get mainCurrenciesPairsForUSDC() {
if (this._ethersProvider.provider.network.chainId === chain_id_1.ChainId.MAINNET) {
return [
[this.USDCTokenForConnectedNetwork, this.USDTTokenForConnectedNetwork],
[this.USDCTokenForConnectedNetwork, this.COMPTokenForConnectedNetwork],
[this.USDCTokenForConnectedNetwork, this.DAITokenForConnectedNetwork],
[this.USDCTokenForConnectedNetwork, this.WETHTokenForConnectedNetwork],
];
}
return [];
}
get mainCurrenciesPairsForWETH() {
if (this._ethersProvider.provider.network.chainId === chain_id_1.ChainId.MAINNET) {
return [
[this.WETHTokenForConnectedNetwork, this.USDTTokenForConnectedNetwork],
[this.WETHTokenForConnectedNetwork, this.COMPTokenForConnectedNetwork],
[this.WETHTokenForConnectedNetwork, this.DAITokenForConnectedNetwork],
[this.WETHTokenForConnectedNetwork, this.USDCTokenForConnectedNetwork],
];
}
return [];
}
get USDTTokenForConnectedNetwork() {
return usdt_1.USDT.token(this._ethersProvider.provider.network.chainId);
}
get COMPTokenForConnectedNetwork() {
return comp_1.COMP.token(this._ethersProvider.provider.network.chainId);
}
get DAITokenForConnectedNetwork() {
return dai_1.DAI.token(this._ethersProvider.provider.network.chainId);
}
get USDCTokenForConnectedNetwork() {
return usdc_1.USDC.token(this._ethersProvider.provider.network.chainId);
}
get WETHTokenForConnectedNetwork() {
return weth_1.WETH.token(this._ethersProvider.provider.network.chainId);
}
}
exports.SushiswapRouterFactory = SushiswapRouterFactory;