PS C:\Users\lidor\Desktop\Trade Bot> node index.js
Token to Swap Properties: { name: 'Wrapped Ether', symbol: 'WETH', decimals: 18n }
Token to Receive Properties: { name: 'PoolTogether', symbol: 'POOL', decimals: 18n }
Token to Swap Amount: 1000000000000000000
Estimated amount out: 0.00040059895230068 PoolTogether
Estimated gas fee: 0.00000000000048473 ETH
Approving token spend by SwapRouter…
Token spend approval transaction confirmed.
Performing a dry-run of the swap transaction…
Dry-run failed with error: no matching function (argument="key", value="address", code=INVALID_ARGUMENT, version=6.4.1)
Sending swap transaction…
Error: could not coalesce error (error={ "code": -32603, "data": { "data": "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025446000000000000000000000000000000000000000000000000000000000000", "message": "Error: VM Exception while processing transaction: reverted with reason string 'TF'", "txHash": "0x505e79376b0672937bb78f167bbfc1e47f9472de68f5dd472f9a1325ac4577f4" }, "message": "Error: VM Exception while processing transaction: reverted with reason string 'TF'" }, code=UNKNOWN_ERROR, version=6.4.1)
    at makeError (C:\Users\lidor\Desktop\Trade Bot\node_modules\ethers\lib.commonjs\utils\errors.js:125:21)
    at JsonRpcProvider.getRpcError (C:\Users\lidor\Desktop\Trade Bot\node_modules\ethers\lib.commonjs\providers\provider-jsonrpc.js:638:41)
    at C:\Users\lidor\Desktop\Trade Bot\node_modules\ethers\lib.commonjs\providers\provider-jsonrpc.js:257:52
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  code: 'UNKNOWN_ERROR',
  error: {
    code: -32603,
    message: "Error: VM Exception while processing transaction: reverted with reason string 'TF'",
    data: {
      message: "Error: VM Exception while processing transaction: reverted with reason string 'TF'",
      txHash: '0x505e79376b0672937bb78f167bbfc1e47f9472de68f5dd472f9a1325ac4577f4',
      data: '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025446000000000000000000000000000000000000000000000000000000000000'
    }
  }
}
PS C:\Users\lidor\Desktop\Trade Bot> 
const ethers = require('ethers');
const BigNumber = require('big-number');
const { computePoolAddress } = require('@uniswap/v3-sdk');
const { Token } = require('@uniswap/sdk-core');

// Ethereum network configuration
const provider = new ethers.JsonRpcProvider('http://127.0.0.1:8545/');

const privateKey = 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';

// Uniswap V3 contract configuration
const uniswapV3SwapRouterAddress = '0xE592427A0AEce92De3Edee1F18E0157C05861564';
const PoolFactoryContractAddress = '0x1F98431c8aD98523631AE4a59f267346ea31F984';
const quoterContractAddress = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6';
const uniswapV3SwapRouterABI = require('@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json').abi;
const uniswapV3PoolABI = require('@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json').abi;
const uniswapV3QuoterAbi = require('@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json').abi;

// Token configuration
const tokenToSwapAddress = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const tokenToReceiveAddress = '0x0cEC1A9154Ff802e7934Fc916Ed7Ca50bDE6844e';

const wallet = new ethers.Wallet(privateKey, provider);

// Load the Uniswap V3 contract
const uniswapV3SwapRouterContract = new ethers.Contract(uniswapV3SwapRouterAddress, uniswapV3SwapRouterABI, wallet);

async function getEstimatedGasPrice() {
  try {
    const gasPrice = await provider.send('eth_gasPrice', []);
    const decimalGasPrice = BigInt(gasPrice); // Convert to decimal
    const gweiGasPrice = decimalGasPrice / BigInt(1e9); // Convert to Gwei
    return gweiGasPrice.toString();
  } catch (error) {
    console.error('Error fetching gas price:', error);
    // Fallback gas price in case of error fetching it from provider
    return ethers.parseUnits('50', 'gwei');
  }
}

async function estimateGas(txObj) {
  const gasLimit = await provider.estimateGas({ ...txObj, from: wallet.address });
  const estimatedGasPrice = await getEstimatedGasPrice();

  // For EIP-1559 networks
  const maxFeePerGas = ethers.parseUnits(estimatedGasPrice, 'gwei');
  const maxPriorityFeePerGas = ethers.parseUnits('2', 'gwei'); // You can set this as per network conditions

  const totalGas = BigNumber(gasLimit.toString()).multiply(estimatedGasPrice.toString());
  return {
    gasLimit,
    maxFeePerGas,
    maxPriorityFeePerGas,
    total: ethers.formatEther(totalGas.toString()),
  };
}

