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

import config from "config";
import BigNumber from "bignumber.js";
import { 
	CHAIN_INFO, 
	SupportedTokens,
  SPECIAL_CONTRACT_ADDRESS,
 } from "../../config/chains";
import TronWeb from "tronweb";
import * as bip39 from "bip39";
import hdkey from "hdkey";

require('dotenv').config();
import { decrypt } from '../../utils/decrypt';
var FCM = require('fcm-node');
var serverKey = config.get("FCM_SERVER_KEY");
import speakeasy from 'speakeasy';

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


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

    let inputs = req.body;
    // console.log("inputs", inputs)
		
    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");
    }

    // const rpcUrl = process.env.NODE_ENV == "production" ? "https://api.trongrid.io" : "https://api.shasta.trongrid.io";
    const rpcUrl = inputs.network_type == "TRX" ? "https://api.trongrid.io" : inputs.network_type == "SHASTA" ? "https://api.shasta.trongrid.io" : "https://nile.trongrid.io";

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

    // Create a connection to the Tron devnet

    const tronWeb = new TronWeb({
      fullHost: rpcUrl,
      headers: { "TRON-PRO-API-KEY": process.env.TRON_PRO_API_KEY },
      privateKey: process.env.ADMIN_PRIVATE_KEY,
    });

    console.log("Connection Tron @", rpcUrl)

    let tx;
    let responseData;
    let tokensInSUN = Math.round(inputs.tokens * 1e6); // Convert TRX to SUN and round

    if(!["USDC", "USDT"].includes(inputs.token_type)){
      console.log("Initiating Native Currency Transfer Transaction", tokensInSUN)
      tx = await tronWeb.trx.sendTransaction(inputs.receiver_wallet_address, tokensInSUN);
      if(tx.txid){
        responseData = {
          transferTokenResponse: tx.txid,
          fromWalletAddress: tronWeb.defaultAddress.base58,
        };
      }
    } else {

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

      const contract = await tronWeb.contract().at(contract_address);
      tx = await contract.transfer(inputs.receiver_wallet_address, tokensInSUN).send();
      if(tx){
        responseData = {
          transferTokenResponse: tx,
          fromWalletAddress: tronWeb.defaultAddress.base58,
        };
      }
    }

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

  } catch (error) {
    return res.sendError(error.message);
  }
}

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

