Developing a Command-Line Bitcoin Wallet in Go

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setup
  4. Creating a Command-Line Bitcoin Wallet - Initializing the Wallet - Generating a Bitcoin Address - Displaying Wallet Balance - Sending Bitcoin

  5. Conclusion

Introduction

In this tutorial, we will develop a command-line Bitcoin wallet using Go programming language. The purpose of this tutorial is to provide a hands-on experience in building a basic Bitcoin wallet with functionalities such as generating a Bitcoin address, checking wallet balance, and sending Bitcoin transactions.

By the end of this tutorial, you will be able to create your own command-line Bitcoin wallet and perform basic wallet operations using Go.

Prerequisites

To follow along with this tutorial, you should have basic knowledge of Go programming language, including variables, functions, loops, and error handling. Additionally, you should have Go installed on your machine.

Setup

Before we start building our command-line Bitcoin wallet, let’s ensure we have the necessary setup in place.

  1. Install Go by following the official installation guide for your operating system: Go Installation Guide

  2. Verify the Go installation by opening a terminal and running the following command:

     go version
    

    If the installation is successful, it should display the installed Go version.

Creating a Command-Line Bitcoin Wallet

Initializing the Wallet

Let’s begin by creating a new Go project for our Bitcoin wallet. Open a terminal and navigate to the desired directory where you want to create your project.

  1. Create a new directory for the project:

     mkdir bitcoin-wallet
     cd bitcoin-wallet
    
  2. Initialize a new Go module for our project:

     go mod init github.com/your-username/bitcoin-wallet
    

    Make sure to replace your-username with your actual GitHub username or any other preferred username.

  3. Create a new Go file named wallet.go and open it in a text editor.

     touch wallet.go
    

    Now we have our project setup ready to start building the Bitcoin wallet.

Generating a Bitcoin Address

To generate a Bitcoin address, we will use a Go package named btcd. This package provides a set of functions for working with Bitcoin.

  1. Import the required packages at the beginning of wallet.go file:

     package main
        
     import (
     	"fmt"
     	"github.com/btcsuite/btcutil"
     )
    
  2. Define a function named generateAddress that will generate a Bitcoin address:

     func generateAddress() {
     	// Generate a new private key
     	privateKey, err := btcutil.NewPrivateKey(btcutil.Secp256k1)
     	if err != nil {
     		fmt.Println("Error generating private key:", err)
     		return
     	}
        
     	// Derive the corresponding public key
     	publicKey := privateKey.PubKey()
        
     	// Convert public key to Bitcoin address
     	address, err := btcutil.NewAddressPubKey(publicKey.SerializeUncompressed(), &btcutil.MainNetParams)
     	if err != nil {
     		fmt.Println("Error generating Bitcoin address:", err)
     		return
     	}
        
     	// Display the generated Bitcoin address
     	fmt.Println("Generated Bitcoin Address:", address.EncodeAddress())
     }
    

    Here, we use the btcutil.NewPrivateKey function to generate a new private key, privateKey.PubKey() to derive the public key, and btcutil.NewAddressPubKey to convert the public key to a Bitcoin address.

  3. Inside the main function, call the generateAddress function:

     func main() {
     	fmt.Println("Welcome to the Bitcoin Wallet!")
     	generateAddress()
     }
    
  4. Save and close the wallet.go file.

    Now, if you run go run wallet.go in the terminal, it will display the generated Bitcoin address.

Displaying Wallet Balance

To display the wallet balance, we will use a simple API provided by a public blockchain explorer.

  1. Import the necessary packages:

     package main
        
     import (
     	"fmt"
     	"github.com/btcsuite/btcutil"
     	"io/ioutil"
     	"net/http"
     )
    
  2. Define a function named getBalance that will fetch the wallet balance:

     func getBalance(address string) {
     	url := fmt.Sprintf("https://blockchain.info/q/addressbalance/%s", address)
     	response, err := http.Get(url)
     	if err != nil {
     		fmt.Println("Error fetching balance:", err)
     		return
     	}
     	defer response.Body.Close()
        
     	body, err := ioutil.ReadAll(response.Body)
     	if err != nil {
     		fmt.Println("Error reading response body:", err)
     		return
     	}
        
     	fmt.Println("Wallet Balance:", string(body))
     }
    

    Here, we use the http.Get function to send a GET request to the blockchain explorer API. We read the response body and display the wallet balance.

  3. Modify the main function to call the getBalance function with a specific address:

     func main() {
     	fmt.Println("Welcome to the Bitcoin Wallet!")
        
     	address := "YOUR_BITCOIN_ADDRESS"
     	generateAddress()
     	getBalance(address)
     }
    

    Make sure to replace YOUR_BITCOIN_ADDRESS with the actual Bitcoin address you generated.

  4. Save and close the wallet.go file.

    Now, when you run go run wallet.go, it will display both the generated Bitcoin address and the wallet balance.

