javascriptblockchainethereumuniswaptoken-swap-program

Can't use Uniswap V3 SwapRouter for multihop swaps, SwapRouter.exactInput(params) throws 'UNPREDICTABLE_GAS_LIMIT'


I'm trying to implement swap with new Uniswap V3 contracts. I'm using Quoter contract for getting the quotes out and SwapRouter for making the swaps. If I'm using methods for direct swap (when tokens have pools) for example - -

ethersProvider = new ethers.providers.Web3Provider(web3.currentProvider, 137);

uniSwapQuoter = new ethers.Contract(uniSwapQuoterAddress, QuoterAbi.abi, ethersProvider);

uniSwapRouterV3 = new ethers.Contract(uniSwapRouterAddress, RouterAbi.abi, 
ethersProvider.getSigner());

uniSwapQuoter.callStatic.quoteExactInputSingle(.....) 
uniSwapQuoter.callStatic.quoteExactOutputSingle(.....)
uniSwapRouterV3.exactInputSingle(params)

everything works fine, but when I try to use the multihop quotes and multihop swaps if fails with


    "reason": "cannot estimate gas; transaction may fail or may require manual gas limit",
    "code": "UNPREDICTABLE_GAS_LIMIT",
    "error": {
        "code": -32000,
        "message": "execution reverted"
    },
    "method": "estimateGas",
    "transaction": {
        "from": "0x532d647481c20f4422A8331339D76b25cA569959",
        "to": "0xE592427A0AEce92De3Edee1F18E0157C05861564",
        "data": "0xc04b8d59000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000002a6b82b6dd3f38eeb63a35f2f503b9398f02d9bb0000000000000000000000000000000000000000000000000000000861c468000000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000422791bca1f2de4661ed88a30c99a7a9449aa841740005007ceb23fd6bc0add59e62ac25578270cff1b9f619003000c26d47d5c33ac71ac5cf9f776d63ba292a4f7842000000000000000000000000000000000000000000000000000000000000",
        "accessList": null
    }

for encoding the params I'm using the uniswap example from tests:

function encodePath(tokenAddresses, fees) {
  const FEE_SIZE = 3

  if (path.length != fees.length + 1) {
    throw new Error('path/fee lengths do not match')
  }

  let encoded = '0x'
  for (let i = 0; i < fees.length; i++) {
    // 20 byte encoding of the address
    encoded += path[i].slice(2)
    // 3 byte encoding of the fee
    encoded += fees[i].toString(16).padStart(2 * FEE_SIZE, '0')
  }
  // encode the final token
  encoded += path[path.length - 1].slice(2)

  return encoded.toLowerCase()
}

and finally my example code I'm doing for quotes:

    const routeAndFees = await getAddressPath(path);
    const encodedPath = await encodePath(routeAndFees.path, routeAndFees.fees);
    const usdcWithDecimals = parseFloat(usdcAmount) * 1000000
    const tokenDecimals = path[path.length - 1].tokenOut.decimals;

    try {
        const amountOut = await uniSwapQuoter.callStatic.quoteExactInput(encodedPath, usdcWithDecimals.toString());
        console.log("Token amount out:", parseFloat(amountOut) / (10 ** tokenDecimals));
        return {
            tokenOut: parseFloat(amountOut) / (10 ** tokenDecimals),
            usdcIn: parseFloat(usdcAmount)
        };
    } catch (e) {
        console.log(e);
        return e;
    }
}

and swapping:

async function multiSwap(path, userAddress, usdcAmount) {
    const usdcWithDecimals = parseFloat(usdcAmount) * 1000000
    const routeAndFees = await getAddressPath(path);
    const encodedPath = await encodePath(routeAndFees.path, routeAndFees.fees);

    const params = {
        path: encodedPath,
        recipient: userAddress,
        deadline: Math.floor(Date.now() / 1000) + 900,
        amountIn: usdcWithDecimals.toString(),
        amountOutMinimum: 0,
    }
    try {
        return  await uniSwapRouterV3.exactInput(params);
    } catch (e) {
        console.log(e);
        return e;
    }
}

The path is [address,fee,address,fee,address] like it should be, I not sure about the encoding of that, but didn't find any other example. Actually didn't find any example for doing uniswap v3 multihop swaps, even in the UniDocs there is Trade example and single pool swap... Can someone point what could I have done wrong here? The same error is in quoting and when swapping :/

I'm testing on Polygon Mainnet and I can make the same path swap directly on uniswap but it fails when I trigger the script...


Solution

  • You should hash the fee value. Instead of 0 add 6. This should work for you:

     async function encodePath(path, fees, exactInput) {
        const FEE_SIZE = 6
        if (path.length !== fees.length + 1) {
            throw new Error('path/fee lengths do not match')
        }
        if (!exactInput) {
            path = path.reverse();
            fees = fees.reverse();
        }
    
        let encoded = '0x'
        for (let i = 0; i < fees.length; i++) {
            encoded += path[i].slice(2)
            let fee = web3.utils.toHex(parseFloat(fees[i])).slice(2).toString();
            encoded += fee.padStart(FEE_SIZE, '0');
        }
        encoded += path[path.length - 1].slice(2)
        return encoded    
    }