Integrating Bitcoin Wallet Functionality

Lesson 4

Revisiting the previous chapters, we successfully set up our development environment, implemented the user authentication functionality of our Crypto Portfolio Tracker app, and are ready to take the next significant step: integrating Bitcoin wallet functionality. As the pioneer and most widely recognized cryptocurrency, Bitcoin holds a crucial position in the crypto ecosystem. By incorporating Bitcoin support, we empower users to monitor their wallet balances directly within our app, providing a comprehensive view of their crypto portfolio.

Objective

The primary objective of this lesson is to integrate Bitcoin wallet functionality into our Crypto Portfolio Tracker app. We will explore how to fetch Bitcoin balance data using blockchain explorer APIs, set up test wallets for hands-on experience, and adapt our dashboard to dynamically fetch data for any cryptocurrencies the user configures. By the end of this lesson, you will have a solid understanding of how to securely and dynamically retrieve Bitcoin wallet balances and display them within your application.

Setting Up Axios for API Requests

To interact with the blockchain explorer APIs, we will use Axios, a popular JavaScript library, to request HTTP. We will install Axios in our project and configure it to send requests to the chosen API endpoints. Axios simplifies sending HTTP requests and handling responses, making it an ideal choice for our Bitcoin integration.

npm install axios

Understanding Bitcoin APIs

To retrieve Bitcoin wallet balances, we will leverage blockchain explorer APIs. These APIs provide convenient access to Bitcoin blockchain data, allowing us to fetch wallet balances and transaction information without running our own full node. We will explore popular blockchain explorers like Blockchain.info and BlockCypher, which offer well-documented APIs for retrieving Bitcoin wallet data.

import axios from "axios";
import { BitcoinBalanceResponse } from "../../types/cryptoTypes";

export async function fetchBitcoinBalance(address: string) {
  const btcTesnetUrl = `https://api.blockcypher.com/v1/btc/test3/addrs/${address}/balance`;
  // this is blockcypher's own test network
  // const blockCypherTestUrl = `https://api.blockcypher.com/v1/byc/test/addrs/${address}/balance`;
  try {
    const response = await axios.get<BitcoinBalanceResponse>(btcTesnetUrl);
    return response.data.balance || 0;
  } catch (error) {
    throw new Error(`Unable to get balance for address: ${address}`);
  }
}

Enhancing Wallet Routes

We will strengthen our wallet routes to facilitate secure and dynamic retrieval of cryptocurrency balances. We will develop walletRoutes.ts and dashboardRoutes.ts to securely handle the fetching of Bitcoin balances. These routes will utilize the Axios library to make requests to the blockchain explorer APIs and retrieve the necessary wallet information.

import express from "express";
import fetchBalanceFunctions from "../utils/fetchBalanceFunctions";
import User from "../models/User";

const router = express.Router();

// Route for fetching the user's cryptocurrency balance
router.post("/:cryptocurrency", async (request, response) => {
  if (!request.isAuthenticated()) {
    return response.status(401).json({ message: "Unauthorized" });
  }

  const { cryptocurrency } = request.params;
  const { address } = request.body;
  const fetchBalance =
    fetchBalanceFunctions[cryptocurrency as keyof typeof fetchBalanceFunctions];

  if (!fetchBalance) {
    return response
      .status(400)
      .json({ message: "Unsupported cryptocurrency type" });
  }

  try {
    const balance = await fetchBalance(address);
    response.json({ balance });
  } catch (error) {
    if (error instanceof Error) {
      return response
        .status(500)
        .json({ message: "Error fetching balance", error: error.message });
    }
  }
});

// Rout for updating the user's cryptocurrency addresses
router.put("/:cryptocurrency", async (request, response) => {
  if (!request.isAuthenticated()) {
    return response.status(401).json({ message: "Unauthorized" });
  }

  const { cryptocurrency } = request.params;
  const { address } = request.body;

  if (!address || !cryptocurrency) {
    return response
      .status(400)
      .json({ message: "Please provide both address and cryptocurrency" });
  }

  try {
    // Update the user's cryptocurrency address
    const updatedUser = await User.findByIdAndUpdate(
      request.user.id,
      { [`${cryptocurrency}Address`]: address },
      { new: true }
    );
    return response.json({ message: "Address updated", user: updatedUser });
  } catch (error) {
    return response
      .status(500)
      .json({ message: "Error updating address", error });
  }
});

export default router;

Setting Up a Map to Retrieve the Correct Fetch Balance Function

To streamline balance retrieval, we'll create a fetchBalanceFunctions.ts file. This file maps cryptocurrency types to their fetching functions, enabling easy expansion to support more cryptocurrencies.

