DEV Community

Cover image for How to Build a Bitcoin Custodian Wallet with Inquirer JS
Dauda Lawal
Dauda Lawal

Posted on • Originally published at daslaw.hashnode.dev

How to Build a Bitcoin Custodian Wallet with Inquirer JS

Table of Content

Introduction

Building a custodian wallet using Inquirer JS can be a great way to ensure the security of your cryptocurrency assets. A custodian wallet is a type of wallet that is managed by a third party, usually a financial institution, that has custody over your cryptocurrency assets. By using Inquirer JS, you can create a custodian wallet that provides a high level of security and control over your assets.

In this article, I will walk you through how to build a non-custodial Bitcoin wallet using Inquirer JS, block API, and JavaScript. I would like to mention that this project was facilitated by Forward School.

Prerequisites

  • Bitcoin knowledge

  • Nodejs installed

  • JavaScript knowledge

Libraries used:

  • Inquirer (A Node.js library for creating interactive CLI prompts.).

  • Block.io API(A versatile and secure API wallet for your coins).

How it works

You will create a custodian wallet to receive and send bitcoin.

Our custodian wallet will be able to:

  • Allow the creation of a new user account

  • Sending of bitcoin to the wallet

  • Retrieve Address |Transaction | Bitcoin Balance

  • Get help

What is Custodian Wallet?

A custodial wallet is defined as a wallet in which private keys are held by a third party, meaning the third party has full control over your funds while you only have to permit to send or receive payments. In a custodial wallet, users' private keys are kept safe by a third party on their behalf. The third party is in complete control of the user's crypto assets and is in charge of managing their wallet key, signing transactions, and keeping their crypto assets safe.

Custodial wallets are typically offered by cryptocurrency exchanges or a custodial wallet provider as a web or mobile app. Users use the interface provided by the wallet provider to manage their funds and carry out transactions after logging into their wallet account.

How to build your Custodian Wallet using Inquirer JS

Step 1: Install Inquirer and other dependencies

Before we can start building your custodian wallet, you need to install Inquirer JS. Inquirer is a Node.js-based library with a command-line interface that makes it easy to interact with users and gather information.

To install Inquirer, you will need to have Node.js installed on your computer. If you don't have Node.js installed, you can download it from the Node.js website.

Once you have Node.js installed, you can install Inquirer, sqlite3, block_io, and dotenv by running the following commands in your terminal:

npm i --save-dev inquirer sqlite3 block_io dotenv
Enter fullscreen mode Exit fullscreen mode

Navigate to your Package.json to view your dependencies