export async function getTronTransactionDetails(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 = inputs.network_type == "TRX" ? "https://api.trongrid.io" : inputs.network_type == "SHASTA" ? "https://api.shasta.trongrid.io" : "https://nile.trongrid.io";

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

    // Create a connection to the Tron devnet

    const tronWeb = new TronWeb({
      fullHost: rpcUrl,
      headers: { "TRON-PRO-API-KEY": process.env.TRON_PRO_API_KEY },
      privateKey: process.env.ADMIN_PRIVATE_KEY,
    });

    const transactionInfo = await tronWeb.trx.getTransaction(inputs.transaction_hash);
    const transactionReceipt = await tronWeb.trx.getTransactionInfo(inputs.transaction_hash);

    let value = 0;
    let tokenValue;
    let receiverWalletAddress = '';
    let fromWalletAddress = '';
    let explorer_url = CHAIN_INFO[SupportedTokens[inputs.network_type]].explorer + "/#/transaction/" + inputs.transaction_hash;
    let transactionFee = 0;
    let status;

    if (transactionInfo && transactionReceipt && transactionInfo.ret) {
      fromWalletAddress = tronWeb.address.fromHex(transactionInfo.raw_data.contract[0].parameter.value.owner_address);
      
      transactionFee = tronWeb.fromSun(transactionReceipt.fee)
      status = transactionInfo.ret[0].contractRet;
      // console.log("receive add", tronWeb.address.fromHex(transactionReceipt.log[0].address));
      // Extracting value for native TRX transfers
      if (transactionInfo.raw_data.contract[0].type === "TransferContract") {
        receiverWalletAddress = tronWeb.address.fromHex(transactionInfo.raw_data.contract[0].parameter.value.to_address);
        value = tronWeb.fromSun(transactionInfo.raw_data.contract[0].parameter.value.amount); // Convert from SUN to TRX
      } else if (transactionInfo.raw_data.contract[0].type === "TriggerSmartContract") {

        const { parameter } = transactionInfo.raw_data.contract[0];
        const { value } = parameter;
        const { data } = value;

        // Decode the receiver address
        const receiverAddressHex = "0x" + data.substring(10, 74).substring(24);
        // receiverWalletAddress = tronWeb.address.fromHex(receiverAddressHex);

        if (!transactionReceipt.log || transactionReceipt.log.length === 0) {
          throw new Error("No logs found in the transaction receipt.");
        }
    
        // Extract the first log (assuming it's the Transfer event log)
        const transferLog = transactionReceipt.log[0];
    
        // Decode the `topics` to get the `to_address`
        const receiverHex = "0x" + transferLog.topics[2].slice(24); 
        receiverWalletAddress = tronWeb.address.fromHex(receiverHex);

      }

      // Extracting token transfer value for TRC20 tokens (e.g., USDT, USDC) if applicable
      if (transactionInfo.raw_data.contract[0].type === "TriggerSmartContract") {
        const contractData = transactionInfo.raw_data.contract[0].parameter.value.data;

        // Getting decimals dynamically from the token contract
        let contract_address = SPECIAL_CONTRACT_ADDRESS[inputs.network_type][inputs.token_type];
        
        // tokenValue = contract_address
        const tokenContract = await tronWeb.contract().at(contract_address);

        if(tokenContract){
          // const decimals = await tokenContract.methods.decimals().call();
          const decimals = 6;
          const tokenAmountHex = contractData.substring(72);
          const tokenAmountBigNum = new BigNumber("0x" + tokenAmountHex); 
          tokenValue = tokenAmountBigNum.dividedBy(new BigNumber(10).pow(decimals)).toString();
        } else {
          return res.sendError("Token contract not found");
        }

      }
    }

    let responseData = {
      transactionReponse: transactionInfo,
      transReceipt: transactionReceipt,
      value: tokenValue || value,
      transaction_fee: transactionFee,
      explorer_url: explorer_url,
      token_value: tokenValue || value,
      from_wallet_address: fromWalletAddress,
      receiver_wallet_address: receiverWalletAddress,      
      status: status,
    };

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

  } catch (error) {
    return res.sendError(error.message);
  }
}

