Creating a Bitcoin Wallet in Node.js, Part 1: Legacy Wallet

Barry Lavides
8 min readJun 1, 2023

--

The goal of this tutorial series is to help beginner programmers develop a basic Bitcoin wallet. I chose javascript because in my opinion, it is the easiest to learn and the most popular programming language. Aside from a Bitcoin wallet, I’m also planning to do a Lightning Network series next, still in Node.js. And then eventually another series converting everything to Rust, the problem with Rust is that it has a very steep learning curve so I wanted to start with an easy language first so that it won’t be very overwhelming for beginner programmers. The concept of Bitcoin and how it works is hard enough to understand, so I hope developing it in javascript can help understanding it easier.

Series topics (tentative)

  • Part 1: Legacy Wallet
  • Part 2: Creating Transactions
  • Part 3: HD and Multi-sig Wallet
  • Part 4: op_return
  • Part 5: Graphical User Interface (mobile app or browser extension)

Intended audience

This tutorial assumes that you already know how to code basic javascript and Node.js.

What we will discuss in this article

  • Custodial vs Self-custodial wallets
  • Project Setup
  • Generate a Legacy Address
  • Types of Bitcoin Networks
  • Bitcoin Faucet
  • UTXO vs Account-Based Model
  • Checking your address’ balance using code

Custodial vs Self-custodial wallets

All Bitcoin 1) address has a corresponding 2) private key, this private key is used to send Bitcoin. Private key is the first layer of authentication in Bitcoin. By default, you can already send Bitcoin by just doing the first layer of authentication. But, if you want to create a Bitcoin wallet with a nice GUI, you will need to create a second layer of authentication which is the 3) password. So based on our design so far(a wallet with GUI), we can open a wallet by entering the correct password, if successful, it will open the wallet but that doesn’t mean it will automatically send Bitcoin out, for the user to send Bitcoin, she/he still need to do the the first layer of authentication which is the authentication using the private key.

So far, we have discussed these three important information for us to understand different types of Bitcoin wallets

  1. address, use to receive funds, like an email address to receive an email message
  2. private key, 1st layer of authentication, enough to be able to send Bitcoin
  3. password, 2nd layer of authentication, use to open a wallet

Note: You can make the 2nd layer of authentication more secure and complex by adding an OTP or One Time Password(time or sms based), since what we are trying to make is just a basic Bitcoin wallet, a password will suffice

Types of Custody

Now that we have discussed the three important information that makes up a Bitcoin wallet, we can now easily group them and identify the types of wallet custodies

Custodial
In a custodial wallet, information #2 and #3 are all stored in the wallet’s server, meaning the private and sensitive information or data that allows you to open a wallet and send out Bitcoin are stored in someone else’s server or computer, in this case, the user is letting the wallet’s creator, a third party, handle the storage and security of the user’s funds/Bitcoin, this is like putting your money in a bank instead of a vault in your house. You technically don’t own the funds anymore, if you are living in a place with an authoritarian government, they can order the wallet’s creator to freeze or seize your funds. And that defeats the purpose of having Bitcoin, because Bitcoin is created to be a freedom technology in the first place.

Self-custodial
In a self-custodial wallet, information #2 and #3 are the users’ responsibility to secure, they are not stored in someone else’s server or computer unless you specifically do it. This is like storing and securing your money in a physical vault at your house or a physical wallet in your pocket or bag, you are the one and sole responsible for keeping your funds/Bitcoin secure. That means no one can seize or freeze your funds unless your private key(#2) is exposed. There is a popular saying in Bitcoin coined by Andreas Antonopoulos, “Not your keys, not your coin.”.

For this series, self-custodial wallet is what we will be making, this is also the best way to store your real Bitcoins.

Project Setup

Install required Node.js library

Once the library is installed, create a src directory in the root folder

Generate a Legacy Address

There are four types of Bitcoin address, but you don’t need to worry about the other types because we will only focus on the oldest one which is the legacy address.

Types of Bitcoin address

  • Legacy: BIP44, Testnet addresses starts with m or n (mainnet: 1)
  • P2SH-Segwit: BIP49, Testnet addresses starts with 2 (mainnet: 3)
  • Native-Segwit: BIP84, Testnet addresses starts with tb1q (mainnet: bc1q)
  • Taproot: BIP86, Testnet addresses starts with tb1p (mainnet: bc1p)

Inside the src directory, create the first file, we will call it wallet.js, this will be the initial code.

We will generate a private key and address using the PrivateKey method

// wallet.js
const { PrivateKey } = require("bitcore-lib");
const { mainnet, testnet } = require("bitcore-lib/lib/networks");

const createLegacyWallet = (network = testnet) => {
let privateKey = new PrivateKey();
let address = privateKey.toAddress(network);

return {
privateKey: privateKey.toString(),
address: address.toString()
}
}

module.exports = {
createLegacyWallet
}