import { fetchBitcoinBalance } from "./fetchWalletData";

const fetchBalanceFunctions = {
  bitcoin: fetchBitcoinBalance,
  // Future cryptocurrencies and their fetch functions can be added here
};

export default fetchBalanceFunctions;

Secure and Dynamic Data Fetching

Security is paramount when dealing with sensitive financial data. To ensure secure authentication and authorization, we will leverage Passport.js, a popular authentication middleware for Node.js. Passport.js will handle user authentication and provide a safe mechanism for accessing user-specific data.

Furthermore, we will implement dynamic data fetching in dashboardRoutes.ts to retrieve wallet balances for the cryptocurrencies associated with each user. This dynamic approach allows users to track multiple cryptocurrencies seamlessly within our application.

import express from "express";
import fetchBalanceFunctions from "../utils/fetchBalanceFunctions";
import { CryptoType, DashboardData } from "../../types/cryptoTypes";

const router = express.Router();

router.get("/", async (request, response) => {
  if (!request.isAuthenticated()) {
    return response.redirect("/");
  }

  const balancePromises = Object.entries(fetchBalanceFunctions).map(
    async ([cryptoType, fetchBalance]) => {
      const address =
        request.user[`${cryptoType}Address` as keyof typeof request.user];

      if (address) {
        const balance = await fetchBalance(address);
        return {
          cryptoType,
          balance: balance || 0,
        };
      }

      return {
        cryptoType,
        balance: 0,
      };
    }
  );

  try {
    const balances = await Promise.all(balancePromises);
    const dashboardData = balances.reduce((acc, accountData) => {
      const { cryptoType, balance } = accountData;
      acc[cryptoType as CryptoType] = { balance };
      return acc;
    }, {} as DashboardData);

    response.json(dashboardData);
  } catch (error) {
    response.status(500).json({ error: "Failed to fetch wallet data" });
  }
});

export default router;

Setting Up Test Wallets

To gain practical experience with cryptocurrency wallets, we will provide instructions on setting up test wallets using popular platforms like BlockCypher and Electrum. These test wallets will allow you to explore the functionality of Bitcoin wallets, send and receive transactions, and familiarize yourself with the concepts of addresses and private keys.

[Instructions: Step-by-step guide on setting up test wallets using BlockCypher and Electrum]

Update the server.ts

import express from "express";
import session from "express-session";
import mongoose from "mongoose";
import dotenv from "dotenv";
import passport from "passport";
import configurePassport from "./config/passportConfig";
import authRoutes from "./routes/authRoutes";
import walletRoutes from "./routes/walletRoutes";
import dashboardRoutes from "./routes/dashboardRoutes";

dotenv.config();

// Connect to MongoDB

mongoose
  .connect(process.env.MONGODB_URI as string)
  .then(() => console.log("Connected to MongoDB\n"))
  .catch((err) => console.log("MongoDB connection error", err));

// Create Express server

const app = express();
const port = 3000;

app.use(express.json()); // Parse JSON request body

app.use(
  session({
    secret: process.env.SESSION_SECRET as string,
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false }, // In production, set to true to ensure cookies are only sent over HTTPS
  })
); // Session middleware

app.use(passport.initialize());
app.use(passport.session());
configurePassport(passport);

app.get("/", (_request, response) => {
  response.send("Login or register"); // this will display the home page
});

// Use authRoutes to authenticate users
app.use("/auth", authRoutes);
// Use dashboardRoutes
app.use("/dashboard", dashboardRoutes);
// Use walletRoutes
app.use("/wallet", walletRoutes);

app.listen(port, () => {
  console.log(`\nExpress server is running at http://localhost:${port}`);
});

Conclusion

In this chapter, we have covered the integration of Bitcoin wallet functionality into our Crypto Portfolio Tracker app. We can securely and dynamically fetch Bitcoin wallet balances by leveraging blockchain explorer APIs and utilizing Axios for API requests. We have also emphasized the importance of secure authentication using Passport.js and provided guidance on setting up test wallets for hands-on experience.

With the completion of this lesson, you have gained a solid foundation in integrating Bitcoin wallet functionality into your application. You are now equipped with the knowledge and skills to securely retrieve and display Bitcoin balances, enhancing the user experience and value of your Crypto Portfolio Tracker app.

Next Steps

In Lesson 5, we will expand our application's capabilities by introducing Ethereum wallet functionality. This will enable users to track multiple cryptocurrencies, further enhancing the versatility and usefulness of our Crypto Portfolio Tracker app.

Stay tuned for an exciting exploration of Ethereum integration!

Last updated