export async function generateTronWalletAddress(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;

    if (!masterMnemonic) {
      return res.sendError("Master Mnemonic not found in environment variables.");
    }

    // Generate seed from mnemonic
    const seed = await bip39.mnemonicToSeed(masterMnemonic);

    // Create root node
    const root = hdkey.fromMasterSeed(seed);

    // Tron HD Wallet Path: "m/44'/195'/0'/0/{user_id}"
    const childAccountPath = `m/44'/195'/0'/0/${user_id}`;
    const childNode = root.derive(childAccountPath);

    // Generate private key & address
    const privateKeyHex = childNode.privateKey.toString("hex");

    const rpcUrl = process.env.NODE_ENV == "production" ? "https://api.trongrid.io" : "https://api.shasta.trongrid.io";
    
    const tronWeb = new TronWeb({
      fullHost: rpcUrl,
      headers: { "TRON-PRO-API-KEY": process.env.TRON_PRO_API_KEY },
      privateKey: process.env.ADMIN_PRIVATE_KEY,
    });

    const walletAddress = tronWeb.address.fromPrivateKey(privateKeyHex);

    let responseData = {
      wallet_address: walletAddress,
      // privateKey: privateKeyHex,
    };

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

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

export async function generateTronHDWalletAddress(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;

    if (!masterMnemonic) {
      return res.sendError("Master Mnemonic not found in environment variables.");
    }

    // Generate seed from mnemonic
    const seed = await bip39.mnemonicToSeed(masterMnemonic);

    // Create root node
    const root = hdkey.fromMasterSeed(seed);

    // Tron HD Wallet Path: "m/44'/195'/0'/0/{user_id}"
    const childAccountPath = `m/44'/195'/${account_number}'/0/${user_id}`;
    const childNode = root.derive(childAccountPath);

    // Generate private key & address
    const privateKeyHex = childNode.privateKey.toString("hex");

    const rpcUrl = process.env.NODE_ENV == "production" ? "https://api.trongrid.io" : "https://api.shasta.trongrid.io";
    
    const tronWeb = new TronWeb({
      fullHost: rpcUrl,
      headers: { "TRON-PRO-API-KEY": process.env.TRON_PRO_API_KEY },
      privateKey: process.env.ADMIN_PRIVATE_KEY,
    });

    const walletAddress = tronWeb.address.fromPrivateKey(privateKeyHex);

    let responseData = {
      wallet_address: walletAddress,
      // privateKey: privateKeyHex,
    };

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

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

export async function transferTronTokensToAdmin(req:any, res:any) {

  const MASTER_MNEMONIC = process.env.MASTER_MNEMONIC;
  const ADMIN_WALLET_PATH = "m/44'/195'/0'/0";

  try {
    let { user_id, amount, token_type, network_type } = req.body;
    let tronWeb;

    if (!user_id) return res.sendError("User ID is required");
    if (!amount) return res.sendError("Amount is required");
    if (!token_type) return res.sendError("Token Type is required");
    if (!network_type) return res.sendError("Network Type is required");

    const rpcUrl = process.env.NODE_ENV == "production" ? "https://api.trongrid.io" : "https://api.shasta.trongrid.io";

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

    tronWeb = new TronWeb({
      fullHost: rpcUrl,
      headers: { "TRON-PRO-API-KEY": process.env.TRON_PRO_API_KEY },
    });

    // Generate Admin Wallet from Master Mnemonic
    const masterNode = tronWeb.utils.HDNode.fromMnemonic(MASTER_MNEMONIC);
    const adminNode = masterNode.derivePath(`${ADMIN_WALLET_PATH}/0`);
    const adminAddress = tronWeb.address.fromPrivateKey(adminNode.privateKey);

    // Generate Child Wallet from Master Mnemonic using user_id
    const childNode = masterNode.derivePath(`${ADMIN_WALLET_PATH}/${user_id}`);
    const childPrivateKey = childNode.privateKey;
    const childAddress = tronWeb.address.fromPrivateKey(childPrivateKey);

    tronWeb = new TronWeb({
      fullHost: rpcUrl,
      headers: { "TRON-PRO-API-KEY": process.env.TRON_PRO_API_KEY },
      privateKey: childPrivateKey,
    });

    // Check if the child address has enough balance
    const balance = await tronWeb.trx.getBalance(childAddress);
    const balanceInTRX = tronWeb.fromSun(balance);

    if (!["USDC", "USDT"].includes(token_type)) {

      const estimatedFee = 1; // Approximate TRX fee
      const transferableAmount = parseFloat(amount) - estimatedFee;

      if (transferableAmount <= 0) {
        return res.sendError("Transaction fee overshot (fee > amount)");
      }

      if (balanceInTRX < amount) {
        return res.sendError("Insufficient TRX balance in child wallet");
      }

      // Transfer TRX to Admin Wallet
      const signedTx = await tronWeb.trx.sign(
        await tronWeb.trx.sendTransaction(adminAddress, tronWeb.toSun(transferableAmount)),
        childPrivateKey
      );
      const broadcast = await tronWeb.trx.sendRawTransaction(signedTx);

      return res.sendResponse(
        { txID: broadcast.txid, from: childAddress, to: adminAddress },
        "TRX transfer successful"
      );
    } else {
      
      // Transfer TRC-20 Tokens (e.g., USDT, USDC)
      let tokenContract = SPECIAL_CONTRACT_ADDRESS[network_type][token_type];
      const tokenInstance = await tronWeb.contract().at(tokenContract);

      const decimals = await tokenInstance.methods.decimals().call();
      const tokenAmount = tronWeb.toSun(amount, decimals);

      // Check Token Balance
      const childTokenBalance = await tokenInstance.methods.balanceOf(childAddress).call();
      if (BigInt(childTokenBalance) < BigInt(tokenAmount)) {
        return res.sendError("Insufficient token balance in child wallet");
      }

      // Transfer TRC-20 Token
      const tx = await tokenInstance.methods.transfer(adminAddress, tokenAmount).send({
        from: childAddress,
      });

      return res.sendResponse(
        { txID: tx, from: childAddress, to: adminAddress },
        "Token transfer successful"
      );
    }
  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}

export async function fetchTronChildWallets(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 = process.env.NODE_ENV == "production" ? "https://api.trongrid.io" : "https://api.shasta.trongrid.io";

    const tronWeb = new TronWeb({
      fullHost: rpcUrl,
      headers: { "TRON-PRO-API-KEY": process.env.TRON_PRO_API_KEY },
      privateKey: process.env.ADMIN_PRIVATE_KEY,
    });

    const masterMnemonic = process.env.MASTER_MNEMONIC;
    if (!masterMnemonic) {
      return res.sendError("Master Mnemonic not found in environment variables.");
    }

    // Convert mnemonic to seed
    const seed = await bip39.mnemonicToSeed(masterMnemonic);
    const root = hdkey.fromMasterSeed(seed);

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

    for (let user of users) {
      try {
        // Derive child wallet
        const childAccountPath = `m/44'/195'/0'/0/${user}`;
        const childNode = root.derive(childAccountPath);
        const privateKeyHex = childNode.privateKey.toString("hex");
        const walletAddress = tronWeb.address.fromPrivateKey(privateKeyHex);

        // Fetch wallet balance
        const balanceSun = await tronWeb.trx.getBalance(walletAddress);
        const balance = tronWeb.fromSun(balanceSun);

        userWallets.push({
          user_id: user,
          wallet_address: walletAddress,
          // private_key: privateKeyHex,
          balance: Number(balance),
          symbol: "TRX",
        });
      } catch (error) {
        console.error(`Error processing user ${user}:`, error);
      }
    }

    return res.sendResponse(userWallets, "Tron HD Wallets details fetched successfully.");
  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}

export async function fetchTronChildWalletsRange(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 = process.env.NODE_ENV == "production" ? "https://api.trongrid.io" : "https://api.shasta.trongrid.io";

    const tronWeb = new TronWeb({
      fullHost: rpcUrl,
      headers: { "TRON-PRO-API-KEY": process.env.TRON_PRO_API_KEY },
      privateKey: process.env.ADMIN_PRIVATE_KEY,
    });

    const masterMnemonic = process.env.MASTER_MNEMONIC;
    if (!masterMnemonic) {
      return res.sendError("Master Mnemonic not found in environment variables.");
    }

    // Convert mnemonic to seed
    const seed = await bip39.mnemonicToSeed(masterMnemonic);
    const root = hdkey.fromMasterSeed(seed);

    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 = [];

    for (let user of users) {
      try {
        // Derive child wallet
        const childAccountPath = `m/44'/195'/0'/0/${user}`;
        const childNode = root.derive(childAccountPath);
        const privateKeyHex = childNode.privateKey.toString("hex");
        const walletAddress = tronWeb.address.fromPrivateKey(privateKeyHex);

        // Fetch wallet balance
        // const balanceSun = await tronWeb.trx.getBalance(walletAddress);
        // const balance = tronWeb.fromSun(balanceSun);

        userWallets.push({
          user_id: user,
          wallet_address: walletAddress,
          // private_key: privateKeyHex,
          // balance: Number(balance),
          symbol: "TRX",
        });
      } catch (error) {
        console.error(`Error processing user ${user}:`, error);
      }
    }

    return res.sendResponse(userWallets, "Tron HD Wallets details fetched successfully.");
  } catch (error) {
    return res.sendError(`Error: ${error.message}`);
  }
}
Back to Directory File Manager