Then create the main file(index.js) inside src directory, this is where we are going to call the legacy wallet function (createLegacyWallet)

// index.js
const { mainnet, testnet } = require("bitcore-lib/lib/networks");
const { createLegacyWallet } = require("./wallet");

console.log(createLegacyWallet(testnet));

If we run the index.js file from our project’s root directory, it will print something like this, of course the actual values will be different from yours because the PrivateKey method will generate unique address and private key every time.

> node src/index.js

{
privateKey: 'b154c2376da64de119cde3f93d357acc9948c0ff24c51b4fe2165a74b2bd987c',
address: 'mjjnsMKWPWGwEfRGQCLjAXxPEk74eD1YA5'
}

And there you have it, our first legacy testnet Bitcoin address and private key. Note that we don’t need a password yet at this point because we are not creating a GUI yet.

Save the private key and address in an .env file at the project’s root directory because we will use it later.

// .env
WALLET_PRIVATE_KEY=b154c2376da64de119cde3f93d357acc9948c0ff24c51b4fe2165a74b2bd987c
WALLET_ADDRESS=mjjnsMKWPWGwEfRGQCLjAXxPEk74eD1YA5

Types of Bitcoin Networks

Before we proceed to the Bitcoin faucet, let’s discuss first the different types of Bitcoin network that’s available and which one are we going to use in this series and why.

  • regtest (regression test) — this is a network that you setup on your own, it can be on your local computer or a remote server, you can configure the block time and difficulty and it is usually accessible only by you, although this network is the most flexible one for testing, it’s also not the most user friendly network to use if you’re just starting out on Bitcoin development
  • testnet — this is what we will be using on this series, this is an online, free and public test network so we don’t have to setup anything like the regtest, so this is easier to use for development. testnet is the replica of the mainnet, this is usually the network where you test and deploy your Bitcoin app before deploying to mainnet
  • mainnet — production, actual bitcoin network where the real Bitcoin with real value in the real world is processed

Bitcoin Faucet

Now let’s try and play with a test BTC(Bitcoin), for us to do that we need to send some test BTC to our generated address using this faucet. A faucet is a website that gives out test BTC for free. A test BTC is like a play money, it doesn’t have a real value in the real world, its sole purpose is for testing only.

Note: We will not receive a whole BTC, what we will be receiving is what we call a sats or satoshis. Think of sats like cents and 1 Bitcoin as 1 dollar.

Sats are the smallest denomination of bitcoin that is recorded on the Bitcoin blockchain. One sat represents 0.00000001 BTC, or 1 one-hundred-millionth of a bitcoin. — Bitcoinmagazine

You just need to put your generated address in the faucet and answer the captcha.

Example:

Once successful, open the transaction id’s link on a new tab to verify the address’ balance.

As you can see from the highlighted part of the screenshot above, in this instance(amount of sats that the faucet will give out varies), the faucet successfully sent 0.00010082 sats to the address I generated above. You can check it for yourself here

Double-Entry vs Triple-Entry Accounting (WORK IN PROGRESS….)

Bitcoin is using a triple entry accounting, so I think it’s important to make a compariso about the usual accounting model so it’s easier to understand how we calculate the balance in Bitcoin

double entry only links and keeps the record of a specific entity or group so a financial record of another entity is completely unrelated, it requires auditors, auditors will be the who will need to trace it with another entity like a bank for example

Whereas triple entry records and link of everyone around the world

UTXO vs Account-Based Accounting(Ethereum)

Checking the balance using code

Install additional Node.js libraries

Once the new libraries have been installed, create another function inside the wallet.js to check the balance from blockcypher.com

Since we don’t have our own Bitcoin node(I might do a separate article for this), we will be using a third party Bitcoin node instead, in this case it’s a Bitcoin node from blockcypher.

Add this line in the .env file

// .env
UTXO_API=https://api.blockcypher.com/v1/btc/test3/addrs

In our wallet.js file, create a get balance function.

This part assumes you already know how to make an http request using axios.

Since the purpose of this function is to check the updated balance, notice that we specified a includeScript=true and unspentOnly=true filters in our API call. This call will return the unspent transactions outputs only or UTXOs because address’ balance is the sum of all available UTXOs.

Note: This filter is unique to blockcypher’s API, other providers will have a different way or filters.

const getBalance = async () => {
let totalAmountAvailable = 0;

const response = await axios.get(
`${process.env.UTXO_API}/${process.env.WALLET_ADDRESS}?includeScript=true&unspentOnly=true`
);

for (const element of response.data.txrefs) {
totalAmountAvailable += element.value;
}

return totalAmountAvailable;
}

// Our export will now look like this
module.exports = {
createLegacyWallet,
getBalance
}

Then in index.js file, add the following lines

// index.js
...
...

require('dotenv').config()

getBalance()
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});

...
...

If we run the index file, it should display the updated balance in sats.

In Part 2, we will create a transaction or send sats to another testnet address.

--

--