Build A Bitcoin Bank Using Bitcoin’s JSON RPC And Typescript

Raphael Osaze Eyerin
7 min readApr 25, 2022
Illustration by In Bitcoin We Trust

We were to choose Bitcoin projects to work on last week in the Qala Africa Bitcoin / Lightning development program's first cohort, I chose the Bitcoin bank project and built an MVP API using Typescript I thought it would be nice to write about my experience hence this article.

The outline for the Bitcoin bank project was:

  • Build a bank API using Bitcoin’s JSON RPC
  • Users should be able to create an account (a Bitcoin address should be generated when a user account is created)
  • Users should be able to make inbound and outbound transactions
  • Users' addresses should be updated after an inbound transaction is confirmed and their balance should be updated
  • The bank funds should be shared with the ratio of 80:20 80% of the funds should be kept in the bank’s cold wallet while 20% should be kept in the bank’s hotwallet

Project Use case

The use case for the Bitcoin bank project is a Bitcoin exchange platform that runs its own Bitcoin node and users use the exchange platform to carry out their Bitcoin transactions example Bitcoin exchanges like Binance.

Prerequisites

  • A running Bitcoin Core node on Signet or Tesnet network
  • Nodejs installed a minimum of version 14.19.0
  • PostgresDB installed
  • Typescript experience
  • Bitcoin development knowledge

How It Works

  • The project works with 2 Bitcoin wallets: a cold and hot wallet, ideally the cold wallet is supposed to be a Bitcoin cold storage but for the purpose of this project we will use 2 Bitcoin core wallets. When you run the project npm run dev it checks for the number of wallets on your node if none it creates 2 wallets named ‘coldwallet’, and ‘hotwallet’, if one wallet is available it creates one wallet named hotwallet. You can set the wallet names in the .env HOTWALLETNAME= COLDWALLETNAME= file if you have wallets already on your node and they are not named coldwallet and hotwallet.
  • Every hour a check is done to make sure the hotwallet has 20% of the total funds while the rest of the 80% is in the cold wallet, if the hotwallet has more than 20% the amount of the percentage excess will be transferred to the cold wallet, also if the coldwallet has 80% the amount of the percentage that exceeds 80% will be transferred to the hotwallet
  • When you create a user using the api/v1/user/register endpoint it creates a new Segwit user to the database table
  • Outbound transactions use Bitcoin’s JSON RPC sendtoaddress command to create and broadcast a transaction
  • Inbound transactions are checked every 10 mins (Bitcoin’s block time) after the confirmation meets the confirmation count specified in the .env it updates the user’s balance and creates a new address for them

Why split the bank funds?

We all know the internet is a scary place, and a hot wallet is a wallet connected it will be extremely risky to keep the whole of the bank funds in a hot wallet, if eventually the hot wallet gets hacked it will be a complete disaster users' funds will be totally lost which should never happen hence the need for a cold wallet (a wallet not connected to the internet). In this project, I simulated a cold wallet by creating two (2) Bitcoin wallets in my Bitcoin, the hotwallet process all transactions, and create users' addresses, while the coldwallet is just used for storage.

Wallets creation code

The code is self-explanatory I did my best to write comments to explain how the code works, When the app is run the code checks to see the number of wallets on the Bitcoin node using Bitcoin’s RPC command

bitcoin-cli listwallets

The command returns the wallet array list, then it checks the wallet count if it's greater or equal to 2 it returns else it creates the number of required wallets. The app needs to wallet to run

Note: If you have existing wallets on your node and they are not loaded the list wallets command won’t list them. You can easily load your wallet using this command

bitcoin-cli loadwallet "wallet"

Updating user's balance after inbound transaction and wallet balances settlement

I created a file name cron.ts in the services folder the corn function has two (2) setInterval functions one of the setInterval functions checks for users' inbound transactions every ten(10) minutes (Bitcoin’s block time), while the other settles hot and cold wallet balances every hour (After 6 transaction confirmations).

import { getReceived, checkWalletBalances } from "./cronfunctions";export const cron = () => {
// Check for receive transactions every 10 minutes
setInterval(() => {
getReceived();
}, 1000 * 60 * 10);
// Check the wallet balance every hour
setInterval(() => {
checkWalletBalances();
}, 1000 * 60 * 60);
}

How the getReceived function works

Every ten(10) minutes the function is run it gets Bitcoin transactions using Bitcoin’s RPC list transactions command

