Repository: hummingbot/hummingbot
Pull request: Refactor/hummingbot uniswap interaction

Problem statement:
Integrate uniswap from gateway-v2 into hummingbot client
@mifeng has already made a branch of hummingbot that has gateway-v2. Now we need to update the uniswap connector so it works with the v2 version. The finished result will be merged into the gateway-v2 branch. This will not be merged into development until it is better tested and gateway-v2 supports a greater number of exchanges.

Hints from issue discussion:
Branch: https://github.com/coinalpha/hummingbot/tree/feat/gateway-v2
We can treat this as done as soon as Hummingbot is able to approve tokens and make a valid trade with the amm-arb strategy with Uniswap v2 on Kovan.

Commit message: delete unnecessary logs

Patch to review:
diff --git a/gateway/conf/gateway-config-example.yml b/gateway/conf/gateway-config-example.yml
index f8e084f16c1..ce89eaadc5f 100644
--- a/gateway/conf/gateway-config-example.yml
+++ b/gateway/conf/gateway-config-example.yml
@@ -26,6 +26,6 @@ ETH_GAS_STATION_REFRESH_TIME: 60
 ETH_MANUAL_GAS_PRICE: 100
 
 # Uniswap
-UNISWAP_ALLOWED_SLIPPAGE: 1.5
+UNISWAP_ALLOWED_SLIPPAGE: '1/100'
 UNISWAP_GAS_LIMIT: 150688
 UNISWAP_TTL: 300
diff --git a/gateway/manual-tests/curl-with-private-key.sh b/gateway/manual-tests/curl-with-private-key.sh
index c3ea053ddfb..062b6df0471 100644
--- a/gateway/manual-tests/curl-with-private-key.sh
+++ b/gateway/manual-tests/curl-with-private-key.sh
@@ -10,3 +10,12 @@ curl -X GET -k --key $GATEWAY_KEY --cert $GATEWAY_CERT https://localhost:5000/et
 
 # get balances for your private key
 curl -X POST -k --key $GATEWAY_KEY --cert $GATEWAY_CERT -H "Content-Type: application/json" -d "{\"privateKey\":\"$ETH_PRIVATE_KEY\",\"tokenSymbols\":[\"ETH\",\"WETH\",\"DAI\"]}" https://localhost:5000/eth/balances
+
+curl -X POST -k --key $GATEWAY_KEY --cert $GATEWAY_CERT -H "Content-Type: application/json" -d "{\"privateKey\":\"$ETH_PRIVATE_KEY\",\"spender\":\"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D\",\"tokenSymbols\":[\"DAI\",\"WETH\"]}" https://localhost:5000/eth/allowances
+
+curl -X POST -k --key $GATEWAY_KEY --cert $GATEWAY_CERT -H "Content-Type: application/json" -d "{\"privateKey\":\"$ETH_PRIVATE_KEY\",\"spender\":\"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D\",\"tokenSymbols\":[\"DAI\",\"WETH\"]}" https://localhost:5000/eth/allowances
+
+curl -X POST -k --key $GATEWAY_KEY --cert $GATEWAY_CERT  -H "Content-Type: application/json" -d "{\"privateKey\":\"$ETH_PRIVATE_KEY\",\"amount\":\"0\",\"spender\":\"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D\",\"token\":\"DAI\"}" https://localhost:5000/eth/approve
+
+
+curl -X POST -k --key $GATEWAY_KEY --cert $GATEWAY_CERT  -H "Content-Type: application/json" -d "{\"privateKey\":\"$ETH_PRIVATE_KEY\",\"spender\":\"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D\",\"token\":\"DAI\"}" localhost:5000/eth/approve
diff --git a/gateway/manual-tests/curl.sh b/gateway/manual-tests/curl.sh
index 4d3dc20d09f..a81258d0690 100644
--- a/gateway/manual-tests/curl.sh
+++ b/gateway/manual-tests/curl.sh
@@ -16,6 +16,12 @@ curl -X POST -H "Content-Type: application/json" -d "{\"privateKey\":\"$ETH_PRIV
 
 curl -X POST -H "Content-Type: application/json" -d "{\"privateKey\":\"$ETH_PRIVATE_KEY\",\"spender\":\"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D\",\"token\":\"DAI\"}" localhost:5000/eth/approve
 
+# check allowances
+
+curl -X POST -H "Content-Type: application/json" -d "{\"privateKey\":\"$ETH_PRIVATE_KEY\",\"spender\":\"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D\",\"tokenSymbols\":[\"DAI\"]}" localhost:5000/eth/allowances
+
+curl -X POST -H "Content-Type: application/json" -d "{\"privateKey\":\"$ETH_PRIVATE_KEY\",\"spender\":\"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D\",\"tokenSymbols\":[\"DAI\",\"WETH\"]}" localhost:5000/eth/allowances
+
 # check status of transaction 0x6d068067a5e5a0f08c6395b31938893d1cdad81f54a54456221ecd8c1941294d
 
 curl -X POST -H "Content-Type: application/json" -d "{\"txHash\":\"0x6d068067a5e5a0f08c6395b31938893d1cdad81f54a54456221ecd8c1941294d\"}" localhost:5000/eth/poll
