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

import axios from "axios";
import * as bitcoin from "bitcoinjs-lib";
import * as bip39 from "bip39";
import * as crypto from "crypto";
import * as ecc from "tiny-secp256k1";
import BIP32Factory from "bip32";
import { BITCOIN_HD_WALLET_STRING } from "@config/constant";

const getNetwork = (networkType: "mainnet" | "testnet") => {
  return networkType === "mainnet"
    ? bitcoin.networks.bitcoin
    : bitcoin.networks.testnet;
};

function getDeterministicMnemonic(userId: string): string {
  const hash = crypto
    .createHash("sha256")
    .update(BITCOIN_HD_WALLET_STRING + userId)
    .digest("hex");
  const entropy = hash.substring(0, 32);
  return bip39.entropyToMnemonic(entropy);
}

export const generateBitcoinHDWallet = async (req: any, res: any) => {
  try {
    let inputs = req.body;

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

    let { user_id } = inputs;
    let networkType: any =
      process.env.BITCOIN_NETWORK_TYPE === "mainnet" ? "mainnet" : "testnet";
    const network = getNetwork(networkType);

    const mnemonic = getDeterministicMnemonic(user_id);
    const seed = bip39.mnemonicToSeedSync(mnemonic);
    const bip32 = BIP32Factory(ecc);
    const root = bip32.fromSeed(seed, network);

    // BIP84 - Native SegWit
    const purpose = 84;
    const coinType = networkType === "mainnet" ? 0 : 1;

    // const path = `m/${purpose}'/${coinType}'/0'/0/${user_id}`;
    const path = `m/${purpose}'/${coinType}'/0'/0/0`; // Standard dervivation 

    const child = root.derivePath(path);

    const { address } = bitcoin.payments.p2wpkh({
      pubkey: Buffer.from(child.publicKey),
      network,
    });

    let walletDetails = {
      wallet_address: address,
      // privateKey: child.toWIF(),
      // mnemonic,
      // path,
      // publicKey: child.publicKey.toString(),
    };

    return res.sendResponse(
      walletDetails,
      "Bitcoin Address generated successfully."
    );
  } catch (error: any) {
    if (error?.message) {
      return res.sendError(error.message);
    }
  }
};

export const generateBitcoinLegacyHDWallet = async (req: any, res: any) => {
  try {
    let inputs = req.body;

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

    let { user_id } = inputs;
    let networkType: any =
      process.env.BITCOIN_NETWORK_TYPE === "mainnet" ? "mainnet" : "testnet";
    const network = getNetwork(networkType);

    const mnemonic = getDeterministicMnemonic(user_id);
    const seed = bip39.mnemonicToSeedSync(mnemonic);
    const bip32 = BIP32Factory(ecc);
    const root = bip32.fromSeed(seed, network);

    // BIP44 - Legacy Bitcoin
    const purpose = 44;
    const coinType = networkType === "mainnet" ? 0 : 1;

    const path = `m/${purpose}'/${coinType}'/0'/0/0`; // Standard dervivation 
    const child = root.derivePath(path);

    const { address } = bitcoin.payments.p2pkh({
      pubkey: Buffer.from(child.publicKey),
      network,
    });

    let walletDetails = {
      wallet_address: address,
      // privateKey: child.toWIF(),
      // mnemonic,
    };

    return res.sendResponse(
      walletDetails,
      "Bitcoin Address generated successfully."
    );
  } catch (error: any) {
    if (error?.message) {
      return res.sendError(error.message);
    }
  }
};

export const getWalletDetails = async (req: any, res: any) => {
  try {
    const { address } = req.body;

    if (!address) return res.sendError("Wallet address is required");

    const networkType: "mainnet" | "testnet" =
      process.env.BITCOIN_NETWORK_TYPE === "mainnet" ? "mainnet" : "testnet";

    const baseURL =
      networkType === "mainnet"
        ? "https://mempool.space/api"
        : "https://mempool.space/testnet4/api";

    // 1. Fetch basic wallet info
    const addressRes = await axios.get(`${baseURL}/address/${address}`); 

    if (!addressRes) {
      return res.sendError("Failed to fetch wallet address info");
    }

    const addressData = await addressRes.data;

    if (!addressData) {
      return res.sendError("Invalid wallet address");
    }

    const confirmedBalance = addressData.chain_stats.funded_txo_sum - addressData.chain_stats.spent_txo_sum;
    const unconfirmedBalance = addressData.mempool_stats.funded_txo_sum - addressData.mempool_stats.spent_txo_sum;
    const totalBalance = confirmedBalance + unconfirmedBalance;
    const txCount = addressData.chain_stats.tx_count + addressData.mempool_stats.tx_count;

    // 2. Fetch recent transactions
    let transactions:any = [];
    const txsRes = await fetch(`${baseURL}/address/${address}/txs`, {
      method: 'GET',
    });

    if (!txsRes.ok) {
      return res.sendError("Failed to fetch transactions");
    }

    const txData = await txsRes.json();

    if (txData && Array.isArray(txData)) {
      transactions = txData.map((tx: any) => {
        return {
          txid: tx.txid,
          fee: tx.fee,
          status: tx.status,
          inputs: tx.vin?.map((input: any) => {
            return {
              address: input.prevout?.scriptpubkey_address,
              value: (
                parseFloat(input.prevout.value) / Math.pow(10, 8)
              ).toFixed(6),
            };
          }),
          outputs: tx.vout?.map((output: any) => {
            return {
              address: output.scriptpubkey_address,
              value: (parseFloat(output.value) / Math.pow(10, 8)).toFixed(6),
            };
          }),
        };
      });
    }

    const result = {
      address,
      confirmed_balance: (confirmedBalance / Math.pow(10, 8)).toFixed(6),
      unconfirmed_balance: (unconfirmedBalance / Math.pow(10, 8)).toFixed(6),
      total_balance: (totalBalance / Math.pow(10, 8)).toFixed(6),
      tx_count: txCount,
      transactions,
    };

    return res.sendResponse(result, "Wallet details fetched successfully.");
  } catch (error: any) {
    console.error("Error fetching wallet details:", error.message || error);
    return res.sendError("Failed to fetch wallet details");
  }
};


Back to Directory File Manager