Viewing File: /home/ubuntu/efiexchange-node-base/src/controllers/userApp/user.controller.ts

import Web3 from "web3";
import Token from "../../utils/abi.json";
import { BigNumber, ethers, providers, utils } from "ethers";
import {
  SUPPORTED_RPC_URLS,
  CHAIN_INFO,
  SupportedChainId,
  SupportedTokens,
  SPECIAL_CONTRACT_ADDRESS,
} from "../../config/chains";
import { decrypt } from "@utils/decrypt";
import speakeasy from 'speakeasy';
require('dotenv').config();


// Helper: normalize input based on token decimals (max 8)
function normalizeAmount(value: string, tokenDecimals: number): string {
  const allowedDecimals = tokenDecimals > 8 ? 8 : tokenDecimals;
  if (value.includes(".")) {
    const [intPart, decPart] = value.split(".");
    const trimmedDecPart = decPart.slice(0, allowedDecimals);
    return trimmedDecPart.length > 0 ? `${intPart}.${trimmedDecPart}` : intPart;
  }
  return value;
}

/**
 * @method transferTokensHandler
 *
 * @description transfer Tokens
 *
 * @param req
 *
 * @param res
 *
 * @returns json response
 */

export async function transferTokensHandler(req: any, res: any) {
  try {
    let inputs = req.body;

    if (!inputs.receiver_wallet_address) {
      return res.sendError("Receiver Wallet Address is required");
    }
    if (!inputs.tokens) {
      return res.sendError("Amount is required");
    }
    if (!inputs.token_type) {
      return res.sendError("Token Type is required");
    }
    if (!inputs.network_type) {
      return res.sendError("Network Type is required");
    }
    if (!inputs.order_id) {
      return res.sendError("Order ID field is required");
    }

    const rpcUrl = SUPPORTED_RPC_URLS[SupportedTokens[inputs.network_type]];
    if (!rpcUrl) {
      return res.sendError("Invalid Network Type");
    }

    const privateKey = process.env.ADMIN_PRIVATE_KEY;

    let provider, wallet, contract_address, transactionReponse;
    let receiver_wallet_address = inputs.receiver_wallet_address;
    let tokens = inputs.tokens;
    let gasLimit: any = 21620;
    let gasPrice: any = ethers.utils.parseUnits("40", "gwei");

    const web3 = new Web3(rpcUrl);

    // Native token transfer (ETH/BNB/Polygon)
    if (["ETH", "POLYGON_AMOY", "BNB", "POL"].includes(inputs.token_type)) {
      console.log(`Initiating ${inputs.token_type} transfer`);

      if (!web3.utils.isAddress(receiver_wallet_address)) {
        return res.sendError("Invalid receiver wallet address");
      }

      const senderBalance: any = await web3.eth.getBalance(process.env.ADMIN_WALLET_ADDRESS);
      if (web3.utils.toBN(web3.utils.toWei(tokens, "ether")).gt(web3.utils.toBN(senderBalance))) {
        return res.sendError("Insufficient balance in sender's wallet");
      }

      const createTransaction = await web3.eth.accounts.signTransaction(
        {
          from: process.env.ADMIN_WALLET_ADDRESS,
          to: receiver_wallet_address,
          value: web3.utils.toWei(tokens, "ether"),
          gasPrice: gasPrice,
          gasLimit: gasLimit,
        },
        privateKey
      );

      await web3.eth.sendSignedTransaction(createTransaction.rawTransaction, function (error, hash) {
        if (!error) {
          transactionReponse = hash;
        } else {
          return res.sendError(`Transaction failed: ${error.message}`);
        }
      });

    } else {
      // ERC-20 token transfer
      const abi = [
        "function balanceOf(address owner) view returns (uint256)",
        "function transfer(address to, uint256 amount) returns (bool)",
        "function decimals() view returns (uint8)",
      ];

      contract_address = SPECIAL_CONTRACT_ADDRESS[inputs.network_type][inputs.token_type];
      provider = new ethers.providers.JsonRpcProvider(rpcUrl);
      wallet = new ethers.Wallet(privateKey, provider);
      const ethContract = new ethers.Contract(contract_address, abi, wallet);

      const tokenDecimals: number = await ethContract.decimals();
      const normalizedValue = normalizeAmount(tokens, tokenDecimals);
      const amountInBigNumber = ethers.utils.parseUnits(normalizedValue, tokenDecimals);

      if (!ethers.utils.isAddress(receiver_wallet_address)) {
        return res.sendError("Invalid receiver wallet address");
      }

      // Check sender token balance
      const senderTokenBalance = await ethContract.balanceOf(process.env.ADMIN_WALLET_ADDRESS);
      if (amountInBigNumber.gt(senderTokenBalance)) {
        return res.sendError("Insufficient token balance in sender's wallet");
      }

      // Check native currency for gas
      const senderNativeBalance = await provider.getBalance(process.env.ADMIN_WALLET_ADDRESS);
      const transactionCost = ethers.BigNumber.from(100000).mul(ethers.utils.parseUnits("50", "gwei"));

      if (ethers.BigNumber.from(senderNativeBalance).lt(transactionCost)) {
        return res.sendError(
          `Insufficient native currency balance for gas. Requires at least ${ethers.utils.formatEther(transactionCost)}`
        );
      }

      // Proceed with token transfer
      const transferTokenResponse = await ethContract.transfer(receiver_wallet_address, amountInBigNumber, {
        gasLimit: 100000,
        gasPrice: ethers.utils.parseUnits("50", "gwei"),
      });

      transactionReponse = transferTokenResponse.hash;

      let transactionReceipt = await web3.eth.getTransactionReceipt(transferTokenResponse.hash);
      while (transactionReceipt === null) {
        transactionReceipt = await web3.eth.getTransactionReceipt(transferTokenResponse.hash);
      }
    }

    if (!transactionReponse) {
      return res.sendError("Transaction failed, please try again later.");
    }

    inputs = {};

    const responseData = {
      transferTokenResponse: transactionReponse,
      fromWalletAddress: process.env.ADMIN_WALLET_ADDRESS,
    };

    return res.sendResponse(responseData, "Token transfer success");

  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}


/**
 * @method getTransactionDetails
 *
 * @description getTransactionDetails
 *
 * @param req
 *
 * @param res
 *
 * @returns json response
 */

export async function getTransactionDetails(req: any, res: any) {
  try {
    let inputs = req.body;

    if (!inputs.transaction_hash) {
      return res.sendError("Transaction Hash is required");
    }
    if (!inputs.token_type) {
      return res.sendError("Token Type is required");
    }
    if (!inputs.network_type) {
      return res.sendError("Network Type is required");
    }

    const rpcUrl = SUPPORTED_RPC_URLS[SupportedTokens[inputs.network_type]];

    if (!rpcUrl) {
      return res.sendError("Invalid Network Type");
    }

    const web3 = new Web3(rpcUrl);
    const provider = new providers.JsonRpcProvider(rpcUrl);

    let transactionResponse = await web3.eth.getTransaction(inputs.transaction_hash);
    let transReceipt = await web3.eth.getTransactionReceipt(inputs.transaction_hash);

    let transaction_fee, value, token_value, receiver_wallet_address, from_wallet_address;

    if (transactionResponse && transReceipt) {
      let gasPrice:any = transReceipt.effectiveGasPrice ? transReceipt.effectiveGasPrice : 0;
      gasPrice = gasPrice > 0 ? Web3.utils.fromWei(transReceipt.effectiveGasPrice.toString(), "ether") : 0;

      let gasUsed = transReceipt?.gasUsed || 0;
      let gasUsedWei = transactionResponse?.value ? Web3.utils.fromWei(transactionResponse.value.toString(), "ether") : 0;

      from_wallet_address = transactionResponse.from;

      if (["ETH", "POLYGON_AMOY", "BNB", "POL"].includes(inputs.token_type)) {
        // Native currency transfer
        receiver_wallet_address = transactionResponse.to;
        value = gasUsedWei;
      } else {
        // Token Transfer (ERC-20)
        if (transReceipt.logs.length > 0) {
          let contract_address = SPECIAL_CONTRACT_ADDRESS[inputs.network_type][inputs.token_type];

          if(contract_address){
            for (let log of transReceipt.logs) {
              if (log.address.toLowerCase() === contract_address.toLowerCase()) {
                if (log.topics.length >= 3) {
                  // Extract receiver from logs
                  receiver_wallet_address = `0x${log.topics[2].slice(26)}`;
                }
  
                let tokenHexString = log.data ?? "";
                let tokenAmountInWei = web3.utils.hexToNumberString(tokenHexString);
  
                let abi = [
                  "function balanceOf(address owner) view returns (uint256)",
                  "function transfer(address to, uint256 amount) returns (bool)",
                  "function decimals() view returns (uint8)",
                ];
  
                let ethContract = new ethers.Contract(contract_address, abi, provider);
                let decimals = await ethContract.decimals();
                let tokenAmount = parseFloat(tokenAmountInWei) / Math.pow(10, decimals);
  
                token_value = tokenAmount > 0 ? tokenAmount : token_value;
                break; // We found the correct log, no need to continue looping
              } else {
                return res.sendError(`Invalid Token`);
              }
            }
          } else {
            return res.sendError(`Invalid Token`);
          }
        }
      }

      transaction_fee = gasPrice * gasUsed;
    }

    let explorer_url = CHAIN_INFO[SupportedTokens[inputs.network_type]].explorer + "tx/" + inputs.transaction_hash;

    let responseData = {
      transactionResponse,
      transReceipt,
      value: token_value || value,
      transaction_fee,
      explorer_url,
      token_value: token_value || value,
      from_wallet_address,
      receiver_wallet_address,
    };

    return res.sendResponse(responseData, "Transfer Details Fetched");

  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}
export async function generateWalletAddress(req: any, res: any) {
  try {
    let inputs = req.body;

    if (!inputs.user_id) {
      return res.sendError("User Id is required");
    }

    let user_id = inputs.user_id;

    const masterMnemonic = process.env.MASTER_MNEMONIC;

    const masterWallet = ethers.Wallet.fromMnemonic(masterMnemonic);
    const privateKey = masterWallet.privateKey;
    const publicKey = masterWallet.address;

    const hdNode = ethers.utils.HDNode.fromMnemonic(masterMnemonic);
    const childAccountPath = "m/44'/60'/0'/0";
    const childNode = hdNode.derivePath(`${childAccountPath}/${user_id}`);
    const childWallet = new ethers.Wallet(childNode.privateKey);

    let responseData = {
      wallet_address: childWallet.address,
      // privateKey: childWallet.privateKey,
    };

    return res.sendResponse(responseData, "Wallet address generated successfully");

  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}

export async function generateHDWalletAddress(req: any, res: any) {
  try {
    let inputs = req.body;

    if (!inputs.user_id) {
      return res.sendError("User Id is required");
    }

    if (!inputs.account_number) {
      return res.sendError("Account Number is required");
    }

    let { user_id, account_number } = inputs;

    const masterMnemonic = process.env.MASTER_MNEMONIC;

    const masterWallet = ethers.Wallet.fromMnemonic(masterMnemonic);
    const privateKey = masterWallet.privateKey;
    const publicKey = masterWallet.address;

    const hdNode = ethers.utils.HDNode.fromMnemonic(masterMnemonic);
    const childAccountPath = `m/44'/60'/${account_number}'/0`;
    const childNode = hdNode.derivePath(`${childAccountPath}/${user_id}`);
    const childWallet = new ethers.Wallet(childNode.privateKey);

    let responseData = {
      wallet_address: childWallet.address,
      // privateKey: childWallet.privateKey,
    };

    return res.sendResponse(responseData, "Wallet address generated successfully");

  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}

/**
 * @method transferTokensToAdmin
 *
 * @description Transfers tokens to the admin wallet using an HD wallet derived from a userId.
 *
 * @param req
 *
 * @param res
 *
 * @returns json response
 */

export async function transferTokensToAdmin(req: any, res: any) {
  try {

    let inputs = req.body;

    if (!inputs.user_id) {
      return res.sendError("Receiver Wallet Address is required");
    }
    if (!inputs.amount) {
      return res.sendError("Amount is required");
    }
    if (!inputs.token_type) {
      return res.sendError("Token Type is required");
    }
    if (!inputs.network_type) {
      return res.sendError("Network Type is required");
    }

    const rpcUrl = SUPPORTED_RPC_URLS[SupportedTokens[inputs.network_type]];

    if (!rpcUrl) {
      return res.sendError("Invalid Token Type");
    }

    let masterMnemonic = process.env.MASTER_MNEMONIC;

    const masterWallet = ethers.Wallet.fromMnemonic(masterMnemonic);
    const receiver_wallet_address = masterWallet.address;
    const receiver_private_key = masterWallet.privateKey;

    let childNode = ethers.utils.HDNode.fromMnemonic(masterMnemonic).derivePath(`m/44'/60'/0'/0/${inputs.user_id}`);
    let privateKey = childNode.privateKey;
    let childAddress = childNode.address;

    let provider, wallet, contract_address, transactionReponse, estimatedTransactionCost: any;

    let gasLimit = 21000;
    // let gasLimit = 100000;
    let tokens = inputs.amount;

    const web3 = new Web3(rpcUrl);
    let gasPrice: any = ethers.utils.parseUnits(String(40), "gwei");

    let gP = gasPrice.toString();

    const gPAsNumber = parseFloat(gP);
    estimatedTransactionCost = ((gPAsNumber / Math.pow(10, 18)) * gasLimit).toFixed(5).toString();

    console.log("Estimated Transaction Cost", estimatedTransactionCost)

    if (["ETH", "POLYGON_AMOY", "BNB", "POL"].includes(inputs.token_type)) {

      console.log(`Initiating ${inputs.token_type} transfer`);

      let maxTransferableTokens: any = (tokens - estimatedTransactionCost).toFixed(5).toString();
      console.log("Max Transferable Amount", maxTransferableTokens)
      if (maxTransferableTokens < 0) {
        return res.sendError("Transaction fee overshot (fee > amount)");
      }

      // Check if sender has sufficient balance
      const senderBalance:any = await web3.eth.getBalance(childAddress);
      if (web3.utils.toWei(tokens, "ether") > senderBalance) {
        return res.sendError("Insufficient balance in child wallet");
      }

      const createTransaction = await web3.eth.accounts.signTransaction(
        {
          from: childAddress,
          to: receiver_wallet_address,
          value: web3.utils.toWei(maxTransferableTokens, "ether"),
          gasPrice: gasPrice,
          gasLimit: gasLimit,
        },
        privateKey
      );

      await web3.eth.sendSignedTransaction(
        createTransaction.rawTransaction,
        function (error, hash) {
          if (!error) {
            console.log("hash", hash);
            transactionReponse = hash;
          } else {
            return res.sendError(`Transaction failed: ${error.message}`);
          }
        }
      );

    }
    else {

      let abi = [
        "function balanceOf(address owner) view returns (uint256)",
        "function transfer(address to, uint256 amount) returns (bool)",
        "function decimals() view returns (uint8)",
      ];

      let contract_address = SPECIAL_CONTRACT_ADDRESS[inputs.network_type][inputs.token_type];

      provider = new providers.JsonRpcProvider(rpcUrl);

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

      let ethContract = await new ethers.Contract(contract_address, abi, wallet);

      let decimals = await ethContract.decimals();

      const amountInString = ethers.utils.parseUnits(tokens, decimals).toString();

      // Check if receiver wallet address is valid
      if (!utils.isAddress(receiver_wallet_address)) {
        return res.sendError("Invalid receiver wallet address");
      }

      // Check sender's token balance
      const senderTokenBalance = await ethContract.balanceOf(childAddress);
      const tokenBalanceString = ethers.utils.formatUnits(senderTokenBalance, decimals)
      if (parseFloat(tokens) > parseFloat(tokenBalanceString)) {
        return res.sendError("Insufficient token balance in child's wallet");
      }

      // Check sender's native currency balance for gas
      const senderNativeBalance = await provider.getBalance(childAddress);
      const transactionCost = BigNumber.from(100000).mul(ethers.utils.parseUnits(String(50), "gwei"));
      let maxTransactionCost = ethers.utils.formatUnits(transactionCost, 18);

      if (BigNumber.from(senderNativeBalance).lt(transactionCost)) {
        return res.sendError(
          `Insufficient native currency balance for gas. Requires at least ${ethers.utils.formatEther(transactionCost)}`
        );
      }

      // Proceed with token transfer
      let transferTokenResponse = await ethContract.transfer(
        receiver_wallet_address,
        amountInString,
        { gasLimit: 100000, gasPrice: ethers.utils.parseUnits(String(50), "gwei") }
      );

      transactionReponse = transferTokenResponse.hash;
      console.log("transferTokenResponse hash", transferTokenResponse.hash)

      let transactionReceipt = await web3.eth.getTransactionReceipt(
        transferTokenResponse.hash,
        function (error, result) {
          return result;
        }
      );

      while (transactionReceipt === null) {
        transactionReceipt = await web3.eth.getTransactionReceipt(
          transferTokenResponse.hash,
          function (error, result) {
            return result;
          }
        );
      }
    }

    if (!transactionReponse) {
      return res.sendError("Transaction failed, please try again later.");
    }

    inputs = {};

    let responseData = {
      transferTokenResponse: transactionReponse,
      toWalletAddress: receiver_wallet_address,
      fromWalletAddress: childAddress,
    };

    return res.sendResponse(responseData, "Token transfer success");

  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}

function convertToValidJson(str: string) {
  const validJsonStr = str.replace(/([{,]\s*)(\w+)(\s*:)/g, '$1"$2"$3');
  try {
      return JSON.parse(validJsonStr);
  } catch (error) {
      console.error("Invalid JSON format:", error);
      return null;
  }
}

export async function fetchChildWallets(req: any, res: any) {
  try {
    let inputs = req.body;

    if (!inputs.users) {
      return res.sendError("Users list is required");
    }
    if (!inputs.network_type) {
      return res.sendError("Network Type is required");
    }
    if (inputs.password) {
      const decryptedPassword = await decrypt(process.env.ADMIN_PASSWORD_ENCRYPTED!, process.env.ADMIN_PASSWORD_KEY!, process.env.ADMIN_PASSWORD_IV!);
      if (inputs.password !== decryptedPassword) {
        return res.sendError("Invalid Password");
      }
    } else {
      return res.sendError("Password is required");
    }
    if (inputs.verification_code) {
      const decryptedGoogleKey = await decrypt(process.env.GOOGLE_SECRET_ENCRYPTED!, process.env.GOOGLE_SECRET_KEY!, process.env.GOOGLE_SECRET_IV!);

      const is2faValid = speakeasy.totp.verify({
        secret: decryptedGoogleKey,
        encoding: 'base32',
        token: req.body.verification_code,
        window: 1
      });
      if (!is2faValid) {
        return res.sendError("Invalid verification code");
      }
    } else {
      return res.sendError("Verification Code is required");
    }

    const rpcUrl = SUPPORTED_RPC_URLS[SupportedTokens[inputs.network_type]];

    if (!rpcUrl) {
      return res.sendError("Invalid Token Type");
    }

    let masterMnemonic = process.env.MASTER_MNEMONIC;

    const masterWallet = ethers.Wallet.fromMnemonic(masterMnemonic);
    let provider = new providers.JsonRpcProvider(rpcUrl);
    const web3 = new Web3(rpcUrl);

    const users = inputs.users.split(",").map((num: any) => (num.trim()));
    let userWallets = [];

    for (let user of users) {
      try {

        const hdNode = ethers.utils.HDNode.fromMnemonic(masterMnemonic);
        const childAccountPath = "m/44'/60'/0'/0";
        const childNode = hdNode.derivePath(`${childAccountPath}/${user}`);
        const childWallet = new ethers.Wallet(childNode.privateKey);
        let childAddress = childWallet.address;
        let childKey = childNode.privateKey;

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

        const balanceWei = await provider.getBalance(childAddress);
        const senderBalance = await web3.eth.getBalance(childAddress);
        const balance = ethers.utils.formatEther(senderBalance);

        userWallets.push({
          user_id: user.user_id,
          wallet_address: childAddress,
          // private_key: childKey,
          balance: Number(balance),
          symbol: inputs.network_type,
        });

      } catch (error) {
        console.error(`Error processing user ${user.user_id}:`, error);
      }
    }

    return res.sendResponse(userWallets, "HD Wallets details fetched successfully.");

  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}

export async function fetchChildWalletsRange(req: any, res: any) {
  try {
    let inputs = req.body;

    if (!inputs.skip) {
      return res.sendError("Skip is required");
    }
    if (!inputs.take) {
      return res.sendError("Take is required");
    }
    if (!inputs.network_type) {
      return res.sendError("Network Type is required");
    }
    if (inputs.password) {
      const decryptedPassword = await decrypt(process.env.ADMIN_PASSWORD_ENCRYPTED!, process.env.ADMIN_PASSWORD_KEY!, process.env.ADMIN_PASSWORD_IV!);
      if (inputs.password !== decryptedPassword) {
        return res.sendError("Invalid Password");
      }
    } else {
      return res.sendError("Password is required");
    }
    if (inputs.verification_code) {
      const decryptedGoogleKey = await decrypt(process.env.GOOGLE_SECRET_ENCRYPTED!, process.env.GOOGLE_SECRET_KEY!, process.env.GOOGLE_SECRET_IV!);

      const is2faValid = speakeasy.totp.verify({
        secret: decryptedGoogleKey,
        encoding: 'base32',
        token: req.body.verification_code,
        window: 1
      });
      if (!is2faValid) {
        return res.sendError("Invalid verification code");
      }
    } else {
      return res.sendError("Verification Code is required");
    }

    const rpcUrl = SUPPORTED_RPC_URLS[SupportedTokens[inputs.network_type]];

    if (!rpcUrl) {
      return res.sendError("Invalid Token Type");
    }

    let masterMnemonic = process.env.MASTER_MNEMONIC;

    const masterWallet = ethers.Wallet.fromMnemonic(masterMnemonic);
    let provider = new providers.JsonRpcProvider(rpcUrl);
    const web3 = new Web3(rpcUrl);

    const rangeArray = (skip:any, take:any) => Array.from({ length: Number(take) }, (_, i) => Number(skip) + i + 1);

    const users = rangeArray(inputs.skip, inputs.take);
    let userWallets = [];
    
    const ERC20_ABI = [
      "function balanceOf(address owner) view returns (uint256)",
      "function decimals() view returns (uint8)"
    ];

    let usdt_contract_address = SPECIAL_CONTRACT_ADDRESS[inputs.network_type]["USDT"];
    let usdc_contract_address = SPECIAL_CONTRACT_ADDRESS[inputs.network_type]["USDC"];

    let usdt_contract = new ethers.Contract(usdt_contract_address, ERC20_ABI, provider);
    let usdc_contract = new ethers.Contract(usdc_contract_address, ERC20_ABI, provider);

    for (let user of users) {
      try {

        const hdNode = ethers.utils.HDNode.fromMnemonic(masterMnemonic);
        const childAccountPath = "m/44'/60'/0'/0";
        const childNode = hdNode.derivePath(`${childAccountPath}/${user}`);
        const childWallet = new ethers.Wallet(childNode.privateKey);
        let childAddress = childWallet.address;
        let childKey = childNode.privateKey;

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

        const balanceWei = await provider.getBalance(childAddress);
        const senderBalance = await web3.eth.getBalance(childAddress);
        const balance = ethers.utils.formatEther(senderBalance);
        let usdt_balance = 0, usdc_balance = 0;
        let TOKEN_DECIMALS = 6;

        if(usdc_contract){
          const rawBalance = await usdc_contract.balanceOf(childAddress);
          usdc_balance = Number(ethers.utils.formatUnits(rawBalance, TOKEN_DECIMALS));
        }
        if(usdt_contract){
          const rawBalance = await usdt_contract.balanceOf(childAddress);
          usdt_balance = Number(ethers.utils.formatUnits(rawBalance, TOKEN_DECIMALS));
        }

        userWallets.push({
          user_id: user,
          wallet_address: childAddress,
          // private_key: childKey,
          balance: Number(balance),
          usdt_balance: Number(usdt_balance),
          usdc_balance: Number(usdc_balance),
        });

      } catch (error) {
        console.error(`Error processing user ${user}:`, error);
      }
    }

    return res.sendResponse(userWallets, "HD Wallets details fetched successfully.");

  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}

async function transferWithGasEstimation(sender:any, recipient:any, amount:any, privateKey:any, web3Provider: any) {
    try {
        const web3 = new Web3(web3Provider);
        const account = web3.eth.accounts.privateKeyToAccount(privateKey);
        web3.eth.accounts.wallet.add(account);

        // Estimate gas
        const gasPrice = await web3.eth.getGasPrice();
        const gasLimit = await web3.eth.estimateGas({
            from: sender,
            to: recipient,
            value: web3.utils.toWei(amount.toString(), 'ether')
        });

        // Calculate gas fee
        const gasFee = BigInt(gasPrice) * BigInt(gasLimit);

        // Get sender balance
        const balance = BigInt(await web3.eth.getBalance(sender));

        // Ensure there is enough balance for transaction after gas deduction
        if (balance < gasFee) {
            throw new Error('Insufficient balance to cover gas fees');
        }

        // Calculate final transfer amount
        const finalAmount = balance - gasFee;

        if (finalAmount <= 0) {
            throw new Error('Insufficient balance after gas deduction');
        }

        // Create and send transaction
        const tx = {
            from: sender,
            to: recipient,
            value: finalAmount.toString(),
            gas: gasLimit,
            gasPrice
        };

        const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey);
        const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);

        console.log('Transaction successful:', receipt.transactionHash);
        return receipt;
    } catch (error) {
        console.error('Transaction failed:', error.message);
        throw error;
    }
}


Back to Directory File Manager