@@ -23,3 +29,20 @@ curl -X POST -H "Content-Type: application/json" -d "{\"txHash\":\"0x6d068067a5e
 # update config
 
 curl -X POST -H "Content-Type: application/json" -d "{\"LOG_TO_STDOUT\":true}" localhost:5000/config/update
+
+# uniswap
+
+curl -X GET localhost:5000/eth/uniswap
+
+
+curl -X POST -H "Content-Type: application/json" -d "{\"quote\":\"DAI\",\"base\":\"WETH\",\"amount\":\"1000000000000000000\",\"side\":\"BUY\"}" localhost:5000/eth/uniswap/price
+
+curl -X POST -H "Content-Type: application/json" -d "{\"quote\":\"DAI\",\"base\":\"WETH\",\"amount\":\"1000\",\"side\":\"BUY\"}" localhost:5000/eth/uniswap/price
+
+curl -X POST -H "Content-Type: application/json" -d "{\"quote\":\"DAI\",\"base\":\"WETH\",\"amount\":\"1\",\"side\":\"BUY\",\"privateKey\":\"$ETH_PRIVATE_KEY\"}" localhost:5000/eth/uniswap/trade
+
+# run transaction then poll it
+
+TX_HASH=$(curl -X POST -H "Content-Type: application/json" -d "{\"quote\":\"DAI\",\"base\":\"WETH\",\"amount\":\"1\",\"side\":\"BUY\",\"privateKey\":\"$ETH_PRIVATE_KEY\"}" localhost:5000/eth/uniswap/trade | jq '.txHash')
+
+curl -X POST -H "Content-Type: application/json" -d "{\"txHash\":$TX_HASH}" localhost:5000/eth/poll
diff --git a/gateway/src/chains/ethereum/ethereum.routes.ts b/gateway/src/chains/ethereum/ethereum.routes.ts
index 0730e56e18d..1c2db2c2a1e 100644
--- a/gateway/src/chains/ethereum/ethereum.routes.ts
+++ b/gateway/src/chains/ethereum/ethereum.routes.ts
@@ -1,15 +1,17 @@
 /* eslint-disable @typescript-eslint/ban-types */
-import { constants, Wallet } from 'ethers';
+import { constants, BigNumber, Wallet } from 'ethers';
 import { NextFunction, Router, Request, Response } from 'express';
 import { Ethereum } from './ethereum';
 import { EthereumConfig } from './ethereum.config';
 import { ConfigManager } from '../../services/config-manager';
 import { Token } from '../../services/ethereum-base';
-import { tokenValueToString } from '../../services/base';
 import { verifyEthereumIsAvailable } from './ethereum-middlewares';
 import { HttpException, asyncHandler } from '../../services/error-handler';
 import { latency, bigNumberWithDecimalToStr } from '../../services/base';
 import ethers from 'ethers';
+import { tokenValueToString } from '../../services/base';
+import { UniswapConfig } from './uniswap/uniswap.config';
+
 export namespace EthereumRoutes {
   export const router = Router();
   const ethereum = Ethereum.getInstance();
@@ -38,6 +40,78 @@ export namespace EthereumRoutes {
     })
   );
 