bitcoin-cli listtransactions

By default, the command list the top ten(10) recent transactions, but the command can list more than 10 transactions if you add the number of transactions you want to list, it can also be paginated you can read more about the listtransactions command.

After the transactions are listed, the function gets each transaction receiving address and makes a search in the user's address table if the address matches any of the user's addresses the transaction will be added to the transaction logs, If any of Bitcoin bank’s user's inbound transaction has met the confirmation number set in the app’s .env file the transaction amount will be added to to the user's balance, the transaction will be updated in the transaction logs, and a new Segwit bech32 address will be generated for the user.

How the checkWalletBalances function works

The function splits the bank funds between the hot and cold wallets every hour, the function gets the balances of both wallets using Bitcoin’s RPC get balance command

bitcoin-cli -rpcwallet="walletname" getbalance

the -rpcwallet flag is not necessary if only one(1) wallet is present on the Bitcoin node, if the wallets are more than one the wallet has to be specified using the command.

After getting both balances, they are summed up to get the bank's total balance, then a calculation is carried out to get the percentage of the balance each wallet has If the hot wallet has more than 20% the amount of the excess percentage will be sent to the coldwallet, the cold wallet will also send its excess to the hot wallet if it holds more than 80% of the bank's total funds.

Note: After the excess is transferred, it takes a few minutes(transaction confirmations) before the new transaction amount will be added to the wallet balance. So if you want to adjust the checkWalletBalances setInterval function time interval have this in mind, else there will be an error of one wallet continuously sending its excesses to the other wallet.

How to Run

  • Clone the project git clone https://github.com/elraphty/BitcoinBank.git
  • Run npm install
  • Install Knex globally npm install knex -g
  • Start your Bitcoin Core node by running bitcoind in your terminal
  • Confirm if your node is running using bitcoin-cli -getinfo command
  • Create a .env file copy the variables in .env_sample file paste them in ur newly created .env file and update the values
  • Migrate Database schemas by running knex migrate:latest
  • Run npm run dev in the project folder

ENV Variables

PORT = 5001 // Server portRPC_USER= // Bitcoin node rpc username
RPC_PASS= // Bitcoin node rpc password
RPC_PORT=18443 // Bitcoin node rpc port
RPC_URL='127.0.0.1' // Bitcoin node url
DEV_DB_USER = '' // Postgresdb database username
DEV_DB_PASS = '' // Postgresdb database password
DEV_DB_NAME = '' // Postgresdb database name
TOKEN_SECRET = '' // Jsonwebtoken secret keyHOT_WALLET_NAME=hotwallet // Name for the bitcoin hot wallet
COLD_WALLET_NAME=coldwallet // Name for the bitcoin cold wallet
TRANS_FEE = 0.00000200 // Base transaction fee incase u can't estimate the last transaction fee
FEE_PERCENT = 1 // The transaction fee the bank takes for outbound transactions
NO_OF_CONFIRMATIONS = 6 // No of transaction confirmations before adding transaction value to a users' balance

API Routes

  • GET /api/v1 = Base route
  • POST /api/v1/user/register = Register a user, create a Segwit bech32 address for the user, and create a user default balance of 0 bitcoin
  • POST /api/v1/user/login = User login
  • GET /api/v1/user/address = Get user bitcoin segwit address for inbound transactions
  • GET /api/v1/user/balance = Get user bitcoin balance
  • POST /api/v1/wallet/createtransaction = Create an outbound transaction, and update the user’s balance
  • GET /api/v1/wallet/transactions = Get user transactions

Simple Improvements You Can Make

At the moment only a Segwit bech32 address is created for a user but the address creation supports the creation of 3 types of addresses: Bitcoin’s legacy P2PKH address (Pay To Public Key Hash), Segwit bech32, and Segwit’s P2SH address (Pay to Script Hash)

Address enum

export enum addressType {
p2sh = 'p2sh',
p2pkh = 'p2pkh',
bech32 = 'bech32',
}

Address creation function

So you can refactor the database schema to have columns for different address types.

Conclusion

This is just a basic example of how Bitcoin exchanges work, a real exchange will have more advanced codes and many other features, but this should be a good starting point for beginners to learn how to create their own Bitcoin exchange while running their own Bitcoin node. You can register for the Qala program If you are an African developer who wants to transition to Bitcoin / Lightning development.

Thank you.

--

--