Build A Bitcoin Non-Custodial HD Web Wallet Using Typescript

Wallet index page

You need a Bitcoin wallet to perform transactions on the Bitcoin network, Bitcoin wallets can be categorized into two categories: custodial wallets, and non-custodial wallets.

Custodial wallets

Custodial wallets are wallets that don’t give users access to their private key, the private key is held by a third party and users don’t have full control of their bitcoins, centralized exchanges are good examples of custodial wallets.

Non-Custodial wallets

A non-custodial wallet gives users access to their private key and mnemonic words if the wallet is a BIP39 HD wallet, users also have full control of their bitcoins.

Hierarchical Deterministic (HD) wallets

Hierarchical deterministic wallets are wallets that can generate a series of keypairs from one seed, i.e with your wallet seed you can regenerate all your keys, and addresses over and over again, unlike non-deterministic that generate private keys that are random and independent of each other, there is no particular pattern as to how the keys are derived hence the keys need to be backed up each time there is a new one.

In this article, I will walk you through how to build a non-custodial Bitcoin wallet using Typescript, bitcoinjs-lib, and Blockstream Testnet APIs.


  • Bitcoin knowledge
  • Nodejs installed
  • Typescript knowledge
  • Reactjs/Nextjs knowledge

Libraries used:

  • bitcoinjs-lib (A Javascript library for interacting with Bitcoin).
  • bip32 (A Javascript library that implements BIP32), BIP32 brought about the use of HD wallets.
  • bip39 (A Javascript library that implements BIP39), BIP39 brought about the use of mnemonic words for HD wallets.
  • coinselect (A Javascript library used for selecting UTXOs for transactions).

How it works:

  • Users have to register using their email and password (I chose to use email, it could be a username or a pseudo name what’s really important is the user’s password which is used with the mnemonic phrase to generate a seed)

Mnemonic code

// Controller for generating mnemonic seedexport const generateMnenomic = (req: Request, res: Response, next: NextFunction): void => {
try {
const mnemonic = generateMnemonic(256);
responseSuccess(res, 200, 'Successfully generated mnemonic', mnemonic);
} catch (err) {
  • After providing their registration details a 24-word mnemonic phrase is generated users have to copy the mnemonic phrase to proceed (The mnemonic phrase has to be kept securely, anyone who has access to a user’s mnemonic phrase can regenerate the user's keys, and spend their bitcoins), the user’s password can be used to add another level of security as I did on this app i.e the wallet seed is created with the mnemonic phrase and password which also comes with a caveat: if the users forget their password they can’t regenerate the wallet seed, because it was created using the mnemonic words and password.
  • After the wallet seed is generated, it will be used to generate the user’s master private key and master public key

Seed and master keys code

  • The master public key will be used to generate child public keys, then child public keys will be used to generate the user’s addresses, I generated 10 receive and change addresses for the user using different derivation paths the receiving addresses use path 0/{n}, while the change addresses use path 1/{n}. You can learn more about derivation paths by reading this article.

Addresses code

  • Users can create Pay To Script Hash (P2SH) addresses, view Pay To Public Key Hash (P2PKH), and Pay To Witness Public Key Hash (P2WPKH).
Address page
  • The process of creating a P2SH address is different from that of P2PKH, and P2WPKH, users have to provide public keys and the number of required signatures to sign transactions sent to the address.

P2SH creation form

  • Users can view their transactions and UTXOs, they can also make P2WPKH, and P2PKH outbound transactions
  • To receive inbound transactions, an address type needs to be selected, P2WPKH is the default. A QRcode will be generated which can be scanned on Bitcoin wallets, the address can also be copied to the clipboard.
  • On the settings page, users can copy their master private keys, master public keys, and also P2SH addresses and redeem scripts.
Settings page

Why copy P2SH addresses?

P2SH addresses are exported separately because if they use non-standard scripts they cannot necessarily be generated from the wallet seed. If a user fails to export their P2SH addresses and redeem scripts used, funds sent to those addresses could potentially be lost forever. However, features like Bitcoin output script descriptors can come in handy in such situations if the wallet is a descriptor wallet.

How to run

To run the backend

  • Run cd backend
  • Run npm install
  • Create a .env file touch .env
  • Copy the placeholders from the .env.sample file into your .env
  • Create a local Postgres database
  • Update the .env variable values with yours
  • After creating and updating the .env file
  • Run knex migrate:latest (Knex will create and migrate the database schema for you)
  • Run npm run dev

To run the frontend

  • cd client
  • yarn install
  • npm run dev


When choosing a Bitcoin wallet, you should know if you don’t have access to your private key you are not the real owner of the bitcoins in the wallet, you can be frozen out at any time by the third party who holds the private key, you can use custodial wallets for keeping a small number of bitcoins, but it is not advisable to store a large number of bitcoins in a custodial wallet, although using a non-custodial wallet comes with a lot of responsibilities for users: they have to keep their private keys safe, and also their mnemonic phrase if the wallet is an HD wallet. Anyone with the mnemonic phrase can regenerate the same keys and spend their bitcoins.

If you are an African developer and you wish to transition into Bitcoin development Qala is the right program for you, register and join the Bitcoin Bandwagon it has been an interesting journey for me.

Thank you.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store