+  interface EthereumAllowancesRequest {
+    privateKey: string; // the users private Ethereum key
+    spender: string; // the spender address for whom approvals are checked
+    tokenSymbols: string[]; // a list of token symbol
+  }
+
+  interface EthereumAllowancesResponse {
+    network: string;
+    timestamp: number;
+    latency: number;
+    spender: string;
+    approvals: Record<string, string>;
+  }
+
+  router.post(
+    '/allowances',
+    asyncHandler(
+      async (
+        req: Request<{}, {}, EthereumAllowancesRequest>,
+        res: Response<EthereumAllowancesResponse | string, {}>
+      ) => {
+        const initTime = Date.now();
+        const wallet = ethereum.getWallet(req.body.privateKey);
+
+        const tokens: Record<string, Token> = {};
+
+        for (var i = 0; i < req.body.tokenSymbols.length; i++) {
+          const symbol = req.body.tokenSymbols[i];
+          const token = ethereum.getTokenBySymbol(symbol);
+          if (!token) {
+            continue;
+          }
+
+          tokens[symbol] = token;
+        }
+
+        let spender: string;
+        if (req.body.spender === 'uniswap') {
+          if (ConfigManager.config.ETHEREUM_CHAIN === 'mainnet') {
+            spender = UniswapConfig.config.mainnet.uniswapV2RouterAddress;
+          } else {
+            spender = UniswapConfig.config.kovan.uniswapV2RouterAddress;
+          }
+        } else {
+          spender = req.body.spender;
+        }
+
+        let approvals: Record<string, string> = {};
+        await Promise.all(
+          Object.keys(tokens).map(async (symbol) => {
+            approvals[symbol] = tokenValueToString(
+              await ethereum.getERC20Allowance(
+                wallet,
+                spender,
+                tokens[symbol].address,
+                tokens[symbol].decimals
+              )
+            );
+          })
+        );
+
+        res.status(200).json({
+          network: ConfigManager.config.ETHEREUM_CHAIN,
+          timestamp: initTime,
+          latency: latency(initTime, Date.now()),
+          spender: spender,
+          approvals: approvals,
+        });
+      }
+    )
+  );
+
   interface EthereumBalanceRequest {
     privateKey: string; // the users private Ethereum key
     tokenSymbols: string[]; // a list of token symbol
@@ -131,7 +205,16 @@ export namespace EthereumRoutes {
         res: Response<EthereumApproveResponse | string, {}>
       ) => {
         const initTime = Date.now();
-        const spender: string = req.body.spender;
+        let spender: string;
+        if (req.body.spender === 'uniswap') {
+          if (ConfigManager.config.ETHEREUM_CHAIN === 'mainnet') {
+            spender = UniswapConfig.config.mainnet.uniswapV2RouterAddress;
+          } else {
+            spender = UniswapConfig.config.kovan.uniswapV2RouterAddress;
+          }
+        } else {
+          spender = req.body.spender;
+        }
 
         let wallet: Wallet;
         try {
@@ -151,7 +234,7 @@ export namespace EthereumRoutes {
 
         let amount = constants.MaxUint256;
         if (req.body.amount) {
-          amount = ethers.utils.parseUnits(req.body.amount, token.decimals);
+            amount = BigNumber.from(req.body.amount);
         }
         // call approve function
         let approval;
@@ -182,6 +265,30 @@ export namespace EthereumRoutes {
   interface EthereumPollRequest {
     txHash: string;
   }
+  // TransactionReceipt from ethers uses BigNumber which is not easy to interpret directly from JSON.
+  // Transform those BigNumbers to string and pass the rest of the data without changes.
+  export interface EthereumTransactionRecept
+    extends Omit<
+      ethers.providers.TransactionReceipt,
+      'gasUsed' | 'cumulativeGasUsed'
+    > {
+    gasUsed: string;
+    cumulativeGasUsed: string;
+  }
+
+  const toEthereumTransactionReceipt = (
+    receipt: ethers.providers.TransactionReceipt | null
+  ): EthereumTransactionRecept | null => {
+    if (receipt) {
+      return {
+        ...receipt,
+        gasUsed: receipt.gasUsed.toString(),
+        cumulativeGasUsed: receipt.cumulativeGasUsed.toString(),
+      };
+    }
+
+    return null;
+  };
 
   interface EthereumPollResponse {
     network: string;
@@ -189,7 +296,7 @@ export namespace EthereumRoutes {
     latency: number;
     txHash: string;
     confirmed: boolean;
-    receipt: ethers.providers.TransactionReceipt | null;
+    receipt: EthereumTransactionRecept | null;
   }
 
   router.post(
@@ -209,7 +316,7 @@ export namespace EthereumRoutes {
           latency: latency(initTime, Date.now()),
           txHash: req.body.txHash,
           confirmed,
-          receipt: receipt,
+          receipt: toEthereumTransactionReceipt(receipt),
         });
       }
     )
diff --git a/gateway/src/chains/ethereum/uniswap/uniswap.routes.ts b/gateway/src/chains/ethereum/uniswap/uniswap.routes.ts
index 211a3b3ab3a..a4a0dad1fc3 100644
--- a/gateway/src/chains/ethereum/uniswap/uniswap.routes.ts
+++ b/gateway/src/chains/ethereum/uniswap/uniswap.routes.ts
@@ -4,16 +4,20 @@ import { Uniswap, ExpectedTrade } from './uniswap';
 import { ConfigManager } from '../../../services/config-manager';
 import { HttpException, asyncHandler } from '../../../services/error-handler';
 import { BigNumber } from 'ethers';
-import { latency } from '../../../services/base';
+import {
+  latency,
+  gasCostInEthString,
+  stringWithDecimalToBigNumber,
+} from '../../../services/base';
 import { ethers } from 'ethers';
-import { CurrencyAmount, Price, Trade } from '@uniswap/sdk';
+import { Trade } from '@uniswap/sdk';
 import { verifyEthereumIsAvailable } from '../ethereum-middlewares';
 import { verifyUniswapIsAvailable } from './uniswap-middlewares';
 
 export namespace UniswapRoutes {
   export const router = Router();
   const uniswap = Uniswap.getInstance();
-  const eth = Ethereum.getInstance();
+  const ethereum = Ethereum.getInstance();
 
   router.use(
     asyncHandler(verifyEthereumIsAvailable),
@@ -33,7 +37,7 @@ export namespace UniswapRoutes {
   interface UniswapPriceRequest {
     quote: string;
     base: string;
-    amount: BigNumber;
+    amount: string;
     side: Side;
   }
 
@@ -43,12 +47,12 @@ export namespace UniswapRoutes {
     latency: number;
     base: string;
     quote: string;
-    amount: BigNumber;
-    expectedAmount: CurrencyAmount;
-    tradePrice: Price;
+    amount: string;
+    expectedAmount: string;
+    price: string;
     gasPrice: number;
     gasLimit: number;
-    gasCost: number;
+    gasCost: string;
     trade: Trade;
   }
 
@@ -60,11 +64,38 @@ export namespace UniswapRoutes {
         res: Response<UniswapPriceResponse, {}>
       ) => {
         const initTime = Date.now();
-        const amount = req.body.amount;
-        const baseToken = eth.getTokenBySymbol(req.body.base);
+
+        // the amount is passed in as a string. We must validate the value.
+        // If it is a strictly an integer string, we can pass it interpet it as a BigNumber.
+        // If is a float string, we need to know how many decimal places it has then we can
+        // convert it to a BigNumber.
+        let amount: BigNumber;
+        if (req.body.amount.indexOf('.') > -1) {
+          let token;
+          if (req.body.side === 'BUY') {
+            token = ethereum.getTokenBySymbol(req.body.quote);
+          } else {
+            token = ethereum.getTokenBySymbol(req.body.base);
+          }
+          if (token) {
+            amount = stringWithDecimalToBigNumber(
+              req.body.amount,
+              token.decimals
+            );
+          } else {
+            throw new HttpException(
+              500,
+              'Unrecognized token symbol for amount.'
+            );
+          }
+        } else {
+          amount = BigNumber.from(req.body.amount);
+        }
+
+        const baseToken = ethereum.getTokenBySymbol(req.body.base);
 
         if (baseToken) {
-          const quoteToken = eth.getTokenBySymbol(req.body.quote);
+          const quoteToken = ethereum.getTokenBySymbol(req.body.quote);
           if (quoteToken) {
             const result: ExpectedTrade | string =
               req.body.side === 'BUY'
@@ -94,19 +125,19 @@ export namespace UniswapRoutes {
                   : trade.executionPrice;
 
               const gasLimit = ConfigManager.config.UNISWAP_GAS_LIMIT;
-              const gasPrice = eth.getGasPrice();
+              const gasPrice = ethereum.getGasPrice();
               const payload = {
                 network: ConfigManager.config.ETHEREUM_CHAIN,
                 timestamp: initTime,
                 latency: latency(initTime, Date.now()),
                 base: baseToken.address,
                 quote: quoteToken.address,
-                amount: amount,
-                expectedAmount: expectedAmount,
-                tradePrice: tradePrice,
+                amount: amount.toString(),
+                expectedAmount: expectedAmount.toSignificant(8),
+                price: tradePrice.toSignificant(8),
                 gasPrice: gasPrice,
                 gasLimit: gasLimit,
-                gasCost: gasPrice * gasLimit,
+                gasCost: gasCostInEthString(gasPrice, gasLimit),
                 trade: trade,
               };
               res.status(200).json(payload);
@@ -130,9 +161,10 @@ export namespace UniswapRoutes {
   interface UniswapTradeRequest {
     quote: string;
     base: string;
-    amount: BigNumber;
+    amount: string;
     privateKey: string;
     side: Side;
+    limitPrice?: BigNumber;
   }
 
   interface UniswapTradeResponse {
@@ -141,59 +173,97 @@ export namespace UniswapRoutes {
     latency: number;
     base: string;
     quote: string;
-    amount: BigNumber;
-    expectedIn?: CurrencyAmount;
-    expectedOut?: CurrencyAmount;
-    price: Price;
+    amount: string;
+    expectedIn?: string;
+    expectedOut?: string;
+    price: string;
     gasPrice: number;
     gasLimit: number;
-    gasCost: number;
+    gasCost: string;
     txHash: string | undefined;
   }
 
+  interface UniswapTradeErrorResponse {
+    error: string;
+    message: string;
+  }
+
   router.post(
     '/trade',
     asyncHandler(
       async (
         req: Request<{}, {}, UniswapTradeRequest>,
-        res: Response<UniswapTradeResponse, {}>
+        res: Response<UniswapTradeResponse | UniswapTradeErrorResponse, {}>
       ) => {
         const initTime = Date.now();
-        const wallet = new ethers.Wallet(req.body.privateKey, eth.provider);
 
-        const baseToken = eth.getTokenBySymbol(req.body.base);
+        const limitPrice = req.body.limitPrice;
+
+        const wallet = new ethers.Wallet(
+          req.body.privateKey,
+          ethereum.provider
+        );
+
+        const baseToken = ethereum.getTokenBySymbol(req.body.base);
         if (!baseToken)
           throw new HttpException(
             500,
             'Unrecognized base token symbol: ' + req.body.base
           );
-        const quoteToken = eth.getTokenBySymbol(req.body.quote);
+        const quoteToken = ethereum.getTokenBySymbol(req.body.quote);
         if (!quoteToken)
           throw new HttpException(
             500,
             'Unrecognized quote token symbol: ' + req.body.quote
           );
+
+        let amount: BigNumber;
+        if (req.body.amount.indexOf('.') > -1) {
+          let token;
+          if (req.body.side === 'BUY') {
+            token = ethereum.getTokenBySymbol(req.body.quote);
+          } else {
+            token = ethereum.getTokenBySymbol(req.body.base);
+          }
+          if (token) {
+            amount = stringWithDecimalToBigNumber(
+              req.body.amount,
+              token.decimals
+            );
+          } else {
+            throw new HttpException(500, 'Unrecognized quote token symbol.');
+          }
+        } else {
+          amount = BigNumber.from(req.body.amount);
+        }
+
         const result: ExpectedTrade | string =
           req.body.side === 'BUY'
             ? await uniswap.priceSwapOut(
                 quoteToken.address, // tokenIn is quote asset
                 baseToken.address, // tokenOut is base asset
-                req.body.amount
+                amount
               )
             : await uniswap.priceSwapIn(
                 baseToken.address, // tokenIn is base asset
                 quoteToken.address, // tokenOut is quote asset
-                req.body.amount
+                amount
               );
 
         if (typeof result === 'string')
           throw new HttpException(500, 'Uniswap trade query failed: ' + result);
 
-        const gasPrice = eth.getGasPrice();
+        const gasPrice = ethereum.getGasPrice();
         const gasLimit = ConfigManager.config.UNISWAP_GAS_LIMIT;
         if (req.body.side === 'BUY') {
           const price = result.trade.executionPrice.invert();
 
+          if (limitPrice && price.toFixed(8) >= limitPrice.toString())
+            throw new HttpException(
+              500,
+              `Swap price ${price} exceeds limitPrice ${limitPrice}`
+            );
+
           const tx = await uniswap.executeTrade(wallet, result.trade, gasPrice);
           return res.status(200).json({
             network: ConfigManager.config.ETHEREUM_CHAIN,
@@ -201,16 +271,22 @@ export namespace UniswapRoutes {
             latency: latency(initTime, Date.now()),
             base: baseToken.address,
             quote: quoteToken.address,
-            amount: req.body.amount,
-            expectedIn: result.expectedAmount,
-            price: price,
+            amount: amount.toString(),
+            expectedIn: result.expectedAmount.toSignificant(8),
+            price: price.toSignificant(8),
             gasPrice: gasPrice,
             gasLimit: gasLimit,
-            gasCost: gasPrice * gasLimit,
+            gasCost: gasCostInEthString(gasPrice, gasLimit),
             txHash: tx.hash,
           });
         } else {
           const price = result.trade.executionPrice;
+          if (limitPrice && price.toFixed(8) >= limitPrice.toString())
+            throw new HttpException(
+              500,
+              `Swap price ${price} lower than limitPrice ${limitPrice}`
+            );
+
           const tx = await uniswap.executeTrade(wallet, result.trade, gasPrice);
           return res.status(200).json({
             network: ConfigManager.config.ETHEREUM_CHAIN,
@@ -218,12 +294,12 @@ export namespace UniswapRoutes {
             latency: latency(initTime, Date.now()),
             base: baseToken.address,
             quote: quoteToken.address,
-            amount: req.body.amount,
-            expectedOut: result.expectedAmount,
-            price: price,
+            amount: amount.toString(),
+            expectedOut: result.expectedAmount.toSignificant(8),
+            price: price.toSignificant(8),
             gasPrice: gasPrice,
             gasLimit,
-            gasCost: gasPrice * gasLimit,
+            gasCost: gasCostInEthString(gasPrice, gasLimit),
             txHash: tx.hash,
           });
         }
diff --git a/gateway/src/chains/ethereum/uniswap/uniswap.ts b/gateway/src/chains/ethereum/uniswap/uniswap.ts
index bcde7449938..eb218ac128b 100644
--- a/gateway/src/chains/ethereum/uniswap/uniswap.ts
+++ b/gateway/src/chains/ethereum/uniswap/uniswap.ts
@@ -101,7 +101,7 @@ export class Uniswap {
       `Best trade for ${tokenIn.address}-${tokenOut.address}: ${trades[0]}`
     );
     const expectedAmount = trades[0].minimumAmountOut(
-      ConfigManager.config.UNISWAP_ALLOWED_SLIPPAGE
+      ConfigManager.getUniswapAllowedSlippagePercentage(ConfigManager.config)
     );
     return { trade: trades[0], expectedAmount };
   }
@@ -134,8 +134,9 @@ export class Uniswap {
     logger.info(
       `Best trade for ${tokenIn.address}-${tokenOut.address}: ${trades[0]}`
     );
+
     const expectedAmount = trades[0].maximumAmountIn(
-      ConfigManager.config.UNISWAP_ALLOWED_SLIPPAGE
+      ConfigManager.getUniswapAllowedSlippagePercentage(ConfigManager.config)
     );
     return { trade: trades[0], expectedAmount };
   }
@@ -150,7 +151,9 @@ export class Uniswap {
     const result = Router.swapCallParameters(trade, {
       ttl: ConfigManager.config.UNISWAP_TTL,
       recipient: wallet.address,
-      allowedSlippage: ConfigManager.config.UNISWAP_ALLOWED_SLIPPAGE,
+      allowedSlippage: ConfigManager.getUniswapAllowedSlippagePercentage(
+        ConfigManager.config
+      ),
     });
 
     const contract = new Contract(this._uniswapRouter, routerAbi.abi, wallet);
diff --git a/gateway/src/services/base.ts b/gateway/src/services/base.ts
index f5d46f416e1..5902630b526 100644
--- a/gateway/src/services/base.ts
+++ b/gateway/src/services/base.ts
@@ -29,6 +29,40 @@ export const bigNumberWithDecimalToStr = (n: BigNumber, d: number): string => {
     .join('');
 };
 
+export const stringWithDecimalToBigNumber = (
+  numberStr: string,
+  d: number
+): BigNumber => {
+  const leftAndRight = numberStr.split('.');
+
+  if (leftAndRight.length === 2) {
+    const existingDecimals = leftAndRight[1];
+    if (existingDecimals.length > d) {
+      const right = leftAndRight[1].substr(0, d);
+      return BigNumber.from(leftAndRight[0] + right);
+    } else {
+      const neededZeros = d - existingDecimals.length;
+      const zeros = '0'.repeat(neededZeros);
+      return BigNumber.from(leftAndRight[0] + leftAndRight[1] + zeros);
+    }
+  }
+
+  const zeros = '0'.repeat(d);
+  return BigNumber.from(numberStr + zeros);
+};
+
+export const gasCostInEthString = (
+  gasPrice: number,
+  gasLimit: number
+): string => {
+  return bigNumberWithDecimalToStr(
+    BigNumber.from(gasPrice)
+      .mul(BigNumber.from(gasLimit))
+      .mul(BigNumber.from('1000000000')),
+    18
+  );
+};
+
 // a nice way to represent the token value without carrying around as a string
 export interface TokenValue {
   value: BigNumber;
diff --git a/gateway/src/services/config-manager.ts b/gateway/src/services/config-manager.ts
index 0e98ba99c22..dad058a6d8f 100644
--- a/gateway/src/services/config-manager.ts
+++ b/gateway/src/services/config-manager.ts
@@ -20,13 +20,24 @@ export namespace ConfigManager {
     ETH_GAS_STATION_GAS_LEVEL: string;
     ETH_GAS_STATION_REFRESH_TIME: number;
     ETH_MANUAL_GAS_PRICE: number;
-    UNISWAP_ALLOWED_SLIPPAGE: Percent;
+    UNISWAP_ALLOWED_SLIPPAGE: string;
     UNISWAP_GAS_LIMIT: number;
     UNISWAP_TTL: number;
     LOG_TO_STDOUT?: boolean;
     UNSAFE_DEV_MODE_WITH_HTTP?: boolean;
   }
 
+  const percentRegexp = new RegExp(/^(\d+)\/(\d+)$/);
+
+  export function getUniswapAllowedSlippagePercentage(config: Config): Percent {
+    const slippageString = config['UNISWAP_ALLOWED_SLIPPAGE'];
+    const nd = slippageString.match(percentRegexp);
+    if (nd) return new Percent(nd[1], nd[2]);
+    throw new Error(
+      'Encountered a malformed percent string in the config for UNISWAP_ALLOWED_SLIPPAGE.'
+    );
+  }
+
   export function validateConfig(o: any): o is Config {
     return (
       'VERSION' in o &&
@@ -46,6 +57,7 @@ export namespace ConfigManager {
       'ETH_GAS_STATION_REFRESH_TIME' in o &&
       'ETH_MANUAL_GAS_PRICE' in o &&
       'UNISWAP_ALLOWED_SLIPPAGE' in o &&
+      percentRegexp.test(o['UNISWAP_ALLOWED_SLIPPAGE']) &&
       'UNISWAP_GAS_LIMIT' in o &&
       'UNISWAP_TTL' in o
     );
diff --git a/gateway/test/services/base.test.ts b/gateway/test/services/base.test.ts
index 14f3d0cd3f3..0d37b67136f 100644
--- a/gateway/test/services/base.test.ts
+++ b/gateway/test/services/base.test.ts
@@ -1,5 +1,9 @@
 import { BigNumber } from 'ethers';
-import { bigNumberWithDecimalToStr } from '../../src/services/base';
+import {
+  bigNumberWithDecimalToStr,
+  stringWithDecimalToBigNumber,
+  gasCostInEthString,
+} from '../../src/services/base';
 import 'jest-extended';
 
 test('bigNumberWithDecimalToStr', () => {
@@ -23,3 +27,21 @@ test('bigNumberWithDecimalToStr', () => {
     bigNumberWithDecimalToStr(BigNumber.from('1345000000000000000000'), 18)
   ).toEqual('1345.000000000000000000');
 });
+
+test('stringWithDecimalToBigNumber', () => {
+  expect(stringWithDecimalToBigNumber('1.001', 5)).toEqual(
+    BigNumber.from('100100')
+  );
+
+  expect(stringWithDecimalToBigNumber('1', 5)).toEqual(
+    BigNumber.from('100000')
+  );
+
+  expect(stringWithDecimalToBigNumber('1.00000000000', 2)).toEqual(
+    BigNumber.from('100')
+  );
+});
+
+test('gasCostInEthString', () => {
+  expect(gasCostInEthString(200, 21000)).toEqual('0.004200000000000000');
+});
diff --git a/hummingbot/connector/connector/uniswap/uniswap_connector.py b/hummingbot/connector/connector/uniswap/uniswap_connector.py
index d9b1468bb7b..28e5d1cd605 100644
--- a/hummingbot/connector/connector/uniswap/uniswap_connector.py
+++ b/hummingbot/connector/connector/uniswap/uniswap_connector.py
@@ -85,8 +85,6 @@ def __init__(self,
         self._allowances = {}
         self._status_polling_task = None
         self._auto_approve_task = None
-        self._initiate_pool_task = None
-        self._initiate_pool_status = None
         self._real_time_balance_update = False
         self._poll_notifier = None
 
@@ -105,28 +103,6 @@ def limit_orders(self) -> List[LimitOrder]:
             for in_flight_order in self._in_flight_orders.values()
         ]
 
-    async def initiate_pool(self) -> str:
-        """
-        Initiate connector and start caching paths for trading_pairs
-        """
-        while True:
-            try:
-                # self.logger().info(f"Initializing Uniswap connector and paths for {self._trading_pairs} pairs.")
-                resp = await self._api_request("get", "eth/uniswap/start",
-                                               {"pairs": json.dumps(self._trading_pairs)})
-                status = bool(str(resp["success"]))
-                if status:
-                    self._initiate_pool_status = status
-                    await asyncio.sleep(60)
-            except asyncio.CancelledError:
-                raise
-            except Exception as e:
-                self.logger().network(
-                    f"Error initializing {self._trading_pairs} ",
-                    exc_info=True,
-                    app_warning_msg=str(e)
-                )
-
     async def auto_approve(self):
         """
         Automatically approves Uniswap contract as a spender for token in trading pairs.
@@ -151,7 +127,7 @@ async def approve_uniswap_spender(self, token_symbol: str) -> Decimal:
         resp = await self._api_request("post",
                                        "eth/approve",
                                        {"token": token_symbol,
-                                        "connector": self.name})
+                                        "spender": self.name})
         amount_approved = Decimal(str(resp["amount"]))
         if amount_approved > 0:
             self.logger().info(f"Approved Uniswap spender contract for {token_symbol}.")
@@ -166,8 +142,8 @@ async def get_allowances(self) -> Dict[str, Decimal]:
         """
         ret_val = {}
         resp = await self._api_request("post", "eth/allowances",
-                                       {"tokenList": "[" + (",".join(['"' + t + '"' for t in self._tokens])) + "]",
-                                        "connector": self.name})
+                                       {"tokenSymbols": list(self._tokens),
+                                        "spender": self.name})
         for token, amount in resp["approvals"].items():
             ret_val[token] = Decimal(str(amount))
         return ret_val
@@ -190,17 +166,18 @@ async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal
                                            {"base": base,
                                             "quote": quote,
                                             "side": side.upper(),
-                                            "amount": amount})
+                                            "amount": str(amount)})
             required_items = ["price", "gasLimit", "gasPrice", "gasCost"]
             if any(item not in resp.keys() for item in required_items):
                 if "info" in resp.keys():
                     self.logger().info(f"Unable to get price. {resp['info']}")
                 else:
-                    self.logger().info(f"Missing data from price result. Incomplete return result for ({resp.keys()})")
+                    missing_items = ', '.join([item for item in required_items if item not in resp.keys()])
+                    self.logger().info(f"Missing data from price result. Incomplete return result for ({missing_items})")
             else:
                 gas_limit = resp["gasLimit"]
                 gas_price = resp["gasPrice"]
-                gas_cost = resp["gasCost"]
+                gas_cost = Decimal(str(resp["gasCost"]))
                 price = resp["price"]
                 account_standing = {
                     "allowances": self._allowances,
@@ -466,7 +443,6 @@ def status_dict(self) -> Dict[str, bool]:
     async def start_network(self):
         if self._trading_required:
             self._status_polling_task = safe_ensure_future(self._status_polling_loop())
-            self._initiate_pool_task = safe_ensure_future(self.initiate_pool())
             self._auto_approve_task = safe_ensure_future(self.auto_approve())
 
     async def stop_network(self):
@@ -476,14 +452,11 @@ async def stop_network(self):
         if self._auto_approve_task is not None:
             self._auto_approve_task.cancel()
             self._auto_approve_task = None
-        if self._initiate_pool_task is not None:
-            self._initiate_pool_task.cancel()
-            self._initiate_pool_task = None
 
     async def check_network(self) -> NetworkStatus:
         try:
-            response = await self._api_request("get", "api")
-            if response["status"] != "ok":
+            response = await self._api_request("get", "")
+            if response["message"] != "ok":
                 raise Exception(f"Error connecting to Gateway API. HTTP status is {response.status}.")
         except asyncio.CancelledError:
             raise
@@ -531,8 +504,7 @@ async def _update_balances(self, on_interval = False):
             remote_asset_names = set()
             resp_json = await self._api_request("post",
                                                 "eth/balances",
-                                                {"tokenList": "[" + (",".join(['"' + t + '"' for t in self._tokens])) + "]"})
-
+                                                {"tokenSymbols": list(self._tokens)})
             for token, bal in resp_json["balances"].items():
                 self._account_available_balances[token] = Decimal(str(bal))
                 self._account_balances[token] = Decimal(str(bal))
diff --git a/hummingbot/strategy/amm_arb/amm_arb.py b/hummingbot/strategy/amm_arb/amm_arb.py
index e1434bdee5b..bbea42757f6 100644
--- a/hummingbot/strategy/amm_arb/amm_arb.py
+++ b/hummingbot/strategy/amm_arb/amm_arb.py
@@ -115,7 +115,8 @@ def tick(self, timestamp: float):
         if not self._all_markets_ready:
             self._all_markets_ready = all([market.ready for market in self.active_markets])
             if not self._all_markets_ready:
-                self.logger().warning("Markets are not ready. Please wait...")
+                unready_markets = ', '.join([market.name for market in self.active_markets if market.ready is False])
+                self.logger().warning(f"Markets are not ready ({unready_markets}). Please wait...")
                 return
             else:
                 self.logger().info("Markets are ready. Trading started.")


Code review comments:
diff_hunk: @@ -29,6 +29,40 @@ export const bigNumberWithDecimalToStr = (n: BigNumber, d: number): string => {
     .join('');
 };
 
+export const stringWithDecimalToBigNumber = (
+  numberStr: string,
+  d: number
+): BigNumber => {
+  const leftAndRight = numberStr.split('.');
+
+  if (leftAndRight.length === 2) {
+    const existingDecimals = leftAndRight[1];
+    if (existingDecimals.length > d) {
+      const right = leftAndRight[1].substr(0, d);
+      return BigNumber.from(leftAndRight[0] + right);
+    } else {
+      const neededZeros = d - existingDecimals.length;
+      const zeros = '0'.repeat(neededZeros);
+      return BigNumber.from(leftAndRight[0] + leftAndRight[1] + zeros);
+    }
+  }
+
+  const zeros = '0'.repeat(d);
+  return BigNumber.from(numberStr + zeros);
+};
+
+export const gasCostInEthString = (
+  gasPrice: number,
+  gasLimit: number
+): string => {
+  return bigNumberWithDecimalToStr(
+    BigNumber.from(gasPrice)
path: gateway/src/services/base.ts
text: @user1:
This is unsafe because floating point numbers (e.g. 2.1) are actually valid gas prices. `BigNumber` on the other hand, only deals with integers.

I'd recommend doing the `gasPrice` * `gasLimit` calculation in floating point and then convert to integer first - since that part usually doesn't overflow the precision of floating point numbers. After that, convert to `BigNumber`. e.g.

`BigNumber.from(Math.ceil(gasPrice * gasLimit)).mul(BigNumber.from(1e9))`

@author:
Good to know, I didn't realize that about gas prices. Refactored.

diff_hunk: @@ -38,6 +40,78 @@ export namespace EthereumRoutes {
     })
   );
 
+  interface EthereumAllowancesRequest {
+    privateKey: string; // the users private Ethereum key
+    spender: string; // the spender address for whom approvals are checked
+    tokenSymbols: string[]; // a list of token symbol
+  }
+
+  interface EthereumAllowancesResponse {
+    network: string;
+    timestamp: number;
+    latency: number;
+    spender: string;
+    approvals: Record<string, string>;
+  }
+
+  router.post(
+    '/allowances',
+    asyncHandler(
+      async (
+        req: Request<{}, {}, EthereumAllowancesRequest>,
+        res: Response<EthereumAllowancesResponse | string, {}>
+      ) => {
+        const initTime = Date.now();
+        const wallet = ethereum.getWallet(req.body.privateKey);
+
+        const tokens: Record<string, Token> = {};
+
+        for (var i = 0; i < req.body.tokenSymbols.length; i++) {
+          const symbol = req.body.tokenSymbols[i];
+          const token = ethereum.getTokenBySymbol(symbol);
+          if (!token) {
+            continue;
+          }
+
+          tokens[symbol] = token;
+        }
+
+        let spender: string;
+        if (req.body.spender === 'uniswap') {
path: gateway/src/chains/ethereum/ethereum.routes.ts
text: @user1:
This block is being repeated. Better refactor it out.

@author:
Thanks, refactored.

diff_hunk: @@ -38,6 +40,78 @@ export namespace EthereumRoutes {
     })
   );
 
+  interface EthereumAllowancesRequest {
+    privateKey: string; // the users private Ethereum key
+    spender: string; // the spender address for whom approvals are checked
+    tokenSymbols: string[]; // a list of token symbol
+  }
+
+  interface EthereumAllowancesResponse {
+    network: string;
+    timestamp: number;
+    latency: number;
+    spender: string;
+    approvals: Record<string, string>;
+  }
+
+  router.post(
+    '/allowances',
+    asyncHandler(
+      async (
+        req: Request<{}, {}, EthereumAllowancesRequest>,
+        res: Response<EthereumAllowancesResponse | string, {}>
+      ) => {
+        const initTime = Date.now();
+        const wallet = ethereum.getWallet(req.body.privateKey);
+
+        const tokens: Record<string, Token> = {};
+
+        for (var i = 0; i < req.body.tokenSymbols.length; i++) {
path: gateway/src/chains/ethereum/ethereum.routes.ts
text: @user1:
This block is also being repeated. Refactor.

@author:
Thanks, refactored.