async function executeTrade() {
  try {
    const tokenToSwapProperties = await getTokenProperties(tokenToSwapAddress);
    const tokenToReceiveProperties = await getTokenProperties(tokenToReceiveAddress);
    const tokenToSwap = new Token(1, tokenToSwapAddress, Number(tokenToSwapProperties.decimals), tokenToSwapProperties.symbol, tokenToSwapProperties.name);
    const tokenToReceive = new Token(1, tokenToReceiveAddress, Number(tokenToReceiveProperties.decimals), tokenToReceiveProperties.symbol, tokenToReceiveProperties.name);
    const tokenToSwapAmount = ethers.parseUnits('1', tokenToSwapProperties.decimals);

    console.log('Token to Swap Properties:', tokenToSwapProperties);
    console.log('Token to Receive Properties:', tokenToReceiveProperties);
    console.log('Token to Swap Amount:', tokenToSwapAmount.toString());

    const currentPoolAddress = computePoolAddress({
      factoryAddress: PoolFactoryContractAddress,
      tokenA: tokenToSwap, // Use the Token instance
      tokenB: tokenToReceive, // Use the Token instance
      fee: 3000,
    });

    const poolContract = new ethers.Contract(currentPoolAddress, uniswapV3PoolABI, provider);

    const [token0, token1, fee] = await Promise.all([poolContract.token0(), poolContract.token1(), poolContract.fee()]);

    const quoterContract = new ethers.Contract(quoterContractAddress, uniswapV3QuoterAbi, provider);

    const data = quoterContract.interface.encodeFunctionData('quoteExactInputSingle', [token0, token1, fee, tokenToSwapAmount.toString(), 0]);

    const txObject = {
      to: quoterContractAddress,
      data: data,
    };
    const quotedAmountOut = await provider.call(txObject);
    const humanReadableAmountOut = ethers.formatUnits(quotedAmountOut, tokenToReceiveProperties.decimals);
    console.log('Estimated amount out:', humanReadableAmountOut, tokenToReceiveProperties.name);

    const estimatedGasFee = await estimateGas(txObject);
    console.log('Estimated gas fee:', estimatedGasFee.total, 'ETH');

    // Approve Uniswap V3 SwapRouter to spend tokenToSwap
    console.log('Approving token spend by SwapRouter…');
    const tokenToSwapContract = new ethers.Contract(tokenToSwapAddress, ['function approve(address spender, uint256 amount) external returns (bool)'], wallet);
    const approvalTx = await tokenToSwapContract.approve(uniswapV3SwapRouterAddress, tokenToSwapAmount, {
      gasLimit: estimatedGasFee.gasLimit,
      maxFeePerGas: estimatedGasFee.maxFeePerGas,
      maxPriorityFeePerGas: estimatedGasFee.maxPriorityFeePerGas,
    });

    await approvalTx.wait();
    console.log('Token spend approval transaction confirmed.');

    const swapParams = {
      tokenIn: tokenToSwapAddress,
      tokenOut: tokenToReceiveAddress,
      fee: 3000,
      recipient: wallet.address,
      deadline: Math.floor(Date.now() / 1000) + 60 * 20,
      amountIn: tokenToSwapAmount,
      amountOutMinimum: 0,
      sqrtPriceLimitX96: 0,
    };

    console.log('Performing a dry-run of the swap transaction…');
    try {
      const dryRunResult = await dryRunTransaction(
        uniswapV3SwapRouterContract,
        swapParams,
        wallet.address,
        estimatedGasFee.gasLimit,
        estimatedGasFee.maxFeePerGas,
        estimatedGasFee.maxPriorityFeePerGas
      );
      console.log('Dry-run result:', dryRunResult);
    } catch (e) {
      console.error('Dry-run failed with error:', e.message);
    }

    console.log('Sending swap transaction…');
    const swapTx = await uniswapV3SwapRouterContract.exactInputSingle(swapParams, {
      gasLimit: estimatedGasFee.gasLimit,
      maxFeePerGas: estimatedGasFee.maxFeePerGas,
      maxPriorityFeePerGas: estimatedGasFee.maxPriorityFeePerGas,
    });

    console.log('Swap transaction hash:', swapTx.hash);

    // Wait for transaction confirmation
    console.log('Waiting for swap transaction confirmation…');
    await swapTx.wait();
    console.log('Swap transaction confirmed!');
  } catch (e) {
    console.log(e);
  }
}
executeTrade();

async function getTokenProperties(tokenAddress) {
  const erc20ABI = ['function name() view returns (string)', 'function symbol() view returns (string)', 'function decimals() view returns (uint8)'];
  const tokenContract = new ethers.Contract(tokenAddress, erc20ABI, provider);
  const [name, symbol, decimals] = await Promise.all([tokenContract.name(), tokenContract.symbol(), tokenContract.decimals()]);
  return {
    name,
    symbol,
    decimals,
  };
}

async function dryRunTransaction(contract, swapParams, sender, gasLimit, maxFeePerGas, maxPriorityFeePerGas) {
  const callTransaction = {
    to: contract.address,
    from: sender,
    data: contract.interface.encodeFunctionData('exactInputSingle', [swapParams]),
    gasLimit,
    maxFeePerGas,
    maxPriorityFeePerGas,
  };

  try {
    const result = await provider.call(callTransaction);
    return contract.interface.decodeFunctionResult('exactInputSingle', result);
  } catch (e) {
    throw new Error('Dry-run failed: ' + e.message);
  }
}