Sending Bitcoin

To send Bitcoin from our wallet, we will use a public Bitcoin testnet provided by the go-bitcoin package.

  1. Import the required packages:

     package main
        
     import (
     	"fmt"
     	"github.com/btcsuite/btcd/chaincfg"
     	"github.com/btcsuite/btcutil"
     	"github.com/btcsuite/btcutil/hdkeychain"
     	"log"
     )
    
  2. Define a function named sendBitcoin that will send Bitcoin from the wallet:

     func sendBitcoin(seedPhrase []string, amount int64, toAddress string) {
     	// Create a wallet from the seed phrase
     	masterKey, err := hdkeychain.NewMaster(parseSeedPhrase(seedPhrase), &chaincfg.TestNet3Params)
     	if err != nil {
     		log.Fatal("Error creating wallet:", err)
     	}
        
     	// Derive the account key
     	accountKey, err := masterKey.Child(hdkeychain.HardenedKeyStart + 0)
     	if err != nil {
     		log.Fatal("Error deriving account key:", err)
     	}
        
     	// Derive the external address key
     	addressKey, err := accountKey.Child(0)
     	if err != nil {
     		log.Fatal("Error deriving address key:", err)
     	}
        
     	// Get the public key associated with the address
     	publicKey, err := addressKey.ECPubKey()
     	if err != nil {
     		log.Fatal("Error getting public key:", err)
     	}
        
     	// Convert the public key to a Bitcoin address
     	address, err := publicKey.Address(&chaincfg.TestNet3Params)
     	if err != nil {
     		log.Fatal("Error generating Bitcoin address:", err)
     	}
        
     	// Create a new transaction
     	transaction := btcutil.NewTx(nil)
        
     	// Set the recipient address
     	to, err := btcutil.DecodeAddress(toAddress, &chaincfg.TestNet3Params)
     	if err != nil {
     		log.Fatal("Error decoding recipient address:", err)
     	}
     	pkScript, err := btcutil.PayToAddrScript(to)
     	if err != nil {
     		log.Fatal("Error creating pay-to-address script:", err)
     	}
     	outpoint := wire.NewOutPoint(&chainhash.Hash{}, math.MaxUint32)
     	txOut := wire.NewTxOut(amount, pkScript)
     	txIn := wire.NewTxIn(outpoint, nil, nil)
     	tx := wire.NewMsgTx(wire.TxVersion)
     	tx.AddTxIn(txIn)
     	tx.AddTxOut(txOut)
        
     	// Sign the transaction
     	sigScript, err := addressKey.Sign(tx.SignatureHash(0, sigHashNone), txscript.SigHashAll, publicKey.Serialize())
     	if err != nil {
     		log.Fatal("Error signing transaction:", err)
     	}
     	txIn.SignatureScript = sigScript
        
     	// Send the transaction
     	txHash, err := client.SendRawTransaction(tx, true)
     	if err != nil {
     		log.Fatal("Error sending transaction:", err)
     	}
        
     	fmt.Println("Successfully sent Bitcoin with transaction hash:", txHash)
     }
    

    Here, we use the hdkeychain.NewMaster function to create a wallet from the seed phrase, derive the account key, derive the external address key, and get the public key associated with the address.

    We create a new transaction, set the recipient address, sign the transaction using our private key, and send the transaction using the Bitcoin testnet.

  3. Modify the main function to call the sendBitcoin function with appropriate parameters:

     func main() {
     	fmt.Println("Welcome to the Bitcoin Wallet!")
        
     	seedPhrase := []string{"word1", "word2", "word3"} // Replace with your actual seed phrase
        
     	address := "YOUR_BITCOIN_ADDRESS" // Replace with your actual Bitcoin address
     	toAddress := "RECIPIENT_BITCOIN_ADDRESS" // Replace with the Bitcoin address of the recipient
     	amount := int64(1000000) // Specify the amount to send in satoshis (e.g., 0.01 BTC)
        
     	generateAddress()
     	getBalance(address)
     	sendBitcoin(seedPhrase, amount, toAddress)
     }
    

    Make sure to replace YOUR_BITCOIN_ADDRESS with your actual Bitcoin address and RECIPIENT_BITCOIN_ADDRESS with the Bitcoin address of the recipient.

  4. Save and close the wallet.go file.

    Now, when you run go run wallet.go, it will generate a Bitcoin address, display the wallet balance, and send a Bitcoin transaction to the specified recipient.

Conclusion

Congratulations! You have successfully developed a command-line Bitcoin wallet in Go. In this tutorial, we covered the basics of generating a Bitcoin address, checking wallet balance, and sending Bitcoin transactions.

By understanding the concepts and code presented in this tutorial, you can further enhance the wallet functionality or explore more advanced features of Bitcoin programming in Go.