const ROUTER_ABI = [ { inputs: [ { internalType: "address", name: "tokenA", type: "address" }, { internalType: "address", name: "tokenB", type: "address" }, { internalType: "bool", name: "stable", type: "bool" }, { internalType: "address", name: "_factory", type: "address" }, ], name: "poolFor", outputs: [{ internalType: "address", name: "pool", type: "address" }], stateMutability: "view", type: "function", }, { inputs: [ { internalType: "uint256", name: "amountIn", type: "uint256" }, { internalType: "uint256", name: "amountOutMin", type: "uint256" }, { components: [ { internalType: "address", name: "from", type: "address" }, { internalType: "address", name: "to", type: "address" }, { internalType: "bool", name: "stable", type: "bool" }, { internalType: "address", name: "factory", type: "address" }, ], internalType: "struct IRouter.Route[]", name: "routes", type: "tuple[]", }, { internalType: "address", name: "to", type: "address" }, { internalType: "uint256", name: "deadline", type: "uint256" }, ], name: "swapExactTokensForTokens", outputs: [ { internalType: "uint256[]", name: "amounts", type: "uint256[]" }, ], stateMutability: "nonpayable", type: "function", }, { inputs: [ { internalType: "uint256", name: "amountOutMin", type: "uint256" }, { components: [ { internalType: "address", name: "from", type: "address" }, { internalType: "address", name: "to", type: "address" }, { internalType: "bool", name: "stable", type: "bool" }, { internalType: "address", name: "factory", type: "address" }, ], internalType: "struct IRouter.Route[]", name: "routes", type: "tuple[]", }, { internalType: "address", name: "to", type: "address" }, { internalType: "uint256", name: "deadline", type: "uint256" }, ], name: "swapExactETHForTokens", outputs: [ { internalType: "uint256[]", name: "amounts", type: "uint256[]" }, ], stateMutability: "payable", type: "function", }, { inputs: [ { internalType: "uint256", name: "amountIn", type: "uint256" }, { internalType: "uint256", name: "amountOutMin", type: "uint256" }, { components: [ { internalType: "address", name: "from", type: "address" }, { internalType: "address", name: "to", type: "address" }, { internalType: "bool", name: "stable", type: "bool" }, { internalType: "address", name: "factory", type: "address" }, ], internalType: "struct IRouter.Route[]", name: "routes", type: "tuple[]", }, { internalType: "address", name: "to", type: "address" }, { internalType: "uint256", name: "deadline", type: "uint256" }, ], name: "swapExactTokensForETH", outputs: [ { internalType: "uint256[]", name: "amounts", type: "uint256[]" }, ], stateMutability: "nonpayable", type: "function", }, ]; const POOL_ABI = [ { inputs: [ { internalType: "uint256", name: "amountIn", type: "uint256" }, { internalType: "address", name: "tokenIn", type: "address" }, ], name: "getAmountOut", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, { constant: true, inputs: [], name: "getReserves", outputs: [ { internalType: "uint112", name: "_reserve0", type: "uint112" }, { internalType: "uint112", name: "_reserve1", type: "uint112" }, { internalType: "uint32", name: "_blockTimestampLast", type: "uint32", }, ], payable: false, stateMutability: "view", type: "function", }, ]; const { updater, routerAddress, factoryAddress, wethAddress, inputCurrency, outputCurrency, inputCurrencyAmount, onLoad, slippage, account, } = props; useEffect(() => { if (!updater) return; if ( (!inputCurrency.address && !inputCurrency.isNative) || (!outputCurrency.address && !outputCurrency.isNative) || !inputCurrencyAmount ) { return; } const wrapType = inputCurrency.isNative && outputCurrency.address === wethAddress ? 1 : inputCurrency.address === wethAddress && outputCurrency.isNative ? 2 : 0; if (wrapType) { onLoad({ outputCurrencyAmount: inputCurrencyAmount, noPair: false, }); return; } const amount = ethers.utils.parseUnits( Big(inputCurrencyAmount || 0).toFixed(inputCurrency.decimals), inputCurrency.decimals ); const path = [ inputCurrency.isNative ? wethAddress : inputCurrency.address, outputCurrency.isNative ? wethAddress : outputCurrency.address, ]; const RouterContract = new ethers.Contract( routerAddress, ROUTER_ABI, Ethers.provider().getSigner() ); const getPoolAddress = () => { RouterContract.poolFor(path[0], path[1], false, factoryAddress) .then((res) => { if (res) { getAmountOut(res); } else { onLoad({ noPair: true, outputCurrencyAmount: "", }); } }) .catch((err) => { onLoad({ noPair: true, outputCurrencyAmount: "", }); }); }; const getAmountOut = (poolAddress) => { const PoolContract = new ethers.Contract( poolAddress, POOL_ABI, Ethers.provider().getSigner() ); PoolContract.getAmountOut(amount, path[0]) .then((res) => { const _amount = Big( ethers.utils.formatUnits(res, outputCurrency.decimals) ); if (_amount.gt(0)) { getReverse({ amountoutDesimals: _amount.toString(), amountOut: res, poolAddress: poolAddress, }); } else { onLoad({ noPair: true, outputCurrencyAmount: "", }); } }) .catch((err) => { onLoad({ noPair: true, outputCurrencyAmount: "", }); }); }; const getReverse = ({ amountOut, amountoutDesimals, poolAddress }) => { const PoolContract = new ethers.Contract( poolAddress, POOL_ABI, Ethers.provider().getSigner() ); PoolContract.getReserves() .then((res) => { const isReverse = Number(path[0]) > Number(path[1]); const token0 = Big( ethers.utils.formatUnits( res[0], isReverse ? outputCurrency.decimals : inputCurrency.decimals ) ); const token1 = Big( ethers.utils.formatUnits( res[1], isReverse ? inputCurrency.decimals : outputCurrency.decimals ) ); const poolPrice = token1.div(token0); const amountoutPrice = isReverse ? Big(inputCurrencyAmount).div(amountoutDesimals) : Big(amountoutDesimals).div(inputCurrencyAmount); const priceImpact = poolPrice .minus(amountoutPrice) .div(poolPrice) .mul(100) .toString(); getTransaction({ priceImpact, amountoutDesimals, amountOut, }); }) .catch((err) => { getTransaction({ amountoutDesimals, amountOut, }); }); }; const getTransaction = ({ amountOut, amountoutDesimals, priceImpact }) => { let method = ""; const deadline = Math.ceil(Date.now() / 1000) + 60; const _amountOut = Big(amountOut) .mul(1 - (slippage || 0.05)) .toFixed(0); const options = {}; const params = [ _amountOut, [ { from: path[0], to: path[1], factory: factoryAddress, }, ], account, deadline, ]; if (inputCurrency.isNative) { method = "swapExactETHForTokens"; options.value = amount; } else if (outputCurrency.isNative) { method = "swapExactTokensForETH"; params.unshift(amount); } else { method = "swapExactTokensForTokens"; params.unshift(amount); } const returnData = { outputCurrencyAmount: Big(amountoutDesimals).gt(0.01) ? Big(amountoutDesimals).toPrecision(10) : Big(amountoutDesimals).toFixed(10), priceImpact, }; const getTx = (_gas, gasPrice) => { RouterContract.populateTransaction[method](...params, { ...options, gasLimit: _gas ? _gas : 4000000, gasPrice, }) .then((res) => { onLoad({ ...returnData, noPair: false, gas: _gas, unsignedTx: res, }); }) .catch((err) => { onLoad({ ...returnData, noPair: false, gas: _gas, }); }); }; const getGasPrice = (_gas) => { Ethers.provider() .getGasPrice() .then((gasPrice) => { getTx(_gas, gasPrice); }) .catch((err) => { getTx(_gas, null); }); }; const estimateGas = () => { RouterContract.estimateGas[method](...params, options) .then((_gas) => { getGasPrice(_gas); }) .catch((err) => { getGasPrice(null); }); }; estimateGas(); }; getPoolAddress(); }, [updater]);