{
  "type": "module",
  "dependencies": {
    "block_io": "^4.2.1",
    "dotenv": "^16.0.3",
    "inquirer": "^9.1.4",
    "sqlite3": "^5.1.4"
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Set up the basic wallet structure

To get started with your custodian wallet, we need to set up the basic structure of our wallet. You will create a new folder for your wallet, and inside that folder, you will create a file called wallet.js. This file will be the main file for your wallet, and it will contain all the code that you need to build your custodian wallet.

import dotenv from 'dotenv'
import BlockIo from 'block_io'
import sqlite3 from "sqlite3";

dotenv.config();
Enter fullscreen mode Exit fullscreen mode

export default class Wallet

{
    static async send(amount, from_label, to_addresses) {
        /** prepare transaction */
        const block_io = new BlockIo(
            process.env.BLOCK_IO_KEY,
            process.env.BLOCK_IO_PIN
        )

        try {
            // Withdraw to our new address
            // get the data to create the transaction
            let prepared_transaction = await block_io.prepare_transaction({
                from_labels: from_label,
                to_addresses: to_addresses,
                amount: `${amount}`
            });

            // summarize the transaction we are going to prepare
            // for in-depth review, look at prepared_transaction yourself
            let summarized_transaction = await block_io.summarize_prepared_transaction({data: prepared_transaction});
            console.log(JSON.stringify(summarized_transaction, null, 2));

            // after review, if you wish to approve the transaction: create and sign it
            let signed_transaction = await block_io.create_and_sign_transaction({data: prepared_transaction});
            console.log(JSON.stringify(signed_transaction, null,2));

            // review the signed transaction (specifically, the tx_hex) and ensure things are as you want them
            // if you approve of it, submit the transaction for Block.io's signatures and broadcast to the blockchain network
            // if successful, the data below contains the transaction ID on the blockchain network
            let data = await block_io.submit_transaction({transaction_data: signed_transaction});
            console.log(JSON.stringify(data, null, 2));

            return data
        }
        catch(err) {
            console.log(err)
            return {
                error: err
            }
        }
    }

    static async receive(user_label, result) {
        const db = new sqlite3.Database('wallet.db');

        /** retrieve wallet info **/
        let sql = `SELECT * FROM wallet WHERE user_label='${user_label}'`
        db.get(sql, async (err, row) => {
            if (err) return result({
                error: `An error occurred retrieving wallet`
            })
            if (row == undefined) return result({
                error: 'No wallet found'
            })

            return result({ row })
        })
    }

    static async balance(user_label, result) {
        /** generate wallet address with label */
        const block_io = new BlockIo(process.env.BLOCK_IO_KEY)
        let response

        try {
            response = await block_io.get_address_by_label({ label: user_label })
            //console.log(JSON.stringify(response, null, 2));

        }
        catch(err) {
            console.log(err)
            return result({
                error: err
            })
        }

        return result(response. Data)
    }

    static async create(user_label, result) {
        /** validate user */
        Wallet.found(user_label, async (found) => {

            if (found) return result({
                error: `Duplicate user wallet`
            });

            /** generate wallet address with label */
            const block_io = new BlockIo(process.env.BLOCK_IO_KEY)
            let response

            try {
                response = await block_io.get_new_address({ label: user_label })
                console.log(JSON.stringify(response, null, 2));
            }
            catch(err) {
                console.log(err)
                return result({
                    error: err
                })
            }

            const address = response.data.address

            /** Save wallet in local db */
            const db = new sqlite3.Database('wallet.db');
            let sql = `INSERT INTO wallet(user_label, address) VALUES('${user_label}', '${address}')`

            db.run(SQL, (err) => {
                if (err) return result({
                    error: 'Error occured saving wallet'
                })

                return result(true)
            })

            return result(true)
        })
    }

    static async found(user_label, found) {
        const db = new sqlite3.Database('wallet.db');

        /** Prevent duplicate wallet label */
        let sql = `SELECT * FROM wallet WHERE user_label='${user_label}'`
        db.get(sql, async (err, row) => {
            if (err) return found(false)
            if (row == undefined) return found(false)

            return found(true)
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Wallet functions and importing the Sqlite3 library

You will also create a file calleddb.js that will contain all the data that your wallet needs to function and import the Sqlite3 library.

import sqlite3 from "sqlite3";

const db = new sqlite3.Database('wallet.db');

db.serialize(() => {
    db.run(`
        CREATE TABLE IF NOT EXISTS wallet (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            balance INTEGER DEFAULT 0,
            address TEXT UNIQUE,
            user_label TEXT UNIQUE
        )
    `);
});

db.close();
Enter fullscreen mode Exit fullscreen mode

Step 4: Wallet values and functions

Inside your folder, you will create a file called function.js. This file will contain all the information about your wallet's values and functions.

export function Satoshi(value) {
    if (isNaN(value))
        return 0

    return Math.floor(value * 100000000)
}

export function bitcoin(value) {
    if (isNaN(value))
        return 0

    value = parseInt(value) / 100000000
    let round = parseFloat(value.toFixed(8))

    return round
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Prompt to ask for the Get user input

Once you have set up the basic structure of your wallet, you can start getting user input. You will use Inquirer to ask the user for the information that you need to create your wallet. You will ask the user for the following information:

  • Wallet username

  • A wallet address

  • Get help

Inside your folder, we will create a file called view.js. This file will contain all the information on prompt functions to ask for the user's details and crypto wallet address, and methods used to handle any errors that might occur.

import inquirer from 'inquirer'
import Wallet from './wallet.js'
import { bitcoin } from './functions.js';

var ui = new inquirer.ui.BottomBar();

// prompt to ask for the user's crypto wallet address. We then call the prompt() 
// The then() method is used to log the user's wallet address
// catch() method is used to handle any errors that might occur

export function startView() {
    const options = [
        {
            type: 'list',
            name: 'authentication',
            message: `Welcome to ABDcrest Crypto Wallet`,
            choices: [
                {
                    key: 'login',
                    'name': 'Have an existing account: Log In',
                    value: 'login'
                },
                {
                    key: 'signup',
                    'name': 'New User, Click here to sign Up',
                    value: 'signup'
                },
                {
                    key: 'help',
                    'name': 'Need help',
                    value: 'help'
                },

                new inquirer. Separator('\n'),
                new inquirer. Separator(),
                'Do you need help? Click need help above'
            ]
        }
    ]


    inquirer
    .prompt(options)
    .then((answers) => {
        if (answers. Authentication == 'login') {
            loginView()
        }
        else if (answers. Authentication == 'signup') {
            signupView();
        } else {
            helpView()
        }

    })

    .catch( (error) => {
            if (error.isTtyError) {
                console.log(`This app could not be opened in this terminal environment`)
            } else {
                console.log(`Something went wrong, Try Again`)
            }
        })
}

function loginView() {
    const options = [
        {
            type: 'input',
            name: 'username',
            message: `Enter your crypto wallet username`,
            validate: function(value) {
                var done = this.async();
                Wallet.found(value, (found) => {
                    if (!found) {
                        done('Wallet username not found')
                        return
                    }

                    done(true)
                })
            }
        }
    ]

    inquirer
        .prompt(options)
        .then((answers) => {
            dashboardView(answers. Username)
        })
        .catch( (error) => {
            console.log(error)
        })
}

function signupView() {


    const options = [
        {
            type: 'input',
            name: 'username',
            message: `Enter your wallet username`,
            validate: function(value) {
                var done = this.async();
                Wallet.found(value, (found) => {
                    if (found) {
                        done('Username already registered, go back and login')
                        return
                    }

                    done(true)
                })
            }
        }
    ]

    inquirer
        .prompt(options)
        .then((answers) => {

            Wallet.create(answers.username, (result) => {
                if (result. Error) {
                    ui.log.write("Error creating crypto wallet, Try Again");
                    return
                }

                dashboardView(answers. Username)
            })
        })
        .catch( (error) => {
            console.log(error)
        })
}

async function sendView(user) {
    const inputs = [
        {
            type: 'input',
            name: 'amount',
            message: `How much crypto do you want to send?`,
            validate(value) {
                let invalid = isNaN(value)

                if (invalid) {
                    return 'Enter a valid crypto amount to send'
                }

                return true
            }
        },
        {
            type: 'input',
            name: 'Wallet address',
            message: `What crypto address do you want to send to?`,
        }
    ]


    await inquirer
    .prompt(inputs)
    .then( async (answers) => {
        let amount = bitcoin(answers. Amount)
        let address = answers. Address

        const send = await Wallet.send(amount, user, address)

        if (send. Error) {
            ui.log.write('Hey, Error sending transaction: Try again')
            return
        }

        ui.log.write('Transaction sent' + amount)
    })
    .catch( (error) => {
        console.log(error)
    })
}

function helpView() {
    const options = [
        {
            type: 'list',
            name: 'help',
            message: 'What do you need help with?',
            choices: ['How to send crypto', 'How to receive crypto', 'How to check your balance'],
        }
    ]

    inquirer
        .prompt(options)
        .then((answers) => {
                if (answers. Help === 'How to send crypto') {
                  console.log('Here are the steps to send crypto:');
                  console.log('1. Log in to your wallet');
                  console.log('2. Click on "Send"');
                  console.log('3. Enter the recipient address and amount');
                  console.log('4. Click "Send"');
                } else if (answers.help === 'How to receive crypto') {
                  console.log('Here are the steps to receive crypto:');
                  console.log('1. Log in to your wallet');
                  console.log('2. Click on "Receive"');
                  console.log('3. Copy your wallet address');
                  console.log('4. Share your wallet address with the sender');
                } else if (answers.help === 'How to check your balance') {
                  console.log('Here are the steps to check your balance:');
                  console.log('1. Log in to your wallet');
                  console.log('2. Navigate to the "Balance" section');
                  console.log('3. Your balance will be displayed there');
                } else {
                  console.log('Invalid option selected.');
                }
              })
              .catch((error) => {
                console. Error(error);
              });

}


function dashboardView(user) {

    const options = [
        {
            type: 'list',
            name: 'wallet',
            message: `What do you want to do?`,
            choices: [
                {
                    key: 'send',
                    'name': 'Send crypto',
                    value: 'send'
                },
                {
                    key: 'receive',
                    'name': 'Receive crypto',
                    value: 'receive'
                },
                {
                    key: 'balance',
                    'name': 'View your crypto wallet balance',
                    value: 'balance'
                },
                new inquirer. Separator('\n'),
                new inquirer. Separator(),
                'Exit',
            ]
        }
    ]
    inquirer
    .prompt(options)
    .then( (answers) => {    

        if (answers. Wallet == 'send') {
            sendView(user)
        }
        else if (answers. Wallet == 'receive') {
            Wallet.receive(user, (result) => {
                if (result. Error) {
                    ui.log.write("Could not retrieve wallet address. Address may not be found.");
                    return;
                }

                ui.log.write(`your payment address: ${result.row.address}`);
                return;
            })
        }
        else {
            Wallet.balance(user, (result) => {
                if (result. Error) {
                    ui.log.write("Could not retrieve wallet balance. Wallet Address may not be found.");
                    return;
                }

                ui.log.write(`Your crypto balance: ${result.available_balance}`);
                ui.log.write(`Your pending crypto balance: ${result.pending_received_balance}`)
                return;
            })
        }
    })
    .catch( (error) => {

        console.log(error)
    })
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Creating an app.js file

Inside your folder, you will create a file called app.js. This file will import your views.js.

import { startView } from './views.js'
startView()
Enter fullscreen mode Exit fullscreen mode

Step 7: Adding API key

Inside your folder, we will create a file called .env. This file will contain all the information about your wallet, including the private key and public pin from Blockio.

Create an account to access Block API(Block.io)

BLOCK_IO_KEY = 'debd-afbb-fee1-be82'
BLOCK_IO_PIN = 'qwertyui334accessxt'
Enter fullscreen mode Exit fullscreen mode

Step 8: Create a Database

Run the following command in your terminal to create your database:

node db.js
Enter fullscreen mode Exit fullscreen mode

Step 9: Test your project CLI

Now, run this command in your terminal to test your project.

node app.js
Enter fullscreen mode Exit fullscreen mode
? Welcome to ABDcrest Crypto Wallet (Use arrow keys)
❯ Have an existing account: Log In 
  New User, Click here to sign Up 
  Need help 


  ──────────────
  Do you need help? Click need help above
Enter fullscreen mode Exit fullscreen mode

We've been able to build a blockchain custodian wallet using JavaScript.

Conclusion

Building a Bitcoin custodian wallet requires careful planning and attention to detail. You need to ensure that the wallet is secure, user-friendly, and reliable. In this article, we have outlined the key steps involved in building a custodian wallet using Node.js and the inquirer module. Building a Bitcoin custodian wallet can be a challenging task, but by following the steps outlined in this article, you can create a secure, reliable, and user-friendly wallet that meets the needs of your clients.

You may always look at the code on my GitHub repository if you get stuck anywhere.

Don’t forget to share, like, and comment.

Credit Forward School

Top comments (0)