UNPKG

simple-sushiswap-sdk

Version:
493 lines (492 loc) 24 kB
"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.SushiswapPairFactory = void 0; const bignumber_js_1 = __importDefault(require("bignumber.js")); const rxjs_1 = require("rxjs"); const constants_1 = require("../../common/constants"); 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 hexlify_1 = require("../../common/utils/hexlify"); const parse_ether_1 = require("../../common/utils/parse-ether"); const to_ethers_big_number_1 = require("../../common/utils/to-ethers-big-number"); const trade_path_1 = require("../../common/utils/trade-path"); const trade_path_2 = require("../../enums/trade-path"); const sushiswap_router_contract_factory_1 = require("../router/sushiswap-router-contract.factory"); const sushiswap_router_factory_1 = require("../router/sushiswap-router.factory"); const token_factory_1 = require("../token/token.factory"); const sushiswap_pair_contract_factory_1 = require("./sushiswap-pair-contract.factory"); class SushiswapPairFactory { constructor(_sushiswapPairFactoryContext) { this._sushiswapPairFactoryContext = _sushiswapPairFactoryContext; this.LIQUIDITY_PROVIDER_FEE = 0.003; this._fromTokenFactory = new token_factory_1.TokenFactory(this._sushiswapPairFactoryContext.fromToken.contractAddress, this._sushiswapPairFactoryContext.ethersProvider); this._SushiswapRouterContractFactory = new sushiswap_router_contract_factory_1.SushiswapRouterContractFactory(this._sushiswapPairFactoryContext.ethersProvider); this._sushiswapPairFactory = new sushiswap_pair_contract_factory_1.SushiswapPairContractFactory(this._sushiswapPairFactoryContext.ethersProvider); this._SushiswapRouterFactory = new sushiswap_router_factory_1.SushiswapRouterFactory(this._sushiswapPairFactoryContext.fromToken, this._sushiswapPairFactoryContext.toToken, this._sushiswapPairFactoryContext.settings.disableMultihops, this._sushiswapPairFactoryContext.ethersProvider); this._quoteChanged$ = new rxjs_1.Subject(); } /** * The to token */ get toToken() { return this._sushiswapPairFactoryContext.toToken; } /** * The from token */ get fromToken() { return this._sushiswapPairFactoryContext.fromToken; } /** * Get the contract calls */ get contractCalls() { return this._sushiswapPairFactory; } /** * Execute the trade path * @param amount The amount */ executeTradePath(amount) { return __awaiter(this, void 0, void 0, function* () { switch (this.tradePath()) { case trade_path_2.TradePath.erc20ToEth: return yield this.getTokenTradeAmountErc20ToEth(amount); case trade_path_2.TradePath.ethToErc20: return yield this.getTokenTradeAmountEthToErc20(amount); case trade_path_2.TradePath.erc20ToErc20: return yield this.getTokenTradeAmountErc20ToErc20(amount); default: throw new sushiswap_error_1.SushiswapError(`${this.tradePath()} is not defined`, error_codes_1.ErrorCodes.tradePathIsNotSupported); } }); } /** * Destroy the trade instance watchers + subscriptions */ destroy() { for (let i = 0; i < this._quoteChanged$.observers.length; i++) { this._quoteChanged$.observers[i].complete(); } if (this._quoteChangeTimeout) { clearTimeout(this._quoteChangeTimeout); } } /** * Generate trade - this will return amount but you still need to send the transaction * if you want it to be executed on the blockchain * @amount The amount you want to swap, this is the FROM token amount. */ trade(amount) { return __awaiter(this, void 0, void 0, function* () { this.destroy(); const tradeContext = yield this.executeTradePath(new bignumber_js_1.default(amount)); this.watchTradePrice(tradeContext); return tradeContext; }); } /** * Route getter */ get _routes() { return this._SushiswapRouterFactory; } /** * Find the best route rate out of all the route quotes * @param amountToTrade The amount to trade */ findBestRoute(amountToTrade) { return __awaiter(this, void 0, void 0, function* () { return yield this._routes.findBestRoute(new bignumber_js_1.default(amountToTrade)); }); } /** * Find the best route rate out of all the route quotes * @param amountToTrade The amount to trade */ findAllPossibleRoutesWithQuote(amountToTrade) { return __awaiter(this, void 0, void 0, function* () { return yield this._routes.getAllPossibleRoutesWithQuotes(new bignumber_js_1.default(amountToTrade)); }); } /** * Find all possible routes */ findAllPossibleRoutes() { return __awaiter(this, void 0, void 0, function* () { return yield this._routes.getAllPossibleRoutes(); }); } /** * Has got enough allowance to do the trade * @param amount The amount you want to swap */ hasGotEnoughAllowance(amount) { return __awaiter(this, void 0, void 0, function* () { if (this.tradePath() === trade_path_2.TradePath.ethToErc20) { return true; } const allowance = yield this.allowance(); return this._hasGotEnoughAllowance(amount, allowance); }); } /** * Has got enough allowance to do the trade * @param amount The amount you want to swap */ _hasGotEnoughAllowance(amount, allowance) { if (this.tradePath() === trade_path_2.TradePath.ethToErc20) { return true; } const bigNumberAllowance = new bignumber_js_1.default(allowance).shiftedBy(this.fromToken.decimals * -1); if (new bignumber_js_1.default(amount).isGreaterThan(bigNumberAllowance)) { return false; } return true; } /** * Has got enough balance to do the trade (erc20 check only) * @param amount The amount you want to swap */ hasGotEnoughBalanceErc20(amount, balance) { const bigNumberBalance = new bignumber_js_1.default(balance).shiftedBy(this.fromToken.decimals * -1); if (new bignumber_js_1.default(amount).isGreaterThan(bigNumberBalance)) { return { hasEnough: false, balance: bigNumberBalance.toFixed(), }; } return { hasEnough: true, balance: bigNumberBalance.toFixed(), }; } /** * Has got enough balance to do the trade (eth check only) * @param amount The amount you want to swap */ hasGotEnoughBalanceEth(amount) { return __awaiter(this, void 0, void 0, function* () { const balance = yield this._sushiswapPairFactoryContext.ethersProvider.balanceOf(this._sushiswapPairFactoryContext.ethereumAddress); const bigNumberBalance = new bignumber_js_1.default(balance).shiftedBy(constants_1.Constants.ETH_MAX_DECIMALS * -1); if (new bignumber_js_1.default(amount).isGreaterThan(bigNumberBalance)) { return { hasEnough: false, balance: bigNumberBalance.toFixed(), }; } return { hasEnough: true, balance: bigNumberBalance.toFixed(), }; }); } /** * Get the allowance and balance for the from token (erc20 > blah) only */ getAllowanceAndBalanceOfForFromToken() { return __awaiter(this, void 0, void 0, function* () { return yield this._fromTokenFactory.getAllowanceAndBalanceOf(this._sushiswapPairFactoryContext.ethereumAddress); }); } /** * Get the allowance for the amount which can be moved from the `fromToken` * on the users behalf. Only valid when the `fromToken` is a ERC20 token. */ allowance() { return __awaiter(this, void 0, void 0, function* () { if (this.tradePath() === trade_path_2.TradePath.ethToErc20) { return '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; } const allowance = yield this._fromTokenFactory.allowance(this._sushiswapPairFactoryContext.ethereumAddress); return allowance; }); } /** * Generate the from token approve data max allowance to move the tokens. * This will return the data for you to send as a transaction */ generateApproveMaxAllowanceData() { return __awaiter(this, void 0, void 0, function* () { if (this.tradePath() === trade_path_2.TradePath.ethToErc20) { throw new sushiswap_error_1.SushiswapError('You do not need to generate approve sushiswap allowance when doing eth > erc20', error_codes_1.ErrorCodes.generateApproveMaxAllowanceDataNotAllowed); } const data = this._fromTokenFactory.generateApproveAllowanceData(contract_context_1.ContractContext.routerAddress, '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); return { to: this.fromToken.contractAddress, from: this._sushiswapPairFactoryContext.ethereumAddress, data, value: constants_1.Constants.EMPTY_HEX_STRING, }; }); } /** * Get the token trade amount for erc20 > eth * @param amount The amount */ getTokenTradeAmountErc20ToEth(amount) { return __awaiter(this, void 0, void 0, function* () { return yield this.findBestPriceAndPathErc20ToEth(amount); }); } /** * Gets how much token they will get for their trade minus all fees * @param ethAmount The eth amount */ getTokenTradeAmountEthToErc20(ethAmount) { return __awaiter(this, void 0, void 0, function* () { return yield this.findBestPriceAndPathEthToErc20(ethAmount); }); } /** * Get the token trade amount for erc20 > erc20 * @param amount The amount */ getTokenTradeAmountErc20ToErc20(amount) { return __awaiter(this, void 0, void 0, function* () { return yield this.findBestPriceAndPathErc20ToErc20(amount); }); } /** * finds the best price and path for Erc20ToEth * @param amount the erc20Token amount being sent */ findBestPriceAndPathErc20ToEth(erc20Amount) { return __awaiter(this, void 0, void 0, function* () { const bestRouteQuotes = yield this._routes.findBestRoute(erc20Amount); const bestRouteQuote = bestRouteQuotes.bestRouteQuote; const convertQuoteWithSlippage = new bignumber_js_1.default(bestRouteQuote.expectedConvertQuote).minus(new bignumber_js_1.default(bestRouteQuote.expectedConvertQuote) .times(this._sushiswapPairFactoryContext.settings.slippage) .toFixed(this.fromToken.decimals)); const tradeExpires = this.generateTradeDeadlineUnixTime(); const data = this.generateTradeDataErc20ToEth(erc20Amount, convertQuoteWithSlippage, bestRouteQuote.routePathArray, tradeExpires.toString()); const allowanceAndBalanceOf = yield this.getAllowanceAndBalanceOfForFromToken(); const tradeContext = { baseConvertRequest: erc20Amount.toFixed(), minAmountConvertQuote: convertQuoteWithSlippage.toFixed(this.toToken.decimals), expectedConvertQuote: bestRouteQuote.expectedConvertQuote, liquidityProviderFee: erc20Amount .times(this.LIQUIDITY_PROVIDER_FEE) .toFixed(this.fromToken.decimals), tradeExpires, routePathTokenMap: bestRouteQuote.routePathArrayTokenMap, routeText: bestRouteQuote.routeText, routePath: bestRouteQuote.routePathArray, hasEnoughAllowance: this._hasGotEnoughAllowance(erc20Amount.toFixed(), allowanceAndBalanceOf.allowance), toToken: this.toToken, fromToken: this.fromToken, fromBalance: this.hasGotEnoughBalanceErc20(erc20Amount.toFixed(), allowanceAndBalanceOf.balanceOf), transaction: this.buildUpTransactionErc20(data), allTriedRoutesQuotes: bestRouteQuotes.triedRoutesQuote, quoteChanged$: this._quoteChanged$, destroy: () => this.destroy(), }; return tradeContext; }); } /** * finds the best price and path for Erc20ToErc20 * @param amount the erc20Token amount being sent */ findBestPriceAndPathErc20ToErc20(erc20Amount) { return __awaiter(this, void 0, void 0, function* () { const bestRouteQuotes = yield this._routes.findBestRoute(erc20Amount); const bestRouteQuote = bestRouteQuotes.bestRouteQuote; const convertQuoteWithSlippage = new bignumber_js_1.default(bestRouteQuote.expectedConvertQuote).minus(new bignumber_js_1.default(bestRouteQuote.expectedConvertQuote) .times(this._sushiswapPairFactoryContext.settings.slippage) .toFixed(this.fromToken.decimals)); const tradeExpires = this.generateTradeDeadlineUnixTime(); const data = this.generateTradeDataErc20ToErc20(erc20Amount, convertQuoteWithSlippage, bestRouteQuote.routePathArray, tradeExpires.toString()); const allowanceAndBalanceOf = yield this.getAllowanceAndBalanceOfForFromToken(); const tradeContext = { baseConvertRequest: erc20Amount.toFixed(), minAmountConvertQuote: convertQuoteWithSlippage.toFixed(this.toToken.decimals), expectedConvertQuote: bestRouteQuote.expectedConvertQuote, liquidityProviderFee: erc20Amount .times(this.LIQUIDITY_PROVIDER_FEE) .toFixed(this.fromToken.decimals), tradeExpires, routePathTokenMap: bestRouteQuote.routePathArrayTokenMap, routeText: bestRouteQuote.routeText, routePath: bestRouteQuote.routePathArray, hasEnoughAllowance: this._hasGotEnoughAllowance(erc20Amount.toFixed(), allowanceAndBalanceOf.allowance), toToken: this.toToken, fromToken: this.fromToken, fromBalance: this.hasGotEnoughBalanceErc20(erc20Amount.toFixed(), allowanceAndBalanceOf.balanceOf), transaction: this.buildUpTransactionErc20(data), allTriedRoutesQuotes: bestRouteQuotes.triedRoutesQuote, quoteChanged$: this._quoteChanged$, destroy: () => this.destroy(), }; return tradeContext; }); } /** * Find the best price and route path to take (will round down the slippage) * @param ethAmount The eth amount */ findBestPriceAndPathEthToErc20(ethAmount) { return __awaiter(this, void 0, void 0, function* () { const bestRouteQuotes = yield this._routes.findBestRoute(ethAmount); const bestRouteQuote = bestRouteQuotes.bestRouteQuote; const convertQuoteWithSlippage = new bignumber_js_1.default(bestRouteQuote.expectedConvertQuote).minus(new bignumber_js_1.default(bestRouteQuote.expectedConvertQuote) .times(this._sushiswapPairFactoryContext.settings.slippage) .toFixed(this.toToken.decimals)); const tradeExpires = this.generateTradeDeadlineUnixTime(); const data = this.generateTradeDataEthToErc20(convertQuoteWithSlippage, bestRouteQuote.routePathArray, tradeExpires.toString()); const tradeContext = { baseConvertRequest: ethAmount.toFixed(), minAmountConvertQuote: convertQuoteWithSlippage.toFixed(this.toToken.decimals), expectedConvertQuote: bestRouteQuote.expectedConvertQuote, liquidityProviderFee: ethAmount .times(this.LIQUIDITY_PROVIDER_FEE) .toFixed(this.fromToken.decimals), tradeExpires, routePathTokenMap: bestRouteQuote.routePathArrayTokenMap, routeText: bestRouteQuote.routeText, routePath: bestRouteQuote.routePathArray, hasEnoughAllowance: true, toToken: this.toToken, fromToken: this.fromToken, fromBalance: yield this.hasGotEnoughBalanceEth(ethAmount.toFixed()), transaction: this.buildUpTransactionEth(ethAmount, data), allTriedRoutesQuotes: bestRouteQuotes.triedRoutesQuote, quoteChanged$: this._quoteChanged$, destroy: () => this.destroy(), }; return tradeContext; }); } /** * Generate trade data eth > erc20 * @param tokenAmount The token amount * @param routePath The route path * @param deadline The deadline it expiries unix time */ generateTradeDataEthToErc20(tokenAmount, routePathArray, deadline) { // sushiswap adds extra digits on even if the token is say 8 digits long const convertedMinTokens = tokenAmount .shiftedBy(this.toToken.decimals) .decimalPlaces(0); const hex = hexlify_1.hexlify(convertedMinTokens); return this._SushiswapRouterContractFactory.swapExactETHForTokens(hex, routePathArray, this._sushiswapPairFactoryContext.ethereumAddress, deadline); } /** * Generate trade amount erc20 > eth * @param tokenAmount The token amount * @param ethAmountOutMin The min eth in eth not wei this converts it * @param routePathArray The route path array * @param deadline The deadline it expiries unix time */ generateTradeDataErc20ToEth(tokenAmount, ethAmountOutMin, routePathArray, deadline) { // sushiswap adds extra digits on even if the token is say 8 digits long const amountIn = tokenAmount .shiftedBy(this.fromToken.decimals) .decimalPlaces(0); const ethAmountOutWei = hexlify_1.hexlify(parse_ether_1.parseEther(ethAmountOutMin)); return this._SushiswapRouterContractFactory.swapExactTokensForETH(hexlify_1.hexlify(amountIn), ethAmountOutWei, routePathArray, this._sushiswapPairFactoryContext.ethereumAddress, deadline); } /** * Generate trade amount erc20 > erc20 * @param tokenAmount The token amount * @param tokenAmountOut The min token amount out * @param routePathArray The route path array * @param deadline The deadline it expiries unix time */ generateTradeDataErc20ToErc20(tokenAmount, tokenAmountMin, routePathArray, deadline) { // sushiswap adds extra digits on even if the token is say 8 digits long const amountIn = tokenAmount .shiftedBy(this.fromToken.decimals) .decimalPlaces(0); const amountMin = tokenAmountMin .shiftedBy(this.toToken.decimals) .decimalPlaces(0); return this._SushiswapRouterContractFactory.swapExactTokensForTokens(hexlify_1.hexlify(amountIn), hexlify_1.hexlify(amountMin), routePathArray, this._sushiswapPairFactoryContext.ethereumAddress, deadline); } /** * Build up a transaction for erc20 from * @param data The data */ buildUpTransactionErc20(data) { return { to: contract_context_1.ContractContext.routerAddress, from: this._sushiswapPairFactoryContext.ethereumAddress, data, value: constants_1.Constants.EMPTY_HEX_STRING, }; } /** * Build up a transaction for eth from * @param ethValue The eth value * @param data The data */ buildUpTransactionEth(ethValue, data) { return { to: contract_context_1.ContractContext.routerAddress, from: this._sushiswapPairFactoryContext.ethereumAddress, data, value: to_ethers_big_number_1.toEthersBigNumber(parse_ether_1.parseEther(ethValue)).toHexString(), }; } /** * Get the trade path */ tradePath() { const network = this._sushiswapPairFactoryContext.ethersProvider.network(); return trade_path_1.getTradePath(network.chainId, this.fromToken, this.toToken); } /** * Generates the trade datetime unix time */ generateTradeDeadlineUnixTime() { const now = new Date(); const expiryDate = new Date(now.getTime() + this._sushiswapPairFactoryContext.settings.deadlineMinutes * 60000); return (expiryDate.getTime() / 1e3) | 0; } /** * Watch trade price move automatically emitting the stream if it changes * @param tradeContext The old trade context aka the current one */ watchTradePrice(tradeContext) { return __awaiter(this, void 0, void 0, function* () { this._quoteChangeTimeout = setTimeout(() => __awaiter(this, void 0, void 0, function* () { if (this._quoteChanged$.observers.length > 0) { const trade = yield this.executeTradePath(new bignumber_js_1.default(tradeContext.baseConvertRequest)); if (!new bignumber_js_1.default(trade.expectedConvertQuote).eq(tradeContext.expectedConvertQuote) || trade.routeText !== tradeContext.routeText) { this._quoteChanged$.next(trade); this.watchTradePrice(trade); return; } // it has expired send another one to them if (tradeContext.tradeExpires > this.generateTradeDeadlineUnixTime()) { this._quoteChanged$.next(trade); this.watchTradePrice(trade); return; } this.watchTradePrice(tradeContext); } else { this.watchTradePrice(tradeContext); } // maybe make config??? // query new prices every 10 seconds }), 10000); }); } } exports.SushiswapPairFactory = SushiswapPairFactory;